diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/source/gdi | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/gdi')
45 files changed, 45360 insertions, 0 deletions
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx new file mode 100644 index 0000000000..bcf6f54639 --- /dev/null +++ b/vcl/source/gdi/CommonSalLayout.cxx @@ -0,0 +1,850 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/configmgr.hxx> +#include <o3tl/temporary.hxx> + +#include <vcl/unohelp.hxx> +#include <vcl/font/Feature.hxx> +#include <vcl/font/FeatureParser.hxx> +#include <vcl/svapp.hxx> + +#include <ImplLayoutArgs.hxx> +#include <TextLayoutCache.hxx> +#include <font/FontSelectPattern.hxx> +#include <salgdi.hxx> +#include <sallayout.hxx> + +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> + +#include <unicode/uchar.h> +#include <hb-ot.h> +#include <hb-graphite2.h> +#include <hb-icu.h> + +#include <map> +#include <memory> + +GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont) + : m_GlyphItems(rFont) + , mpVertGlyphs(nullptr) + , mbFuzzing(utl::ConfigManager::IsFuzzing()) +{ +} + +GenericSalLayout::~GenericSalLayout() +{ + if (mpVertGlyphs) + hb_set_destroy(mpVertGlyphs); +} + +void GenericSalLayout::ParseFeatures(std::u16string_view aName) +{ + vcl::font::FeatureParser aParser(aName); + const OUString& sLanguage = aParser.getLanguage(); + if (!sLanguage.isEmpty()) + msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US); + + for (auto const &rFeat : aParser.getFeatures()) + { + hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd }; + maFeatures.push_back(aFeature); + } +} + +namespace { + +struct SubRun +{ + int32_t mnMin; + int32_t mnEnd; + hb_script_t maScript; + hb_direction_t maDirection; +}; + +} + +namespace { + int32_t GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag) + { + // Override orientation of fullwidth colon , semi-colon, + // and Bopomofo tonal marks. + if ((cCh == 0xff1a || cCh == 0xff1b + || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9) + && rTag.getLanguage() == "zh") + return U_VO_TRANSFORMED_UPRIGHT; + + return u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION); + } +} // namespace + +SalLayoutGlyphs GenericSalLayout::GetGlyphs() const +{ + SalLayoutGlyphs glyphs; + glyphs.AppendImpl(m_GlyphItems.clone()); + return glyphs; +} + +void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft) +{ + if (nCharPos < 0 || mbFuzzing) + return; + + using namespace ::com::sun::star; + + if (!mxBreak.is()) + mxBreak = vcl::unohelper::CreateBreakIterator(); + + lang::Locale aLocale(rArgs.maLanguageTag.getLocale()); + + //if position nCharPos is missing in the font, grab the entire grapheme and + //mark all glyphs as missing so the whole thing is rendered with the same + //font + sal_Int32 nDone; + int nGraphemeEndPos = + mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale, + i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + // Safely advance nCharPos in case it is a non-BMP character. + rArgs.mrStr.iterateCodePoints(&nCharPos); + int nGraphemeStartPos = + mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale, + i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + + // tdf#107612 + // If the start of the fallback run is Mongolian character and the previous + // character is NNBSP, we want to include the NNBSP in the fallback since + // it has special uses in Mongolian and have to be in the same text run to + // work. + sal_Int32 nTempPos = nGraphemeStartPos; + if (nGraphemeStartPos > 0) + { + auto nCurrChar = rArgs.mrStr.iterateCodePoints(&nTempPos, 0); + auto nPrevChar = rArgs.mrStr.iterateCodePoints(&nTempPos, -1); + if (nPrevChar == 0x202F + && u_getIntPropertyValue(nCurrChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN) + nGraphemeStartPos = nTempPos; + } + + //stay inside the Layout range (e.g. with tdf124116-1.odt) + nGraphemeStartPos = std::max(rArgs.mnMinCharPos, nGraphemeStartPos); + nGraphemeEndPos = std::min(rArgs.mnEndCharPos, nGraphemeEndPos); + + rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft); +} + +void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs) +{ + SalLayout::AdjustLayout(rArgs); + + if (rArgs.mpDXArray) + ApplyDXArray(rArgs.mpDXArray, rArgs.mpKashidaArray); + else if (rArgs.mnLayoutWidth) + Justify(rArgs.mnLayoutWidth); + // apply asian kerning if the glyphs are not already formatted + else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian) + && !(rArgs.mnFlags & SalLayoutFlags::Vertical)) + ApplyAsianKerning(rArgs.mrStr); +} + +void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const +{ + //call platform dependent DrawText functions + rSalGraphics.DrawTextLayout( *this ); +} + +// Find if the nominal glyph of the character is an input to “vert” feature. +// We don’t check for a specific script or language as it shouldn’t matter +// here; if the glyph would be the result from applying “vert” for any +// script/language then we want to always treat it as upright glyph. +bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector) +{ + sal_GlyphId nGlyphIndex = GetFont().GetGlyphIndex(aChar, aVariationSelector); + if (!nGlyphIndex) + return false; + + if (!mpVertGlyphs) + { + hb_face_t* pHbFace = hb_font_get_face(GetFont().GetHbFont()); + mpVertGlyphs = hb_set_create(); + + // Find all GSUB lookups for “vert” feature. + hb_set_t* pLookups = hb_set_create(); + hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE }; + hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups); + if (!hb_set_is_empty(pLookups)) + { + // Find the input glyphs in each lookup (i.e. the glyphs that + // this lookup applies to). + hb_codepoint_t nIdx = HB_SET_VALUE_INVALID; + while (hb_set_next(pLookups, &nIdx)) + { + hb_set_t* pGlyphs = hb_set_create(); + hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx, + nullptr, // glyphs before + pGlyphs, // glyphs input + nullptr, // glyphs after + nullptr); // glyphs out + hb_set_union(mpVertGlyphs, pGlyphs); + } + } + hb_set_destroy(pLookups); + } + + return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0; +} + +bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* pGlyphs) +{ + // No need to touch m_GlyphItems at all for an empty string. + if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0) + return true; + + if (pGlyphs) + { + // Work with pre-computed glyph items. + m_GlyphItems = *pGlyphs; + for(const GlyphItem& item : m_GlyphItems) + if(!item.glyphId()) + SetNeedFallback(rArgs, item.charPos(), item.IsRTLGlyph()); + // Some flags are set as a side effect of text layout, restore them here. + rArgs.mnFlags |= pGlyphs->GetFlags(); + return true; + } + + hb_font_t *pHbFont = GetFont().GetHbFont(); + bool isGraphite = GetFont().IsGraphiteFont(); + + int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos); + m_GlyphItems.reserve(nGlyphCapacity); + + const int nLength = rArgs.mrStr.getLength(); + const sal_Unicode *pStr = rArgs.mrStr.getStr(); + + std::optional<vcl::text::TextLayoutCache> oNewScriptRun; + vcl::text::TextLayoutCache const* pTextLayout; + if (rArgs.m_pTextLayoutCache) + { + pTextLayout = rArgs.m_pTextLayoutCache; // use cache! + } + else + { + oNewScriptRun.emplace(pStr, rArgs.mnEndCharPos); + pTextLayout = &*oNewScriptRun; + } + + // nBaseOffset is used to align vertical text to the center of rotated + // horizontal text. That is the offset from original baseline to + // the center of EM box. Maybe we can use OpenType base table to improve this + // in the future. + double nBaseOffset = 0; + if (rArgs.mnFlags & SalLayoutFlags::Vertical) + { + hb_font_extents_t extents; + if (hb_font_get_h_extents(pHbFont, &extents)) + nBaseOffset = ( extents.ascender + extents.descender ) / 2.0; + } + + hb_buffer_t* pHbBuffer = hb_buffer_create(); + hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity); + + const vcl::font::FontSelectPattern& rFontSelData = GetFont().GetFontSelectPattern(); + if (rArgs.mnFlags & SalLayoutFlags::DisableKerning) + { + SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName); + maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) }); + } + + if (rArgs.mnFlags & SalLayoutFlags::DisableLigatures) + { + SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName); + + // Both of these are optional ligatures, enabled by default but not for + // orthographically-required ligatures. + maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) }); + maFeatures.push_back({ HB_TAG('c','l','i','g'), 0, 0, static_cast<unsigned int>(-1) }); + } + + ParseFeatures(rFontSelData.maTargetName); + + double nXScale = 0; + double nYScale = 0; + GetFont().GetScale(&nXScale, &nYScale); + + basegfx::B2DPoint aCurrPos(0, 0); + while (true) + { + int nBidiMinRunPos, nBidiEndRunPos; + bool bRightToLeft; + if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft)) + break; + + // Find script subruns. + std::vector<SubRun> aSubRuns; + int nCurrentPos = nBidiMinRunPos; + size_t k = 0; + for (; k < pTextLayout->runs.size(); ++k) + { + vcl::text::Run const& rRun(pTextLayout->runs[k]); + if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd) + { + break; + } + } + + if (isGraphite) + { + hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode); + aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR }); + } + else + { + while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size()) + { + int32_t nMinRunPos = nCurrentPos; + int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos); + hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; + hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode); + // For vertical text, further divide the runs based on character + // orientation. + if (rArgs.mnFlags & SalLayoutFlags::Vertical) + { + sal_Int32 nIdx = nMinRunPos; + while (nIdx < nEndRunPos) + { + sal_Int32 nPrevIdx = nIdx; + sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx); + int32_t aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag); + + sal_UCS4 aVariationSelector = 0; + if (nIdx < nEndRunPos) + { + sal_Int32 nNextIdx = nIdx; + sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx); + if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR)) + { + nIdx = nNextIdx; + aVariationSelector = aNextChar; + } + } + + // Characters with U and Tu vertical orientation should + // be shaped in vertical direction. But characters + // with Tr should be shaped in vertical direction + // only if they have vertical alternates, otherwise + // they should be shaped in horizontal direction + // and then rotated. + // See http://unicode.org/reports/tr50/#vo + if (aVo == U_VO_UPRIGHT || aVo == U_VO_TRANSFORMED_UPRIGHT || + (aVo == U_VO_TRANSFORMED_ROTATED && + HasVerticalAlternate(aChar, aVariationSelector))) + { + aDirection = HB_DIRECTION_TTB; + } + else + { + aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; + } + + if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection || aSubRuns.back().maScript != aScript) + aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection }); + else + aSubRuns.back().mnEnd = nIdx; + } + } + else + { + aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection }); + } + + nCurrentPos = nEndRunPos; + ++k; + } + } + + // RTL subruns should be reversed to ensure that final glyph order is + // correct. + if (bRightToLeft) + std::reverse(aSubRuns.begin(), aSubRuns.end()); + + for (const auto& aSubRun : aSubRuns) + { + hb_buffer_clear_contents(pHbBuffer); + + const int nMinRunPos = aSubRun.mnMin; + const int nEndRunPos = aSubRun.mnEnd; + const int nRunLen = nEndRunPos - nMinRunPos; + + int nHbFlags = HB_BUFFER_FLAGS_DEFAULT; + + // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below. + nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL; + + if (nMinRunPos == 0) + nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */ + if (nEndRunPos == nLength) + nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */ + + hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection); + hb_buffer_set_script(pHbBuffer, aSubRun.maScript); + if (!msLanguage.isEmpty()) + { + hb_buffer_set_language(pHbBuffer, hb_language_from_string(msLanguage.getStr(), msLanguage.getLength())); + } + else + { + OString sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US); + hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), sLanguage.getLength())); + } + hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags)); + hb_buffer_add_utf16( + pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength, + nMinRunPos, nRunLen); + + // The shapers that we want HarfBuzz to use, in the order of + // preference. + const char*const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr }; + bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers); + assert(ok); + (void) ok; + + int nRunGlyphCount = hb_buffer_get_length(pHbBuffer); + hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr); + hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr); + + for (int i = 0; i < nRunGlyphCount; ++i) { + int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint; + int32_t nCharPos = pHbGlyphInfos[i].cluster; + int32_t nCharCount = 0; + bool bInCluster = false; + bool bClusterStart = false; + + // Find the number of characters that make up this glyph. + if (!bRightToLeft) + { + // If the cluster is the same as previous glyph, then this + // already consumed, skip. + if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster) + { + nCharCount = 0; + bInCluster = true; + } + else + { + // Find the next glyph with a different cluster, or the + // end of text. + int j = i; + int32_t nNextCharPos = nCharPos; + while (nNextCharPos == nCharPos && j < nRunGlyphCount) + nNextCharPos = pHbGlyphInfos[j++].cluster; + + if (nNextCharPos == nCharPos) + nNextCharPos = nEndRunPos; + nCharCount = nNextCharPos - nCharPos; + if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) && + (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)) + bClusterStart = true; + } + } + else + { + // If the cluster is the same as previous glyph, then this + // will be consumed later, skip. + if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster) + { + nCharCount = 0; + bInCluster = true; + } + else + { + // Find the previous glyph with a different cluster, or + // the end of text. + int j = i; + int32_t nNextCharPos = nCharPos; + while (nNextCharPos == nCharPos && j >= 0) + nNextCharPos = pHbGlyphInfos[j--].cluster; + + if (nNextCharPos == nCharPos) + nNextCharPos = nEndRunPos; + nCharCount = nNextCharPos - nCharPos; + if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) && + (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)) + bClusterStart = true; + } + } + + // if needed request glyph fallback by updating LayoutArgs + if (!nGlyphIndex) + { + SetNeedFallback(rArgs, nCharPos, bRightToLeft); + if (SalLayoutFlags::ForFallback & rArgs.mnFlags) + continue; + } + + GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE; + if (bRightToLeft) + nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH; + + if (bClusterStart) + nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START; + + if (bInCluster) + nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER; + + sal_UCS4 aChar + = rArgs.mrStr.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos)), 0); + + if (u_isUWhiteSpace(aChar)) + nGlyphFlags |= GlyphItemFlags::IS_SPACING; + + if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) + nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK; + + if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL) + nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA; + + double nAdvance, nXOffset, nYOffset; + if (aSubRun.maDirection == HB_DIRECTION_TTB) + { + nGlyphFlags |= GlyphItemFlags::IS_VERTICAL; + + nAdvance = -pHbPositions[i].y_advance; + nXOffset = -pHbPositions[i].y_offset; + nYOffset = -pHbPositions[i].x_offset - nBaseOffset; + + if (GetFont().NeedOffsetCorrection(pHbPositions[i].y_offset)) + { + // We need glyph's advance, top bearing, and height to + // correct y offset. + tools::Rectangle aRect; + // Get cached bound rect value for the font, + GetFont().GetGlyphBoundRect(nGlyphIndex, aRect, true); + + nXOffset = -(aRect.Top() / nXScale + ( pHbPositions[i].y_advance + + ( aRect.GetHeight() / nXScale ) ) / 2.0 ); + } + + } + else + { + nAdvance = pHbPositions[i].x_advance; + nXOffset = pHbPositions[i].x_offset; + nYOffset = -pHbPositions[i].y_offset; + } + + nAdvance = nAdvance * nXScale; + nXOffset = nXOffset * nXScale; + nYOffset = nYOffset * nYScale; + if (!GetSubpixelPositioning()) + { + nAdvance = std::lround(nAdvance); + nXOffset = std::lround(nXOffset); + nYOffset = std::lround(nYOffset); + } + + basegfx::B2DPoint aNewPos(aCurrPos.getX() + nXOffset, aCurrPos.getY() + nYOffset); + const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags, + nAdvance, nXOffset, nYOffset); + m_GlyphItems.push_back(aGI); + + aCurrPos.adjustX(nAdvance); + } + } + } + + hb_buffer_destroy(pHbBuffer); + + // Some flags are set as a side effect of text layout, save them here. + if (rArgs.mnFlags & SalLayoutFlags::GlyphItemsOnly) + m_GlyphItems.SetFlags(rArgs.mnFlags); + + return true; +} + +void GenericSalLayout::GetCharWidths(std::vector<double>& rCharWidths, const OUString& rStr) const +{ + const int nCharCount = mnEndCharPos - mnMinCharPos; + + rCharWidths.clear(); + rCharWidths.resize(nCharCount, 0); + + css::uno::Reference<css::i18n::XBreakIterator> xBreak; + auto aLocale(maLanguageTag.getLocale()); + + for (auto const& aGlyphItem : m_GlyphItems) + { + if (aGlyphItem.charPos() >= mnEndCharPos) + continue; + + unsigned int nGraphemeCount = 0; + if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty()) + { + // We are calculating DX array for cursor positions and this is a + // ligature, find out how many grapheme clusters are in it. + if (!xBreak.is()) + xBreak = mxBreak.is() ? mxBreak : vcl::unohelper::CreateBreakIterator(); + + // Count grapheme clusters in the ligature. + sal_Int32 nDone; + sal_Int32 nPos = aGlyphItem.charPos(); + while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount()) + { + nPos = xBreak->nextCharacters(rStr, nPos, aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + nGraphemeCount++; + } + } + + if (nGraphemeCount > 1) + { + // More than one grapheme cluster, we want to distribute the glyph + // width over them. + std::vector<double> aWidths(nGraphemeCount); + + // Check if the glyph has ligature caret positions. + unsigned int nCarets = nGraphemeCount; + std::vector<hb_position_t> aCarets(nGraphemeCount); + hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(), + aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, + aGlyphItem.glyphId(), 0, &nCarets, aCarets.data()); + + // Carets are 1-less than the grapheme count (since the last + // position is defined by glyph width), if the count does not + // match, ignore it. + if (nCarets == nGraphemeCount - 1) + { + // Scale the carets and apply glyph offset to them since they + // are based on the default glyph metrics. + double fScale = 0; + GetFont().GetScale(&fScale, nullptr); + for (size_t i = 0; i < nCarets; i++) + aCarets[i] = (aCarets[i] * fScale) + aGlyphItem.xOffset(); + + // Use the glyph width for the last caret. + aCarets[nCarets] = aGlyphItem.newWidth(); + + // Carets are absolute from the X origin of the glyph, turn + // them to relative widths that we need below. + for (size_t i = 0; i < nGraphemeCount; i++) + aWidths[i] = aCarets[i] - (i == 0 ? 0 : aCarets[i - 1]); + + // Carets are in visual order, but we want widths in logical + // order. + if (aGlyphItem.IsRTLGlyph()) + std::reverse(aWidths.begin(), aWidths.end()); + } + else + { + // The glyph has no carets, distribute the width evenly. + auto nWidth = aGlyphItem.newWidth() / nGraphemeCount; + std::fill(aWidths.begin(), aWidths.end(), nWidth); + + // Add rounding difference to the last component to maintain + // ligature width. + aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount); + } + + // Set the width of each grapheme cluster. + sal_Int32 nDone; + sal_Int32 nPos = aGlyphItem.charPos(); + for (auto nWidth : aWidths) + { + rCharWidths[nPos - mnMinCharPos] += nWidth; + nPos = xBreak->nextCharacters(rStr, nPos, aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + } + } + else + rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth(); + } +} + +// - pDXArray: is the adjustments to glyph advances (usually due to +// justification). +// - pKashidaArray: is the places where kashidas are inserted (for Arabic +// justification). The number of kashidas is calculated from the pDXArray. +void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKashidaArray) +{ + int nCharCount = mnEndCharPos - mnMinCharPos; + std::vector<double> aOldCharWidths; + std::unique_ptr<double[]> const pNewCharWidths(new double[nCharCount]); + + // Get the natural character widths (i.e. before applying DX adjustments). + GetCharWidths(aOldCharWidths, {}); + + // Calculate the character widths after DX adjustments. + for (int i = 0; i < nCharCount; ++i) + { + if (i == 0) + pNewCharWidths[i] = pDXArray[i]; + else + pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1]; + } + + // Map of Kashida insertion points (in the glyph items vector) and the + // requested width. + std::map<size_t, std::pair<double, double>> pKashidas; + + // The accumulated difference in X position. + double nDelta = 0; + + // Apply the DX adjustments to glyph positions and widths. + size_t i = 0; + while (i < m_GlyphItems.size()) + { + // Accumulate the width difference for all characters corresponding to + // this glyph. + int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos; + double nDiff = 0; + for (int j = 0; j < m_GlyphItems[i].charCount(); j++) + nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j]; + + if (!m_GlyphItems[i].IsRTLGlyph()) + { + // Adjust the width and position of the first (leftmost) glyph in + // the cluster. + m_GlyphItems[i].addNewWidth(nDiff); + m_GlyphItems[i].adjustLinearPosX(nDelta); + + // Adjust the position of the rest of the glyphs in the cluster. + while (++i < m_GlyphItems.size()) + { + if (!m_GlyphItems[i].IsInCluster()) + break; + m_GlyphItems[i].adjustLinearPosX(nDelta); + } + } + else if (m_GlyphItems[i].IsInCluster()) + { + // RTL glyph in the middle of the cluster, will be handled in the + // loop below. + i++; + } + else // RTL + { + // Adjust the width and position of the first (rightmost) glyph in + // the cluster. This is RTL, so we put all the adjustment to the + // left of the glyph. + m_GlyphItems[i].addNewWidth(nDiff); + m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff); + + // Adjust the X position of the rest of the glyphs in the cluster. + // We iterate backwards since this is an RTL glyph. + for (int j = i - 1; j >= 0 && m_GlyphItems[j].IsInCluster(); j--) + m_GlyphItems[j].adjustLinearPosX(nDelta + nDiff); + + // This is a Kashida insertion position, mark it. Kashida glyphs + // will be inserted below. + if (pKashidaArray && pKashidaArray[nCharPos]) + pKashidas[i] = { nDiff, pNewCharWidths[nCharPos] }; + + i++; + } + + // Increment the delta, the loop above makes sure we do so only once + // for every character (cluster) not for every glyph (otherwise we + // would apply it multiple times for each glyph belonging to the same + // character which is wrong as DX adjustments are character based). + nDelta += nDiff; + } + + // Insert Kashida glyphs. + if (pKashidas.empty()) + return; + + // Find Kashida glyph width and index. + sal_GlyphId nKashidaIndex = GetFont().GetGlyphIndex(0x0640); + double nKashidaWidth = GetFont().GetKashidaWidth(); + if (!GetSubpixelPositioning()) + nKashidaWidth = std::ceil(nKashidaWidth); + + if (nKashidaWidth <= 0) + { + SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width"); + return; + } + + size_t nInserted = 0; + for (auto const& pKashida : pKashidas) + { + auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first; + + // The total Kashida width. + auto const& [nTotalWidth, nClusterWidth] = pKashida.second; + + // Number of times to repeat each Kashida. + int nCopies = 1; + if (nTotalWidth > nKashidaWidth) + nCopies = nTotalWidth / nKashidaWidth; + + // See if we can improve the fit by adding an extra Kashidas and + // squeezing them together a bit. + double nOverlap = 0; + double nShortfall = nTotalWidth - nKashidaWidth * nCopies; + if (nShortfall > 0) + { + ++nCopies; + double nExcess = nCopies * nKashidaWidth - nTotalWidth; + if (nExcess > 0) + nOverlap = nExcess / (nCopies - 1); + } + + basegfx::B2DPoint aPos = pGlyphIter->linearPos(); + int nCharPos = pGlyphIter->charPos(); + GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH; + // Move to the left side of the adjusted width and start inserting + // glyphs there. + aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth()); + while (nCopies--) + { + GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0); + pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida); + aPos.adjustX(nKashidaWidth - nOverlap); + ++pGlyphIter; + ++nInserted; + } + } +} + +// Kashida will be inserted between nCharPos and nNextCharPos. +bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const +{ + // Search for glyph items corresponding to nCharPos and nNextCharPos. + auto const& rGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(), + [&](const GlyphItem& g) { return g.charPos() == nCharPos; }); + auto const& rNextGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(), + [&](const GlyphItem& g) { return g.charPos() == nNextCharPos; }); + + // If either is not found then a ligature is created at this position, we + // can’t insert Kashida here. + if (rGlyph == m_GlyphItems.end() || rNextGlyph == m_GlyphItems.end()) + return false; + + // If the either character is not supported by this layout, return false so + // that fallback layouts would be checked for it. + if (rGlyph->glyphId() == 0 || rNextGlyph->glyphId() == 0) + return false; + + // Lastly check if this position is kashida-safe. + return rNextGlyph->IsSafeToInsertKashida(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/FileDefinitionWidgetDraw.cxx b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx new file mode 100644 index 0000000000..0be4c829dd --- /dev/null +++ b/vcl/source/gdi/FileDefinitionWidgetDraw.cxx @@ -0,0 +1,1130 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <sal/config.h> + +#include <string_view> + +#include <FileDefinitionWidgetDraw.hxx> +#include <widgetdraw/WidgetDefinitionReader.hxx> + +#include <svdata.hxx> +#include <rtl/bootstrap.hxx> +#include <config_folders.h> +#include <osl/file.hxx> + +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <tools/stream.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/gradient.hxx> + +#include <comphelper/seqstream.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/string.hxx> + +#include <com/sun/star/graphic/SvgTools.hpp> +#include <basegfx/DrawCommands.hxx> +#include <o3tl/string_view.hxx> + +using namespace css; + +namespace vcl +{ +namespace +{ +OUString lcl_getThemeDefinitionPath() +{ + OUString sPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/theme_definitions/"); + rtl::Bootstrap::expandMacros(sPath); + return sPath; +} + +bool lcl_directoryExists(OUString const& sDirectory) +{ + osl::DirectoryItem aDirectoryItem; + osl::FileBase::RC eRes = osl::DirectoryItem::get(sDirectory, aDirectoryItem); + return eRes == osl::FileBase::E_None; +} + +bool lcl_fileExists(OUString const& sFilename) +{ + osl::File aFile(sFilename); + osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read); + return osl::FileBase::E_None == eRC; +} + +std::shared_ptr<WidgetDefinition> getWidgetDefinition(OUString const& rDefinitionFile, + OUString const& rDefinitionResourcesPath) +{ + auto pWidgetDefinition = std::make_shared<WidgetDefinition>(); + WidgetDefinitionReader aReader(rDefinitionFile, rDefinitionResourcesPath); + if (aReader.read(*pWidgetDefinition)) + return pWidgetDefinition; + return std::shared_ptr<WidgetDefinition>(); +} + +std::shared_ptr<WidgetDefinition> const& +getWidgetDefinitionForTheme(std::u16string_view rThemenName) +{ + static std::shared_ptr<WidgetDefinition> spDefinition; + if (!spDefinition) + { + OUString sSharedDefinitionBasePath = lcl_getThemeDefinitionPath(); + OUString sThemeFolder = sSharedDefinitionBasePath + rThemenName + "/"; + OUString sThemeDefinitionFile = sThemeFolder + "definition.xml"; + if (lcl_directoryExists(sThemeFolder) && lcl_fileExists(sThemeDefinitionFile)) + spDefinition = getWidgetDefinition(sThemeDefinitionFile, sThemeFolder); + } + return spDefinition; +} + +int getSettingValueInteger(std::string_view rValue, int nDefault) +{ + if (rValue.empty()) + return nDefault; + if (!comphelper::string::isdigitAsciiString(rValue)) + return nDefault; + return o3tl::toInt32(rValue); +} + +bool getSettingValueBool(std::string_view rValue, bool bDefault) +{ + if (rValue.empty()) + return bDefault; + if (rValue == "true" || rValue == "false") + return rValue == "true"; + return bDefault; +} + +} // end anonymous namespace + +FileDefinitionWidgetDraw::FileDefinitionWidgetDraw(SalGraphics& rGraphics) + : m_rGraphics(rGraphics) + , m_bIsActive(false) +{ + m_pWidgetDefinition = getWidgetDefinitionForTheme(u"online"); +#ifdef IOS + if (!m_pWidgetDefinition) + m_pWidgetDefinition = getWidgetDefinitionForTheme(u"ios"); +#endif + + if (!m_pWidgetDefinition) + return; + + auto& pSettings = m_pWidgetDefinition->mpSettings; + + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maNWFData.mbNoFocusRects = true; + pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true; + pSVData->maNWFData.mbNoActiveTabTextRaise + = getSettingValueBool(pSettings->msNoActiveTabTextRaise, true); + pSVData->maNWFData.mbCenteredTabs = getSettingValueBool(pSettings->msCenteredTabs, true); + pSVData->maNWFData.mbProgressNeedsErase = true; + pSVData->maNWFData.mnStatusBarLowerRightOffset = 10; + pSVData->maNWFData.mbCanDrawWidgetAnySize = true; + + int nDefaultListboxEntryMargin = pSVData->maNWFData.mnListBoxEntryMargin; + pSVData->maNWFData.mnListBoxEntryMargin + = getSettingValueInteger(pSettings->msListBoxEntryMargin, nDefaultListboxEntryMargin); + + m_bIsActive = true; +} + +bool FileDefinitionWidgetDraw::isNativeControlSupported(ControlType eType, ControlPart ePart) +{ + switch (eType) + { + case ControlType::Generic: + case ControlType::Pushbutton: + case ControlType::Radiobutton: + case ControlType::Checkbox: + return true; + case ControlType::Combobox: + if (ePart == ControlPart::HasBackgroundTexture) + return false; + return true; + case ControlType::Editbox: + case ControlType::EditboxNoBorder: + case ControlType::MultilineEditbox: + return true; + case ControlType::Listbox: + if (ePart == ControlPart::HasBackgroundTexture) + return false; + return true; + case ControlType::Spinbox: + if (ePart == ControlPart::AllButtons) + return false; + return true; + case ControlType::SpinButtons: + return false; + case ControlType::TabItem: + case ControlType::TabPane: + case ControlType::TabHeader: + case ControlType::TabBody: + return true; + case ControlType::Scrollbar: + if (ePart == ControlPart::DrawBackgroundHorz + || ePart == ControlPart::DrawBackgroundVert) + return false; + return true; + case ControlType::Slider: + case ControlType::Fixedline: + case ControlType::Toolbar: + return true; + case ControlType::Menubar: + case ControlType::MenuPopup: + return true; + case ControlType::Progress: + case ControlType::LevelBar: + return true; + case ControlType::IntroProgress: + return false; + case ControlType::Tooltip: + return true; + case ControlType::WindowBackground: + case ControlType::Frame: + case ControlType::ListNode: + case ControlType::ListNet: + case ControlType::ListHeader: + return true; + } + + return false; +} + +bool FileDefinitionWidgetDraw::hitTestNativeControl( + ControlType /*eType*/, ControlPart /*ePart*/, + const tools::Rectangle& /*rBoundingControlRegion*/, const Point& /*aPos*/, bool& /*rIsInside*/) +{ + return false; +} + +void FileDefinitionWidgetDraw::drawPolyPolygon(SalGraphics& rGraphics, + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& i_rPolyPolygon, + double i_fTransparency) +{ + rGraphics.drawPolyPolygon(rObjectToDevice, i_rPolyPolygon, i_fTransparency); +} + +void FileDefinitionWidgetDraw::drawPolyLine( + SalGraphics& rGraphics, const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& i_rPolygon, double i_fTransparency, double i_fLineWidth, + const std::vector<double>* i_pStroke, basegfx::B2DLineJoin i_eLineJoin, + css::drawing::LineCap i_eLineCap, double i_fMiterMinimumAngle, bool bPixelSnapHairline) +{ + rGraphics.drawPolyLine(rObjectToDevice, i_rPolygon, i_fTransparency, i_fLineWidth, i_pStroke, + i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle, bPixelSnapHairline); +} + +void FileDefinitionWidgetDraw::drawBitmap(SalGraphics& rGraphics, const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap) +{ + rGraphics.drawBitmap(rPosAry, rSalBitmap); +} + +void FileDefinitionWidgetDraw::drawBitmap(SalGraphics& rGraphics, const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rTransparentBitmap) +{ + rGraphics.drawBitmap(rPosAry, rSalBitmap, rTransparentBitmap); +} + +void FileDefinitionWidgetDraw::implDrawGradient(SalGraphics& rGraphics, + const basegfx::B2DPolyPolygon& rPolyPolygon, + const SalGradient& rGradient) +{ + rGraphics.implDrawGradient(rPolyPolygon, rGradient); +} + +namespace +{ +void drawFromDrawCommands(gfx::DrawRoot const& rDrawRoot, SalGraphics& rGraphics, tools::Long nX, + tools::Long nY, tools::Long nWidth, tools::Long nHeight) +{ + basegfx::B2DRectangle aSVGRect = rDrawRoot.maRectangle; + + basegfx::B2DRange aTargetSurface(nX, nY, nX + nWidth + 1, nY + nHeight + 1); + + for (std::shared_ptr<gfx::DrawBase> const& pDrawBase : rDrawRoot.maChildren) + { + switch (pDrawBase->getType()) + { + case gfx::DrawCommandType::Rectangle: + { + auto const& rRectangle = static_cast<gfx::DrawRectangle const&>(*pDrawBase); + + basegfx::B2DRange aInputRectangle(rRectangle.maRectangle); + + double fDeltaX = aTargetSurface.getWidth() - aSVGRect.getWidth(); + double fDeltaY = aTargetSurface.getHeight() - aSVGRect.getHeight(); + + basegfx::B2DRange aFinalRectangle( + aInputRectangle.getMinX(), aInputRectangle.getMinY(), + aInputRectangle.getMaxX() + fDeltaX, aInputRectangle.getMaxY() + fDeltaY); + + aFinalRectangle.transform(basegfx::utils::createTranslateB2DHomMatrix( + aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5)); + + basegfx::B2DPolygon aB2DPolygon = basegfx::utils::createPolygonFromRect( + aFinalRectangle, rRectangle.mnRx / aFinalRectangle.getWidth() * 2.0, + rRectangle.mnRy / aFinalRectangle.getHeight() * 2.0); + + if (rRectangle.mpFillColor) + { + rGraphics.SetLineColor(); + rGraphics.SetFillColor(Color(*rRectangle.mpFillColor)); + FileDefinitionWidgetDraw::drawPolyPolygon(rGraphics, basegfx::B2DHomMatrix(), + basegfx::B2DPolyPolygon(aB2DPolygon), + 1.0 - rRectangle.mnOpacity); + } + else if (rRectangle.mpFillGradient) + { + rGraphics.SetLineColor(); + rGraphics.SetFillColor(); + if (rRectangle.mpFillGradient->meType == gfx::GradientType::Linear) + { + auto* pLinearGradient = static_cast<gfx::LinearGradientInfo*>( + rRectangle.mpFillGradient.get()); + SalGradient aGradient; + double x, y; + + x = pLinearGradient->x1; + y = pLinearGradient->y1; + + if (x > aSVGRect.getCenterX()) + x = x + fDeltaX; + if (y > aSVGRect.getCenterY()) + y = y + fDeltaY; + + aGradient.maPoint1 = basegfx::B2DPoint(x, y); + aGradient.maPoint1 *= basegfx::utils::createTranslateB2DHomMatrix( + aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5); + + x = pLinearGradient->x2; + y = pLinearGradient->y2; + + if (x > aSVGRect.getCenterX()) + x = x + fDeltaX; + if (y > aSVGRect.getCenterY()) + y = y + fDeltaY; + + aGradient.maPoint2 = basegfx::B2DPoint(x, y); + aGradient.maPoint2 *= basegfx::utils::createTranslateB2DHomMatrix( + aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5); + + for (gfx::GradientStop const& rStop : pLinearGradient->maGradientStops) + { + Color aColor(rStop.maColor); + aColor.SetAlpha(255 + - (rStop.mfOpacity * (1.0f - rRectangle.mnOpacity))); + aGradient.maStops.emplace_back(aColor, rStop.mfOffset); + } + FileDefinitionWidgetDraw::implDrawGradient( + rGraphics, basegfx::B2DPolyPolygon(aB2DPolygon), aGradient); + } + } + if (rRectangle.mpStrokeColor) + { + rGraphics.SetLineColor(Color(*rRectangle.mpStrokeColor)); + rGraphics.SetFillColor(); + FileDefinitionWidgetDraw::drawPolyLine( + rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 1.0 - rRectangle.mnOpacity, + rRectangle.mnStrokeWidth, + nullptr, // MM01 + basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false); + } + } + break; + case gfx::DrawCommandType::Path: + { + auto const& rPath = static_cast<gfx::DrawPath const&>(*pDrawBase); + + double fDeltaX = aTargetSurface.getWidth() - aSVGRect.getWidth(); + double fDeltaY = aTargetSurface.getHeight() - aSVGRect.getHeight(); + + basegfx::B2DPolyPolygon aPolyPolygon(rPath.maPolyPolygon); + for (auto& rPolygon : aPolyPolygon) + { + for (size_t i = 0; i < rPolygon.count(); ++i) + { + auto& rPoint = rPolygon.getB2DPoint(i); + double x = rPoint.getX(); + double y = rPoint.getY(); + + if (x > aSVGRect.getCenterX()) + x = x + fDeltaX; + if (y > aSVGRect.getCenterY()) + y = y + fDeltaY; + rPolygon.setB2DPoint(i, basegfx::B2DPoint(x, y)); + } + } + aPolyPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix( + aTargetSurface.getMinX() - 0.5, aTargetSurface.getMinY() - 0.5)); + + if (rPath.mpFillColor) + { + rGraphics.SetLineColor(); + rGraphics.SetFillColor(Color(*rPath.mpFillColor)); + FileDefinitionWidgetDraw::drawPolyPolygon(rGraphics, basegfx::B2DHomMatrix(), + aPolyPolygon, 1.0 - rPath.mnOpacity); + } + if (rPath.mpStrokeColor) + { + rGraphics.SetLineColor(Color(*rPath.mpStrokeColor)); + rGraphics.SetFillColor(); + for (auto const& rPolygon : std::as_const(aPolyPolygon)) + { + FileDefinitionWidgetDraw::drawPolyLine( + rGraphics, basegfx::B2DHomMatrix(), rPolygon, 1.0 - rPath.mnOpacity, + rPath.mnStrokeWidth, + nullptr, // MM01 + basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false); + } + } + } + break; + + default: + break; + } + } +} + +void munchDrawCommands(std::vector<std::shared_ptr<WidgetDrawAction>> const& rDrawActions, + SalGraphics& rGraphics, tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight) +{ + for (std::shared_ptr<WidgetDrawAction> const& pDrawAction : rDrawActions) + { + switch (pDrawAction->maType) + { + case WidgetDrawActionType::RECTANGLE: + { + auto const& rWidgetDraw + = static_cast<WidgetDrawActionRectangle const&>(*pDrawAction); + + basegfx::B2DRectangle rRect( + nX + (nWidth * rWidgetDraw.mfX1), nY + (nHeight * rWidgetDraw.mfY1), + nX + (nWidth * rWidgetDraw.mfX2), nY + (nHeight * rWidgetDraw.mfY2)); + + basegfx::B2DPolygon aB2DPolygon = basegfx::utils::createPolygonFromRect( + rRect, rWidgetDraw.mnRx / rRect.getWidth() * 2.0, + rWidgetDraw.mnRy / rRect.getHeight() * 2.0); + + rGraphics.SetLineColor(); + rGraphics.SetFillColor(rWidgetDraw.maFillColor); + FileDefinitionWidgetDraw::drawPolyPolygon( + rGraphics, basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aB2DPolygon), 0.0f); + rGraphics.SetLineColor(rWidgetDraw.maStrokeColor); + rGraphics.SetFillColor(); + FileDefinitionWidgetDraw::drawPolyLine( + rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f, + rWidgetDraw.mnStrokeWidth, nullptr, // MM01 + basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false); + } + break; + case WidgetDrawActionType::LINE: + { + auto const& rWidgetDraw = static_cast<WidgetDrawActionLine const&>(*pDrawAction); + Point aRectPoint(nX + 1, nY + 1); + + Size aRectSize(nWidth - 1, nHeight - 1); + + rGraphics.SetFillColor(); + rGraphics.SetLineColor(rWidgetDraw.maStrokeColor); + + basegfx::B2DPolygon aB2DPolygon{ + { aRectPoint.X() + (aRectSize.Width() * rWidgetDraw.mfX1), + aRectPoint.Y() + (aRectSize.Height() * rWidgetDraw.mfY1) }, + { aRectPoint.X() + (aRectSize.Width() * rWidgetDraw.mfX2), + aRectPoint.Y() + (aRectSize.Height() * rWidgetDraw.mfY2) }, + }; + + FileDefinitionWidgetDraw::drawPolyLine( + rGraphics, basegfx::B2DHomMatrix(), aB2DPolygon, 0.0f, + rWidgetDraw.mnStrokeWidth, nullptr, // MM01 + basegfx::B2DLineJoin::Round, css::drawing::LineCap_ROUND, 0.0f, false); + } + break; + case WidgetDrawActionType::IMAGE: + { + double nScaleFactor = 1.0; + if (comphelper::LibreOfficeKit::isActive()) + nScaleFactor = comphelper::LibreOfficeKit::getDPIScale(); + + auto const& rWidgetDraw = static_cast<WidgetDrawActionImage const&>(*pDrawAction); + auto& rCacheImages = ImplGetSVData()->maGDIData.maThemeImageCache; + OUString rCacheKey = rWidgetDraw.msSource + "@" + OUString::number(nScaleFactor); + auto aIterator = rCacheImages.find(rCacheKey); + + BitmapEx aBitmap; + if (aIterator == rCacheImages.end()) + { + SvFileStream aFileStream(rWidgetDraw.msSource, StreamMode::READ); + + vcl::bitmap::loadFromSvg(aFileStream, "", aBitmap, nScaleFactor); + if (!aBitmap.IsEmpty()) + { + rCacheImages.insert(std::make_pair(rCacheKey, aBitmap)); + } + } + else + { + aBitmap = aIterator->second; + } + + tools::Long nImageWidth = aBitmap.GetSizePixel().Width(); + tools::Long nImageHeight = aBitmap.GetSizePixel().Height(); + SalTwoRect aTR(0, 0, nImageWidth, nImageHeight, nX, nY, nImageWidth / nScaleFactor, + nImageHeight / nScaleFactor); + if (!aBitmap.IsEmpty()) + { + const std::shared_ptr<SalBitmap> pSalBitmap + = aBitmap.GetBitmap().ImplGetSalBitmap(); + if (aBitmap.IsAlpha()) + { + const std::shared_ptr<SalBitmap> pSalBitmapAlpha + = aBitmap.GetAlphaMask().GetBitmap().ImplGetSalBitmap(); + FileDefinitionWidgetDraw::drawBitmap(rGraphics, aTR, *pSalBitmap, + *pSalBitmapAlpha); + } + else + { + FileDefinitionWidgetDraw::drawBitmap(rGraphics, aTR, *pSalBitmap); + } + } + } + break; + case WidgetDrawActionType::EXTERNAL: + { + auto const& rWidgetDraw + = static_cast<WidgetDrawActionExternal const&>(*pDrawAction); + + auto& rCacheDrawCommands = ImplGetSVData()->maGDIData.maThemeDrawCommandsCache; + + auto aIterator = rCacheDrawCommands.find(rWidgetDraw.msSource); + + if (aIterator == rCacheDrawCommands.end()) + { + SvFileStream aFileStream(rWidgetDraw.msSource, StreamMode::READ); + + uno::Reference<uno::XComponentContext> xContext( + comphelper::getProcessComponentContext()); + const uno::Reference<graphic::XSvgParser> xSvgParser + = graphic::SvgTools::create(xContext); + + std::size_t nSize = aFileStream.remainingSize(); + std::vector<sal_Int8> aBuffer(nSize + 1); + aFileStream.ReadBytes(aBuffer.data(), nSize); + aBuffer[nSize] = 0; + + uno::Sequence<sal_Int8> aData(aBuffer.data(), nSize + 1); + uno::Reference<io::XInputStream> aInputStream( + new comphelper::SequenceInputStream(aData)); + + uno::Any aAny = xSvgParser->getDrawCommands(aInputStream, ""); + if (aAny.has<sal_uInt64>()) + { + auto* pDrawRoot = reinterpret_cast<gfx::DrawRoot*>(aAny.get<sal_uInt64>()); + if (pDrawRoot) + { + rCacheDrawCommands.insert( + std::make_pair(rWidgetDraw.msSource, *pDrawRoot)); + drawFromDrawCommands(*pDrawRoot, rGraphics, nX, nY, nWidth, nHeight); + } + } + } + else + { + drawFromDrawCommands(aIterator->second, rGraphics, nX, nY, nWidth, nHeight); + } + } + break; + } + } +} + +} // end anonymous namespace + +bool FileDefinitionWidgetDraw::resolveDefinition(ControlType eType, ControlPart ePart, + ControlState eState, + const ImplControlValue& rValue, tools::Long nX, + tools::Long nY, tools::Long nWidth, + tools::Long nHeight) +{ + bool bOK = false; + auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ePart); + if (pPart) + { + auto const& aStates = pPart->getStates(eType, ePart, eState, rValue); + if (!aStates.empty()) + { + // use last defined state + auto const& pState = aStates.back(); + { + munchDrawCommands(pState->mpWidgetDrawActions, m_rGraphics, nX, nY, nWidth, + nHeight); + bOK = true; + } + } + } + return bOK; +} + +bool FileDefinitionWidgetDraw::drawNativeControl(ControlType eType, ControlPart ePart, + const tools::Rectangle& rControlRegion, + ControlState eState, + const ImplControlValue& rValue, + const OUString& /*aCaptions*/, + const Color& /*rBackgroundColor*/) +{ + bool bOldAA = m_rGraphics.getAntiAlias(); + m_rGraphics.setAntiAlias(true); + + tools::Long nWidth = rControlRegion.GetWidth() - 1; + tools::Long nHeight = rControlRegion.GetHeight() - 1; + tools::Long nX = rControlRegion.Left(); + tools::Long nY = rControlRegion.Top(); + + bool bOK = false; + + switch (eType) + { + case ControlType::Pushbutton: + { + /*bool bIsAction = false; + const PushButtonValue* pPushButtonValue = static_cast<const PushButtonValue*>(&rValue); + if (pPushButtonValue) + bIsAction = pPushButtonValue->mbIsAction;*/ + + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Radiobutton: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Checkbox: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Combobox: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Editbox: + case ControlType::EditboxNoBorder: + case ControlType::MultilineEditbox: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Listbox: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Spinbox: + { + if (rValue.getType() == ControlType::SpinButtons) + { + const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&rValue); + + { + ControlPart eUpButtonPart = pSpinVal->mnUpperPart; + ControlState eUpButtonState = pSpinVal->mnUpperState; + + tools::Long nUpperX = pSpinVal->maUpperRect.Left(); + tools::Long nUpperY = pSpinVal->maUpperRect.Top(); + tools::Long nUpperWidth = pSpinVal->maUpperRect.GetWidth() - 1; + tools::Long nUpperHeight = pSpinVal->maUpperRect.GetHeight() - 1; + + bOK = resolveDefinition(eType, eUpButtonPart, eUpButtonState, + ImplControlValue(), nUpperX, nUpperY, nUpperWidth, + nUpperHeight); + } + + if (bOK) + { + ControlPart eDownButtonPart = pSpinVal->mnLowerPart; + ControlState eDownButtonState = pSpinVal->mnLowerState; + + tools::Long nLowerX = pSpinVal->maLowerRect.Left(); + tools::Long nLowerY = pSpinVal->maLowerRect.Top(); + tools::Long nLowerWidth = pSpinVal->maLowerRect.GetWidth() - 1; + tools::Long nLowerHeight = pSpinVal->maLowerRect.GetHeight() - 1; + + bOK = resolveDefinition(eType, eDownButtonPart, eDownButtonState, + ImplControlValue(), nLowerX, nLowerY, nLowerWidth, + nLowerHeight); + } + } + else + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + } + break; + case ControlType::SpinButtons: + break; + case ControlType::TabItem: + case ControlType::TabHeader: + case ControlType::TabPane: + case ControlType::TabBody: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Scrollbar: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Slider: + { + const SliderValue* pSliderValue = static_cast<const SliderValue*>(&rValue); + tools::Long nThumbX = pSliderValue->maThumbRect.Left(); + tools::Long nThumbY = pSliderValue->maThumbRect.Top(); + tools::Long nThumbWidth = pSliderValue->maThumbRect.GetWidth() - 1; + tools::Long nThumbHeight = pSliderValue->maThumbRect.GetHeight() - 1; + + if (ePart == ControlPart::TrackHorzArea) + { + tools::Long nCenterX = nThumbX + nThumbWidth / 2; + + bOK = resolveDefinition(eType, ControlPart::TrackHorzLeft, eState, rValue, nX, nY, + nCenterX - nX, nHeight); + if (bOK) + bOK = resolveDefinition(eType, ControlPart::TrackHorzRight, eState, rValue, + nCenterX, nY, nX + nWidth - nCenterX, nHeight); + } + else if (ePart == ControlPart::TrackVertArea) + { + tools::Long nCenterY = nThumbY + nThumbHeight / 2; + + bOK = resolveDefinition(eType, ControlPart::TrackVertUpper, eState, rValue, nX, nY, + nWidth, nCenterY - nY); + if (bOK) + bOK = resolveDefinition(eType, ControlPart::TrackVertLower, eState, rValue, nY, + nCenterY, nWidth, nY + nHeight - nCenterY); + } + + if (bOK) + { + bOK = resolveDefinition(eType, ControlPart::Button, + eState | pSliderValue->mnThumbState, rValue, nThumbX, + nThumbY, nThumbWidth, nThumbHeight); + } + } + break; + case ControlType::Fixedline: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Toolbar: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Menubar: + case ControlType::MenuPopup: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::Progress: + case ControlType::LevelBar: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::IntroProgress: + break; + case ControlType::Tooltip: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::WindowBackground: + case ControlType::Frame: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + case ControlType::ListNode: + case ControlType::ListNet: + case ControlType::ListHeader: + { + bOK = resolveDefinition(eType, ePart, eState, rValue, nX, nY, nWidth, nHeight); + } + break; + default: + break; + } + + m_rGraphics.setAntiAlias(bOldAA); + + return bOK; +} + +bool FileDefinitionWidgetDraw::getNativeControlRegion( + ControlType eType, ControlPart ePart, const tools::Rectangle& rBoundingControlRegion, + ControlState /*eState*/, const ImplControlValue& /*aValue*/, const OUString& /*aCaption*/, + tools::Rectangle& rNativeBoundingRegion, tools::Rectangle& rNativeContentRegion) +{ + Point aLocation(rBoundingControlRegion.TopLeft()); + + switch (eType) + { + case ControlType::Spinbox: + { + auto const& pButtonUpPart + = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonUp); + if (!pButtonUpPart) + return false; + Size aButtonSizeUp(pButtonUpPart->mnWidth, pButtonUpPart->mnHeight); + + auto const& pButtonDownPart + = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonDown); + if (!pButtonDownPart) + return false; + Size aButtonSizeDown(pButtonDownPart->mnWidth, pButtonDownPart->mnHeight); + + auto const& pEntirePart + = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire); + + OString sOrientation = pEntirePart->msOrientation; + + if (sOrientation.isEmpty() || sOrientation == "stacked") + { + return false; + } + else if (sOrientation == "decrease-edit-increase") + { + if (ePart == ControlPart::ButtonUp) + { + Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth() + - aButtonSizeUp.Width(), + aLocation.Y()); + rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeUp); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else if (ePart == ControlPart::ButtonDown) + { + rNativeContentRegion = tools::Rectangle(aLocation, aButtonSizeDown); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else if (ePart == ControlPart::SubEdit) + { + Point aPoint(aLocation.X() + aButtonSizeDown.Width(), aLocation.Y()); + Size aSize(rBoundingControlRegion.GetWidth() + - (aButtonSizeDown.Width() + aButtonSizeUp.Width()), + std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height())); + rNativeContentRegion = tools::Rectangle(aPoint, aSize); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else if (ePart == ControlPart::Entire) + { + Size aSize(rBoundingControlRegion.GetWidth(), + std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height())); + rNativeContentRegion = tools::Rectangle(aLocation, aSize); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + } + else if (sOrientation == "edit-decrease-increase") + { + if (ePart == ControlPart::ButtonUp) + { + Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth() + - aButtonSizeUp.Width(), + aLocation.Y()); + rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeUp); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else if (ePart == ControlPart::ButtonDown) + { + Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth() + - (aButtonSizeDown.Width() + aButtonSizeUp.Width()), + aLocation.Y()); + rNativeContentRegion = tools::Rectangle(aPoint, aButtonSizeDown); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else if (ePart == ControlPart::SubEdit) + { + Size aSize(rBoundingControlRegion.GetWidth() + - (aButtonSizeDown.Width() + aButtonSizeUp.Width()), + std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height())); + rNativeContentRegion = tools::Rectangle(aLocation, aSize); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else if (ePart == ControlPart::Entire) + { + Size aSize(rBoundingControlRegion.GetWidth(), + std::max(aButtonSizeUp.Height(), aButtonSizeDown.Height())); + rNativeContentRegion = tools::Rectangle(aLocation, aSize); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + } + } + break; + case ControlType::Checkbox: + { + auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire); + if (!pPart) + return false; + + Size aSize(pPart->mnWidth, pPart->mnHeight); + rNativeContentRegion = tools::Rectangle(Point(), aSize); + return true; + } + case ControlType::Radiobutton: + { + auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire); + if (!pPart) + return false; + + Size aSize(pPart->mnWidth, pPart->mnHeight); + rNativeContentRegion = tools::Rectangle(Point(), aSize); + return true; + } + case ControlType::TabItem: + { + auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire); + if (!pPart) + return false; + + tools::Long nWidth = std::max(rBoundingControlRegion.GetWidth() + pPart->mnMarginWidth, + tools::Long(pPart->mnWidth)); + tools::Long nHeight + = std::max(rBoundingControlRegion.GetHeight() + pPart->mnMarginHeight, + tools::Long(pPart->mnHeight)); + + rNativeBoundingRegion = tools::Rectangle(aLocation, Size(nWidth, nHeight)); + rNativeContentRegion = rNativeBoundingRegion; + return true; + } + case ControlType::Editbox: + case ControlType::EditboxNoBorder: + case ControlType::MultilineEditbox: + { + sal_Int32 nHeight = rBoundingControlRegion.GetHeight(); + + auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::Entire); + if (pPart) + nHeight = std::max(nHeight, pPart->mnHeight); + + Size aSize(rBoundingControlRegion.GetWidth(), nHeight); + rNativeContentRegion = tools::Rectangle(aLocation, aSize); + rNativeBoundingRegion = rNativeContentRegion; + rNativeBoundingRegion.expand(2); + return true; + } + break; + case ControlType::Scrollbar: + { + if (ePart == ControlPart::ButtonUp || ePart == ControlPart::ButtonDown + || ePart == ControlPart::ButtonLeft || ePart == ControlPart::ButtonRight) + { + rNativeContentRegion = tools::Rectangle(aLocation, Size(0, 0)); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else + { + rNativeBoundingRegion = rBoundingControlRegion; + rNativeContentRegion = rNativeBoundingRegion; + return true; + } + } + break; + case ControlType::Combobox: + case ControlType::Listbox: + { + auto const& pPart = m_pWidgetDefinition->getDefinition(eType, ControlPart::ButtonDown); + Size aComboButtonSize(pPart->mnWidth, pPart->mnHeight); + + if (ePart == ControlPart::ButtonDown) + { + Point aPoint(aLocation.X() + rBoundingControlRegion.GetWidth() + - aComboButtonSize.Width() - 1, + aLocation.Y()); + rNativeContentRegion = tools::Rectangle(aPoint, aComboButtonSize); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else if (ePart == ControlPart::SubEdit) + { + Size aSize(rBoundingControlRegion.GetWidth() - aComboButtonSize.Width(), + aComboButtonSize.Height()); + rNativeContentRegion = tools::Rectangle(aLocation + Point(1, 1), aSize); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + else if (ePart == ControlPart::Entire) + { + Size aSize(rBoundingControlRegion.GetWidth(), aComboButtonSize.Height()); + rNativeContentRegion = tools::Rectangle(aLocation, aSize); + rNativeBoundingRegion = rNativeContentRegion; + rNativeBoundingRegion.expand(2); + return true; + } + } + break; + case ControlType::Slider: + if (ePart == ControlPart::ThumbHorz || ePart == ControlPart::ThumbVert) + { + rNativeContentRegion = tools::Rectangle(aLocation, Size(28, 28)); + rNativeBoundingRegion = rNativeContentRegion; + return true; + } + break; + default: + break; + } + + return false; +} + +bool FileDefinitionWidgetDraw::updateSettings(AllSettings& rSettings) +{ + StyleSettings aStyleSet = rSettings.GetStyleSettings(); + + auto& pDefinitionStyle = m_pWidgetDefinition->mpStyle; + + aStyleSet.SetFaceColor(pDefinitionStyle->maFaceColor); + aStyleSet.SetCheckedColor(pDefinitionStyle->maCheckedColor); + aStyleSet.SetLightColor(pDefinitionStyle->maLightColor); + aStyleSet.SetLightBorderColor(pDefinitionStyle->maLightBorderColor); + aStyleSet.SetShadowColor(pDefinitionStyle->maShadowColor); + aStyleSet.SetDarkShadowColor(pDefinitionStyle->maDarkShadowColor); + aStyleSet.SetDefaultButtonTextColor(pDefinitionStyle->maDefaultButtonTextColor); + aStyleSet.SetButtonTextColor(pDefinitionStyle->maButtonTextColor); + aStyleSet.SetDefaultActionButtonTextColor(pDefinitionStyle->maDefaultActionButtonTextColor); + aStyleSet.SetActionButtonTextColor(pDefinitionStyle->maActionButtonTextColor); + aStyleSet.SetFlatButtonTextColor(pDefinitionStyle->maFlatButtonTextColor); + aStyleSet.SetDefaultButtonRolloverTextColor(pDefinitionStyle->maDefaultButtonRolloverTextColor); + aStyleSet.SetButtonRolloverTextColor(pDefinitionStyle->maButtonRolloverTextColor); + aStyleSet.SetDefaultActionButtonRolloverTextColor( + pDefinitionStyle->maDefaultActionButtonRolloverTextColor); + aStyleSet.SetActionButtonRolloverTextColor(pDefinitionStyle->maActionButtonRolloverTextColor); + aStyleSet.SetFlatButtonRolloverTextColor(pDefinitionStyle->maFlatButtonRolloverTextColor); + aStyleSet.SetDefaultButtonPressedRolloverTextColor( + pDefinitionStyle->maDefaultButtonPressedRolloverTextColor); + aStyleSet.SetButtonPressedRolloverTextColor(pDefinitionStyle->maButtonPressedRolloverTextColor); + aStyleSet.SetDefaultActionButtonPressedRolloverTextColor( + pDefinitionStyle->maDefaultActionButtonPressedRolloverTextColor); + aStyleSet.SetActionButtonPressedRolloverTextColor( + pDefinitionStyle->maActionButtonPressedRolloverTextColor); + aStyleSet.SetFlatButtonPressedRolloverTextColor( + pDefinitionStyle->maFlatButtonPressedRolloverTextColor); + aStyleSet.SetRadioCheckTextColor(pDefinitionStyle->maRadioCheckTextColor); + aStyleSet.SetGroupTextColor(pDefinitionStyle->maGroupTextColor); + aStyleSet.SetLabelTextColor(pDefinitionStyle->maLabelTextColor); + aStyleSet.SetWindowColor(pDefinitionStyle->maWindowColor); + aStyleSet.SetWindowTextColor(pDefinitionStyle->maWindowTextColor); + aStyleSet.SetDialogColor(pDefinitionStyle->maDialogColor); + aStyleSet.SetDialogTextColor(pDefinitionStyle->maDialogTextColor); + aStyleSet.SetWorkspaceColor(pDefinitionStyle->maWorkspaceColor); + aStyleSet.SetMonoColor(pDefinitionStyle->maMonoColor); + aStyleSet.SetFieldColor(pDefinitionStyle->maFieldColor); + aStyleSet.SetFieldTextColor(pDefinitionStyle->maFieldTextColor); + aStyleSet.SetFieldRolloverTextColor(pDefinitionStyle->maFieldRolloverTextColor); + aStyleSet.SetActiveColor(pDefinitionStyle->maActiveColor); + aStyleSet.SetActiveTextColor(pDefinitionStyle->maActiveTextColor); + aStyleSet.SetActiveBorderColor(pDefinitionStyle->maActiveBorderColor); + aStyleSet.SetDeactiveColor(pDefinitionStyle->maDeactiveColor); + aStyleSet.SetDeactiveTextColor(pDefinitionStyle->maDeactiveTextColor); + aStyleSet.SetDeactiveBorderColor(pDefinitionStyle->maDeactiveBorderColor); + aStyleSet.SetMenuColor(pDefinitionStyle->maMenuColor); + aStyleSet.SetMenuBarColor(pDefinitionStyle->maMenuBarColor); + aStyleSet.SetMenuBarRolloverColor(pDefinitionStyle->maMenuBarRolloverColor); + aStyleSet.SetMenuBorderColor(pDefinitionStyle->maMenuBorderColor); + aStyleSet.SetMenuTextColor(pDefinitionStyle->maMenuTextColor); + aStyleSet.SetMenuBarTextColor(pDefinitionStyle->maMenuBarTextColor); + aStyleSet.SetMenuBarRolloverTextColor(pDefinitionStyle->maMenuBarRolloverTextColor); + aStyleSet.SetMenuBarHighlightTextColor(pDefinitionStyle->maMenuBarHighlightTextColor); + aStyleSet.SetMenuHighlightColor(pDefinitionStyle->maMenuHighlightColor); + aStyleSet.SetMenuHighlightTextColor(pDefinitionStyle->maMenuHighlightTextColor); + aStyleSet.SetHighlightColor(pDefinitionStyle->maHighlightColor); + aStyleSet.SetHighlightTextColor(pDefinitionStyle->maHighlightTextColor); + aStyleSet.SetActiveTabColor(pDefinitionStyle->maActiveTabColor); + aStyleSet.SetInactiveTabColor(pDefinitionStyle->maInactiveTabColor); + aStyleSet.SetTabTextColor(pDefinitionStyle->maTabTextColor); + aStyleSet.SetTabRolloverTextColor(pDefinitionStyle->maTabRolloverTextColor); + aStyleSet.SetTabHighlightTextColor(pDefinitionStyle->maTabHighlightTextColor); + aStyleSet.SetDisableColor(pDefinitionStyle->maDisableColor); + aStyleSet.SetHelpColor(pDefinitionStyle->maHelpColor); + aStyleSet.SetHelpTextColor(pDefinitionStyle->maHelpTextColor); + aStyleSet.SetLinkColor(pDefinitionStyle->maLinkColor); + aStyleSet.SetVisitedLinkColor(pDefinitionStyle->maVisitedLinkColor); + aStyleSet.SetToolTextColor(pDefinitionStyle->maToolTextColor); + + auto& pSettings = m_pWidgetDefinition->mpSettings; + + int nFontSize = getSettingValueInteger(pSettings->msDefaultFontSize, 10); + vcl::Font aFont(FAMILY_SWISS, Size(0, nFontSize)); + aFont.SetCharSet(osl_getThreadTextEncoding()); + aFont.SetWeight(WEIGHT_NORMAL); + aFont.SetFamilyName("Liberation Sans"); + aStyleSet.SetAppFont(aFont); + aStyleSet.SetHelpFont(aFont); + aStyleSet.SetMenuFont(aFont); + aStyleSet.SetToolFont(aFont); + aStyleSet.SetGroupFont(aFont); + aStyleSet.SetLabelFont(aFont); + aStyleSet.SetRadioCheckFont(aFont); + aStyleSet.SetPushButtonFont(aFont); + aStyleSet.SetFieldFont(aFont); + aStyleSet.SetIconFont(aFont); + aStyleSet.SetTabFont(aFont); + + aFont.SetWeight(WEIGHT_BOLD); + aStyleSet.SetFloatTitleFont(aFont); + aStyleSet.SetTitleFont(aFont); + + int nTitleHeight = getSettingValueInteger(pSettings->msTitleHeight, aStyleSet.GetTitleHeight()); + aStyleSet.SetTitleHeight(nTitleHeight); + + int nFloatTitleHeight + = getSettingValueInteger(pSettings->msFloatTitleHeight, aStyleSet.GetFloatTitleHeight()); + aStyleSet.SetFloatTitleHeight(nFloatTitleHeight); + + int nLogicWidth = getSettingValueInteger(pSettings->msListBoxPreviewDefaultLogicWidth, + 15); // See vcl/source/app/settings.cxx + int nLogicHeight = getSettingValueInteger(pSettings->msListBoxPreviewDefaultLogicHeight, 7); + aStyleSet.SetListBoxPreviewDefaultLogicSize(Size(nLogicWidth, nLogicHeight)); + + rSettings.SetStyleSettings(aStyleSet); + + return true; +} + +} // end vcl namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/TypeSerializer.cxx b/vcl/source/gdi/TypeSerializer.cxx new file mode 100644 index 0000000000..49890fc38e --- /dev/null +++ b/vcl/source/gdi/TypeSerializer.cxx @@ -0,0 +1,483 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/TypeSerializer.hxx> +#include <tools/vcompat.hxx> +#include <tools/fract.hxx> +#include <sal/log.hxx> +#include <comphelper/fileformat.h> +#include <vcl/filter/SvmReader.hxx> +#include <vcl/filter/SvmWriter.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/dibtools.hxx> + +TypeSerializer::TypeSerializer(SvStream& rStream) + : GenericTypeSerializer(rStream) +{ +} + +void TypeSerializer::readGradient(Gradient& rGradient) +{ + VersionCompatRead aCompat(mrStream); + + sal_uInt16 nStyle = 0; + Color aStartColor; + Color aEndColor; + sal_uInt16 nAngle = 0; + sal_uInt16 nBorder = 0; + sal_uInt16 nOffsetX = 0; + sal_uInt16 nOffsetY = 0; + sal_uInt16 nIntensityStart = 0; + sal_uInt16 nIntensityEnd = 0; + sal_uInt16 nStepCount = 0; + + mrStream.ReadUInt16(nStyle); + readColor(aStartColor); + readColor(aEndColor); + mrStream.ReadUInt16(nAngle); + mrStream.ReadUInt16(nBorder); + mrStream.ReadUInt16(nOffsetX); + mrStream.ReadUInt16(nOffsetY); + mrStream.ReadUInt16(nIntensityStart); + mrStream.ReadUInt16(nIntensityEnd); + mrStream.ReadUInt16(nStepCount); + + rGradient.SetStyle(static_cast<css::awt::GradientStyle>(nStyle)); + rGradient.SetStartColor(aStartColor); + rGradient.SetEndColor(aEndColor); + if (nAngle > 3600) + { + SAL_WARN("vcl", "angle out of range " << nAngle); + nAngle = 0; + } + rGradient.SetAngle(Degree10(nAngle)); + rGradient.SetBorder(nBorder); + rGradient.SetOfsX(nOffsetX); + rGradient.SetOfsY(nOffsetY); + rGradient.SetStartIntensity(nIntensityStart); + rGradient.SetEndIntensity(nIntensityEnd); + rGradient.SetSteps(nStepCount); +} + +void TypeSerializer::writeGradient(const Gradient& rGradient) +{ + VersionCompatWrite aCompat(mrStream, 1); + + mrStream.WriteUInt16(static_cast<sal_uInt16>(rGradient.GetStyle())); + writeColor(rGradient.GetStartColor()); + writeColor(rGradient.GetEndColor()); + mrStream.WriteUInt16(rGradient.GetAngle().get()); + mrStream.WriteUInt16(rGradient.GetBorder()); + mrStream.WriteUInt16(rGradient.GetOfsX()); + mrStream.WriteUInt16(rGradient.GetOfsY()); + mrStream.WriteUInt16(rGradient.GetStartIntensity()); + mrStream.WriteUInt16(rGradient.GetEndIntensity()); + mrStream.WriteUInt16(rGradient.GetSteps()); +} + +void TypeSerializer::readGfxLink(GfxLink& rGfxLink) +{ + sal_uInt16 nType = 0; + sal_uInt32 nDataSize = 0; + sal_uInt32 nUserId = 0; + + Size aSize; + MapMode aMapMode; + bool bMapAndSizeValid = false; + + { + VersionCompatRead aCompat(mrStream); + + // Version 1 + mrStream.ReadUInt16(nType); + mrStream.ReadUInt32(nDataSize); + mrStream.ReadUInt32(nUserId); + + if (aCompat.GetVersion() >= 2) + { + readSize(aSize); + readMapMode(aMapMode); + bMapAndSizeValid = true; + } + } + + auto nRemainingData = mrStream.remainingSize(); + if (nDataSize > nRemainingData) + { + SAL_WARN("vcl", "graphic link stream is smaller than requested size"); + nDataSize = nRemainingData; + } + + rGfxLink = GfxLink(BinaryDataContainer(mrStream, nDataSize), static_cast<GfxLinkType>(nType)); + rGfxLink.SetUserId(nUserId); + + if (bMapAndSizeValid) + { + rGfxLink.SetPrefSize(aSize); + rGfxLink.SetPrefMapMode(aMapMode); + } +} + +void TypeSerializer::writeGfxLink(const GfxLink& rGfxLink) +{ + { + VersionCompatWrite aCompat(mrStream, 2); + + // Version 1 + mrStream.WriteUInt16(sal_uInt16(rGfxLink.GetType())); + mrStream.WriteUInt32(rGfxLink.GetDataSize()); + mrStream.WriteUInt32(rGfxLink.GetUserId()); + + // Version 2 + writeSize(rGfxLink.GetPrefSize()); + writeMapMode(rGfxLink.GetPrefMapMode()); + } + + if (rGfxLink.GetDataSize()) + { + if (rGfxLink.GetData()) + mrStream.WriteBytes(rGfxLink.GetData(), rGfxLink.GetDataSize()); + } +} + +namespace +{ +#define NATIVE_FORMAT_50 COMPAT_FORMAT('N', 'A', 'T', '5') + +} // end anonymous namespace + +void TypeSerializer::readGraphic(Graphic& rGraphic) +{ + if (mrStream.GetError()) + return; + + const sal_uInt64 nInitialStreamPosition = mrStream.Tell(); + sal_uInt32 nType; + + // if there is no more data, avoid further expensive + // reading which will create VDevs and other stuff, just to + // read nothing. + if (mrStream.remainingSize() < 4) + return; + + // read Id + mrStream.ReadUInt32(nType); + + if (NATIVE_FORMAT_50 == nType) + { + Graphic aGraphic; + GfxLink aLink; + + // read compat info, destructor writes stuff into the header + { + VersionCompatRead aCompat(mrStream); + } + + readGfxLink(aLink); + + if (!mrStream.GetError() && aLink.LoadNative(aGraphic)) + { + if (aLink.IsPrefMapModeValid()) + aGraphic.SetPrefMapMode(aLink.GetPrefMapMode()); + + if (aLink.IsPrefSizeValid()) + aGraphic.SetPrefSize(aLink.GetPrefSize()); + } + else + { + mrStream.Seek(nInitialStreamPosition); + mrStream.SetError(ERRCODE_IO_WRONGFORMAT); + } + rGraphic = aGraphic; + } + else + { + BitmapEx aBitmapEx; + const SvStreamEndian nOldFormat = mrStream.GetEndian(); + + mrStream.SeekRel(-4); + mrStream.SetEndian(SvStreamEndian::LITTLE); + ReadDIBBitmapEx(aBitmapEx, mrStream); + + if (!mrStream.GetError()) + { + sal_uInt32 nMagic1 = 0; + sal_uInt32 nMagic2 = 0; + if (mrStream.remainingSize() >= 8) + { + sal_uInt64 nBeginPosition = mrStream.Tell(); + mrStream.ReadUInt32(nMagic1); + mrStream.ReadUInt32(nMagic2); + mrStream.Seek(nBeginPosition); + } + if (!mrStream.GetError()) + { + if (nMagic1 == 0x5344414e && nMagic2 == 0x494d4931) + { + Animation aAnimation; + ReadAnimation(mrStream, aAnimation); + + // #108077# manually set loaded BmpEx to Animation + // (which skips loading its BmpEx if already done) + aAnimation.SetBitmapEx(aBitmapEx); + rGraphic = Graphic(aAnimation); + } + else + { + rGraphic = Graphic(aBitmapEx); + } + } + else + { + mrStream.ResetError(); + } + } + else + { + GDIMetaFile aMetaFile; + + mrStream.Seek(nInitialStreamPosition); + mrStream.ResetError(); + SvmReader aReader(mrStream); + aReader.Read(aMetaFile); + + if (!mrStream.GetError()) + { + rGraphic = Graphic(aMetaFile); + } + else + { + ErrCode nOriginalError = mrStream.GetErrorCode(); + // try to stream in Svg defining data (length, byte array and evtl. path) + // See below (operator<<) for more information + sal_uInt32 nMagic; + mrStream.Seek(nInitialStreamPosition); + mrStream.ResetError(); + mrStream.ReadUInt32(nMagic); + + if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic + || constPdfMagic == nMagic) + { + sal_uInt32 nLength = 0; + mrStream.ReadUInt32(nLength); + + if (nLength) + { + BinaryDataContainer aDataContainer(mrStream, nLength); + + if (!mrStream.GetError()) + { + VectorGraphicDataType aDataType(VectorGraphicDataType::Svg); + + switch (nMagic) + { + case constWmfMagic: + aDataType = VectorGraphicDataType::Wmf; + break; + case constEmfMagic: + aDataType = VectorGraphicDataType::Emf; + break; + case constPdfMagic: + aDataType = VectorGraphicDataType::Pdf; + break; + } + + auto aVectorGraphicDataPtr + = std::make_shared<VectorGraphicData>(aDataContainer, aDataType); + rGraphic = Graphic(aVectorGraphicDataPtr); + } + } + } + else + { + mrStream.SetError(nOriginalError); + } + + mrStream.Seek(nInitialStreamPosition); + } + } + mrStream.SetEndian(nOldFormat); + } +} + +void TypeSerializer::writeGraphic(const Graphic& rGraphic) +{ + Graphic aGraphic(rGraphic); + + if (!aGraphic.makeAvailable()) + return; + + auto pGfxLink = aGraphic.GetSharedGfxLink(); + + if (mrStream.GetVersion() >= SOFFICE_FILEFORMAT_50 + && (mrStream.GetCompressMode() & SvStreamCompressFlags::NATIVE) && pGfxLink + && pGfxLink->IsNative()) + { + // native format + mrStream.WriteUInt32(NATIVE_FORMAT_50); + + // write compat info, destructor writes stuff into the header + { + VersionCompatWrite aCompat(mrStream, 1); + } + pGfxLink->SetPrefMapMode(aGraphic.GetPrefMapMode()); + pGfxLink->SetPrefSize(aGraphic.GetPrefSize()); + writeGfxLink(*pGfxLink); + } + else + { + // own format + const SvStreamEndian nOldFormat = mrStream.GetEndian(); + mrStream.SetEndian(SvStreamEndian::LITTLE); + + switch (aGraphic.GetType()) + { + case GraphicType::NONE: + case GraphicType::Default: + break; + + case GraphicType::Bitmap: + { + auto pVectorGraphicData = aGraphic.getVectorGraphicData(); + if (pVectorGraphicData) + { + // stream out Vector Graphic defining data (length, byte array and evtl. path) + // this is used e.g. in swapping out graphic data and in transporting it over UNO API + // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be + // no problem to extend it; only used at runtime + switch (pVectorGraphicData->getType()) + { + case VectorGraphicDataType::Wmf: + { + mrStream.WriteUInt32(constWmfMagic); + break; + } + case VectorGraphicDataType::Emf: + { + mrStream.WriteUInt32(constEmfMagic); + break; + } + case VectorGraphicDataType::Svg: + { + mrStream.WriteUInt32(constSvgMagic); + break; + } + case VectorGraphicDataType::Pdf: + { + mrStream.WriteUInt32(constPdfMagic); + break; + } + } + + sal_uInt32 nSize = pVectorGraphicData->getBinaryDataContainer().getSize(); + mrStream.WriteUInt32(nSize); + pVectorGraphicData->getBinaryDataContainer().writeToStream(mrStream); + + // For backwards compatibility, used to serialize path + mrStream.WriteUniOrByteString(u"", mrStream.GetStreamCharSet()); + } + else if (aGraphic.IsAnimated()) + { + WriteAnimation(mrStream, aGraphic.GetAnimation()); + } + else + { + WriteDIBBitmapEx(aGraphic.GetBitmapEx(), mrStream); + } + } + break; + + default: + { + if (aGraphic.IsSupportedGraphic()) + { + if (!mrStream.GetError()) + { + SvmWriter aWriter(mrStream); + aWriter.Write(rGraphic.GetGDIMetaFile()); + } + } + } + break; + } + mrStream.SetEndian(nOldFormat); + } +} + +bool TooLargeScaleForMapMode(const Fraction& rScale, int nDPI) +{ + // ImplLogicToPixel will multiply its values by this numerator * dpi and then double that + // result before dividing + if (rScale.GetNumerator() > std::numeric_limits<sal_Int32>::max() / nDPI / 2) + return true; + if (rScale.GetNumerator() < std::numeric_limits<sal_Int32>::min() / nDPI / 2) + return true; + return false; +} + +static bool UselessScaleForMapMode(const Fraction& rScale) +{ + if (!rScale.IsValid()) + return true; + // ofz#62439 negative numbers are multiplied by -1, MIN_INT32 * -1 + // cannot be expressed as an int + if (rScale.GetNumerator() == std::numeric_limits<sal_Int32>::min()) + return true; + if (static_cast<double>(rScale) < 0.0) + return true; + return false; +} + +void TypeSerializer::readMapMode(MapMode& rMapMode) +{ + VersionCompatRead aCompat(mrStream); + sal_uInt16 nTmp16(0); + Point aOrigin; + Fraction aScaleX; + Fraction aScaleY; + bool bSimple(true); + + mrStream.ReadUInt16(nTmp16); + MapUnit eUnit = static_cast<MapUnit>(nTmp16); + readPoint(aOrigin); + readFraction(aScaleX); + readFraction(aScaleY); + mrStream.ReadCharAsBool(bSimple); + + const bool bBogus = UselessScaleForMapMode(aScaleX) || UselessScaleForMapMode(aScaleY); + SAL_WARN_IF(bBogus, "vcl", "invalid scale"); + + if (bSimple || bBogus) + rMapMode = MapMode(eUnit); + else + rMapMode = MapMode(eUnit, aOrigin, aScaleX, aScaleY); +} + +void TypeSerializer::writeMapMode(MapMode const& rMapMode) +{ + VersionCompatWrite aCompat(mrStream, 1); + + mrStream.WriteUInt16(sal_uInt16(rMapMode.GetMapUnit())); + writePoint(rMapMode.GetOrigin()); + writeFraction(rMapMode.GetScaleX()); + writeFraction(rMapMode.GetScaleY()); + mrStream.WriteBool(rMapMode.IsSimple()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/WidgetDefinition.cxx b/vcl/source/gdi/WidgetDefinition.cxx new file mode 100644 index 0000000000..1c8fb1321e --- /dev/null +++ b/vcl/source/gdi/WidgetDefinition.cxx @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <utility> +#include <widgetdraw/WidgetDefinition.hxx> + +#include <sal/config.h> +#include <unordered_map> + +namespace vcl +{ +std::shared_ptr<WidgetDefinitionPart> WidgetDefinition::getDefinition(ControlType eType, + ControlPart ePart) +{ + auto aIterator = maDefinitions.find(ControlTypeAndPart(eType, ePart)); + + if (aIterator != maDefinitions.end()) + return aIterator->second; + return std::shared_ptr<WidgetDefinitionPart>(); +} + +std::vector<std::shared_ptr<WidgetDefinitionState>> +WidgetDefinitionPart::getStates(ControlType eType, ControlPart ePart, ControlState eState, + ImplControlValue const& rValue) +{ + std::vector<std::shared_ptr<WidgetDefinitionState>> aStatesToAdd; + + for (const auto& state : maStates) + { + bool bAdd = true; + + if (state->msEnabled != "any" + && !((state->msEnabled == "true" && eState & ControlState::ENABLED) + || (state->msEnabled == "false" && !(eState & ControlState::ENABLED)))) + bAdd = false; + if (state->msFocused != "any" + && !((state->msFocused == "true" && eState & ControlState::FOCUSED) + || (state->msFocused == "false" && !(eState & ControlState::FOCUSED)))) + bAdd = false; + if (state->msPressed != "any" + && !((state->msPressed == "true" && eState & ControlState::PRESSED) + || (state->msPressed == "false" && !(eState & ControlState::PRESSED)))) + bAdd = false; + if (state->msRollover != "any" + && !((state->msRollover == "true" && eState & ControlState::ROLLOVER) + || (state->msRollover == "false" && !(eState & ControlState::ROLLOVER)))) + bAdd = false; + if (state->msDefault != "any" + && !((state->msDefault == "true" && eState & ControlState::DEFAULT) + || (state->msDefault == "false" && !(eState & ControlState::DEFAULT)))) + bAdd = false; + if (state->msSelected != "any" + && !((state->msSelected == "true" && eState & ControlState::SELECTED) + || (state->msSelected == "false" && !(eState & ControlState::SELECTED)))) + bAdd = false; + + ButtonValue eButtonValue = rValue.getTristateVal(); + + if (state->msButtonValue != "any" + && !((state->msButtonValue == "true" && eButtonValue == ButtonValue::On) + || (state->msButtonValue == "false" && eButtonValue == ButtonValue::Off))) + { + bAdd = false; + } + + OString sExtra = "any"_ostr; + + switch (eType) + { + case ControlType::TabItem: + { + auto const& rTabItemValue = static_cast<TabitemValue const&>(rValue); + + if (rTabItemValue.isLeftAligned() && rTabItemValue.isRightAligned() + && rTabItemValue.isFirst() && rTabItemValue.isLast()) + sExtra = "first_last"_ostr; + else if (rTabItemValue.isLeftAligned() || rTabItemValue.isFirst()) + sExtra = "first"_ostr; + else if (rTabItemValue.isRightAligned() || rTabItemValue.isLast()) + sExtra = "last"_ostr; + else + sExtra = "middle"_ostr; + } + break; + case ControlType::ListHeader: + { + if (ePart == ControlPart::Arrow) + { + if (rValue.getNumericVal() == 1) + sExtra = "down"_ostr; + else + sExtra = "up"_ostr; + } + } + break; + case ControlType::Pushbutton: + { + auto const& rPushButtonValue = static_cast<PushButtonValue const&>(rValue); + if (rPushButtonValue.mbIsAction) + sExtra = "action"_ostr; + } + break; + default: + break; + } + + if (state->msExtra != "any" && state->msExtra != sExtra) + { + bAdd = false; + } + + if (bAdd) + aStatesToAdd.push_back(state); + } + + return aStatesToAdd; +} + +WidgetDefinitionState::WidgetDefinitionState(OString sEnabled, OString sFocused, OString sPressed, + OString sRollover, OString sDefault, OString sSelected, + OString sButtonValue, OString sExtra) + : msEnabled(std::move(sEnabled)) + , msFocused(std::move(sFocused)) + , msPressed(std::move(sPressed)) + , msRollover(std::move(sRollover)) + , msDefault(std::move(sDefault)) + , msSelected(std::move(sSelected)) + , msButtonValue(std::move(sButtonValue)) + , msExtra(std::move(sExtra)) +{ +} + +void WidgetDefinitionState::addDrawRectangle(Color aStrokeColor, sal_Int32 nStrokeWidth, + Color aFillColor, float fX1, float fY1, float fX2, + float fY2, sal_Int32 nRx, sal_Int32 nRy) +{ + auto pCommand(std::make_shared<WidgetDrawActionRectangle>()); + pCommand->maStrokeColor = aStrokeColor; + pCommand->maFillColor = aFillColor; + pCommand->mnStrokeWidth = nStrokeWidth; + pCommand->mnRx = nRx; + pCommand->mnRy = nRy; + pCommand->mfX1 = fX1; + pCommand->mfY1 = fY1; + pCommand->mfX2 = fX2; + pCommand->mfY2 = fY2; + mpWidgetDrawActions.push_back(std::move(pCommand)); +} + +void WidgetDefinitionState::addDrawLine(Color aStrokeColor, sal_Int32 nStrokeWidth, float fX1, + float fY1, float fX2, float fY2) +{ + auto pCommand(std::make_shared<WidgetDrawActionLine>()); + pCommand->maStrokeColor = aStrokeColor; + pCommand->mnStrokeWidth = nStrokeWidth; + pCommand->mfX1 = fX1; + pCommand->mfY1 = fY1; + pCommand->mfX2 = fX2; + pCommand->mfY2 = fY2; + mpWidgetDrawActions.push_back(std::move(pCommand)); +} + +void WidgetDefinitionState::addDrawImage(OUString const& sSource) +{ + auto pCommand(std::make_shared<WidgetDrawActionImage>()); + pCommand->msSource = sSource; + mpWidgetDrawActions.push_back(std::move(pCommand)); +} + +void WidgetDefinitionState::addDrawExternal(OUString const& sSource) +{ + auto pCommand(std::make_shared<WidgetDrawActionExternal>()); + pCommand->msSource = sSource; + mpWidgetDrawActions.push_back(std::move(pCommand)); +} + +} // end vcl namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/WidgetDefinitionReader.cxx b/vcl/source/gdi/WidgetDefinitionReader.cxx new file mode 100644 index 0000000000..f5373b035c --- /dev/null +++ b/vcl/source/gdi/WidgetDefinitionReader.cxx @@ -0,0 +1,495 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <utility> +#include <widgetdraw/WidgetDefinitionReader.hxx> + +#include <sal/config.h> +#include <osl/file.hxx> +#include <tools/stream.hxx> +#include <o3tl/string_view.hxx> +#include <unordered_map> + +namespace vcl +{ +namespace +{ +bool lcl_fileExists(OUString const& sFilename) +{ + osl::File aFile(sFilename); + osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read); + return osl::FileBase::E_None == eRC; +} + +int lcl_gethex(char aChar) +{ + if (aChar >= '0' && aChar <= '9') + return aChar - '0'; + else if (aChar >= 'a' && aChar <= 'f') + return aChar - 'a' + 10; + else if (aChar >= 'A' && aChar <= 'F') + return aChar - 'A' + 10; + else + return 0; +} + +bool readColor(OString const& rString, Color& rColor) +{ + if (rString.getLength() != 7) + return false; + + const char aChar(rString[0]); + + if (aChar != '#') + return false; + + rColor.SetRed((lcl_gethex(rString[1]) << 4) | lcl_gethex(rString[2])); + rColor.SetGreen((lcl_gethex(rString[3]) << 4) | lcl_gethex(rString[4])); + rColor.SetBlue((lcl_gethex(rString[5]) << 4) | lcl_gethex(rString[6])); + + return true; +} + +bool readSetting(OString const& rInputString, OString& rOutputString) +{ + if (!rInputString.isEmpty()) + rOutputString = rInputString; + return true; +} + +OString getValueOrAny(OString const& rInputString) +{ + if (rInputString.isEmpty()) + return "any"_ostr; + return rInputString; +} + +ControlPart xmlStringToControlPart(std::string_view sPart) +{ + if (o3tl::equalsIgnoreAsciiCase(sPart, "NONE")) + return ControlPart::NONE; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "Entire")) + return ControlPart::Entire; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "ListboxWindow")) + return ControlPart::ListboxWindow; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "Button")) + return ControlPart::Button; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonUp")) + return ControlPart::ButtonUp; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonDown")) + return ControlPart::ButtonDown; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonLeft")) + return ControlPart::ButtonLeft; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "ButtonRight")) + return ControlPart::ButtonRight; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "AllButtons")) + return ControlPart::AllButtons; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "SeparatorHorz")) + return ControlPart::SeparatorHorz; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "SeparatorVert")) + return ControlPart::SeparatorVert; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzLeft")) + return ControlPart::TrackHorzLeft; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertUpper")) + return ControlPart::TrackVertUpper; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzRight")) + return ControlPart::TrackHorzRight; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertLower")) + return ControlPart::TrackVertLower; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackHorzArea")) + return ControlPart::TrackHorzArea; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "TrackVertArea")) + return ControlPart::TrackVertArea; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "Arrow")) + return ControlPart::Arrow; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "ThumbHorz")) + return ControlPart::ThumbHorz; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "ThumbVert")) + return ControlPart::ThumbVert; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItem")) + return ControlPart::MenuItem; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItemCheckMark")) + return ControlPart::MenuItemCheckMark; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "MenuItemRadioMark")) + return ControlPart::MenuItemRadioMark; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "Separator")) + return ControlPart::Separator; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "SubmenuArrow")) + return ControlPart::SubmenuArrow; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "SubEdit")) + return ControlPart::SubEdit; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "DrawBackgroundHorz")) + return ControlPart::DrawBackgroundHorz; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "DrawBackgroundVert")) + return ControlPart::DrawBackgroundVert; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "TabsDrawRtl")) + return ControlPart::TabsDrawRtl; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "HasBackgroundTexture")) + return ControlPart::HasBackgroundTexture; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "HasThreeButtons")) + return ControlPart::HasThreeButtons; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "BackgroundWindow")) + return ControlPart::BackgroundWindow; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "BackgroundDialog")) + return ControlPart::BackgroundDialog; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "Border")) + return ControlPart::Border; + else if (o3tl::equalsIgnoreAsciiCase(sPart, "Focus")) + return ControlPart::Focus; + return ControlPart::NONE; +} + +bool getControlTypeForXmlString(OString const& rString, ControlType& reType) +{ + static std::unordered_map<OString, ControlType> aPartMap = { + { "pushbutton", ControlType::Pushbutton }, + { "radiobutton", ControlType::Radiobutton }, + { "checkbox", ControlType::Checkbox }, + { "combobox", ControlType::Combobox }, + { "editbox", ControlType::Editbox }, + { "listbox", ControlType::Listbox }, + { "scrollbar", ControlType::Scrollbar }, + { "spinbox", ControlType::Spinbox }, + { "slider", ControlType::Slider }, + { "fixedline", ControlType::Fixedline }, + { "progress", ControlType::Progress }, + { "levelbar", ControlType::LevelBar }, + { "tabitem", ControlType::TabItem }, + { "tabheader", ControlType::TabHeader }, + { "tabpane", ControlType::TabPane }, + { "tabbody", ControlType::TabBody }, + { "frame", ControlType::Frame }, + { "windowbackground", ControlType::WindowBackground }, + { "toolbar", ControlType::Toolbar }, + { "listnode", ControlType::ListNode }, + { "listnet", ControlType::ListNet }, + { "listheader", ControlType::ListHeader }, + { "menubar", ControlType::Menubar }, + { "menupopup", ControlType::MenuPopup }, + { "tooltip", ControlType::Tooltip }, + }; + + auto const& rIterator = aPartMap.find(rString); + if (rIterator != aPartMap.end()) + { + reType = rIterator->second; + return true; + } + return false; +} + +} // end anonymous namespace + +WidgetDefinitionReader::WidgetDefinitionReader(OUString aDefinitionFile, OUString aResourcePath) + : m_rDefinitionFile(std::move(aDefinitionFile)) + , m_rResourcePath(std::move(aResourcePath)) +{ +} + +void WidgetDefinitionReader::readDrawingDefinition( + tools::XmlWalker& rWalker, const std::shared_ptr<WidgetDefinitionState>& rpState) +{ + rWalker.children(); + while (rWalker.isValid()) + { + if (rWalker.name() == "rect") + { + Color aStrokeColor; + readColor(rWalker.attribute("stroke"_ostr), aStrokeColor); + Color aFillColor; + readColor(rWalker.attribute("fill"_ostr), aFillColor); + OString sStrokeWidth = rWalker.attribute("stroke-width"_ostr); + sal_Int32 nStrokeWidth = -1; + if (!sStrokeWidth.isEmpty()) + nStrokeWidth = sStrokeWidth.toInt32(); + + sal_Int32 nRx = -1; + OString sRx = rWalker.attribute("rx"_ostr); + if (!sRx.isEmpty()) + nRx = sRx.toInt32(); + + sal_Int32 nRy = -1; + OString sRy = rWalker.attribute("ry"_ostr); + if (!sRy.isEmpty()) + nRy = sRy.toInt32(); + + OString sX1 = rWalker.attribute("x1"_ostr); + float fX1 = sX1.isEmpty() ? 0.0 : sX1.toFloat(); + + OString sY1 = rWalker.attribute("y1"_ostr); + float fY1 = sY1.isEmpty() ? 0.0 : sY1.toFloat(); + + OString sX2 = rWalker.attribute("x2"_ostr); + float fX2 = sX2.isEmpty() ? 1.0 : sX2.toFloat(); + + OString sY2 = rWalker.attribute("y2"_ostr); + float fY2 = sY2.isEmpty() ? 1.0 : sY2.toFloat(); + + rpState->addDrawRectangle(aStrokeColor, nStrokeWidth, aFillColor, fX1, fY1, fX2, fY2, + nRx, nRy); + } + else if (rWalker.name() == "line") + { + Color aStrokeColor; + readColor(rWalker.attribute("stroke"_ostr), aStrokeColor); + + OString sStrokeWidth = rWalker.attribute("stroke-width"_ostr); + sal_Int32 nStrokeWidth = -1; + if (!sStrokeWidth.isEmpty()) + nStrokeWidth = sStrokeWidth.toInt32(); + + OString sX1 = rWalker.attribute("x1"_ostr); + float fX1 = sX1.isEmpty() ? -1.0 : sX1.toFloat(); + + OString sY1 = rWalker.attribute("y1"_ostr); + float fY1 = sY1.isEmpty() ? -1.0 : sY1.toFloat(); + + OString sX2 = rWalker.attribute("x2"_ostr); + float fX2 = sX2.isEmpty() ? -1.0 : sX2.toFloat(); + + OString sY2 = rWalker.attribute("y2"_ostr); + float fY2 = sY2.isEmpty() ? -1.0 : sY2.toFloat(); + + rpState->addDrawLine(aStrokeColor, nStrokeWidth, fX1, fY1, fX2, fY2); + } + else if (rWalker.name() == "image") + { + OString sSource = rWalker.attribute("source"_ostr); + rpState->addDrawImage(m_rResourcePath + + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8)); + } + else if (rWalker.name() == "external") + { + OString sSource = rWalker.attribute("source"_ostr); + rpState->addDrawExternal(m_rResourcePath + + OStringToOUString(sSource, RTL_TEXTENCODING_UTF8)); + } + rWalker.next(); + } + rWalker.parent(); +} + +void WidgetDefinitionReader::readDefinition(tools::XmlWalker& rWalker, + WidgetDefinition& rWidgetDefinition, ControlType eType) +{ + rWalker.children(); + while (rWalker.isValid()) + { + if (rWalker.name() == "part") + { + OString sPart = rWalker.attribute("value"_ostr); + ControlPart ePart = xmlStringToControlPart(sPart); + + std::shared_ptr<WidgetDefinitionPart> pPart = std::make_shared<WidgetDefinitionPart>(); + + OString sWidth = rWalker.attribute("width"_ostr); + if (!sWidth.isEmpty()) + { + sal_Int32 nWidth = sWidth.isEmpty() ? 0 : sWidth.toInt32(); + pPart->mnWidth = nWidth; + } + + OString sHeight = rWalker.attribute("height"_ostr); + if (!sHeight.isEmpty()) + { + sal_Int32 nHeight = sHeight.isEmpty() ? 0 : sHeight.toInt32(); + pPart->mnHeight = nHeight; + } + + OString sMarginHeight = rWalker.attribute("margin-height"_ostr); + if (!sMarginHeight.isEmpty()) + { + sal_Int32 nMarginHeight = sMarginHeight.isEmpty() ? 0 : sMarginHeight.toInt32(); + pPart->mnMarginHeight = nMarginHeight; + } + + OString sMarginWidth = rWalker.attribute("margin-width"_ostr); + if (!sMarginWidth.isEmpty()) + { + sal_Int32 nMarginWidth = sMarginWidth.isEmpty() ? 0 : sMarginWidth.toInt32(); + pPart->mnMarginWidth = nMarginWidth; + } + + OString sOrientation = rWalker.attribute("orientation"_ostr); + if (!sOrientation.isEmpty()) + { + pPart->msOrientation = sOrientation; + } + + rWidgetDefinition.maDefinitions.emplace(ControlTypeAndPart(eType, ePart), pPart); + readPart(rWalker, pPart); + } + rWalker.next(); + } + rWalker.parent(); +} + +void WidgetDefinitionReader::readPart(tools::XmlWalker& rWalker, + std::shared_ptr<WidgetDefinitionPart> rpPart) +{ + rWalker.children(); + while (rWalker.isValid()) + { + if (rWalker.name() == "state") + { + OString sEnabled = getValueOrAny(rWalker.attribute("enabled"_ostr)); + OString sFocused = getValueOrAny(rWalker.attribute("focused"_ostr)); + OString sPressed = getValueOrAny(rWalker.attribute("pressed"_ostr)); + OString sRollover = getValueOrAny(rWalker.attribute("rollover"_ostr)); + OString sDefault = getValueOrAny(rWalker.attribute("default"_ostr)); + OString sSelected = getValueOrAny(rWalker.attribute("selected"_ostr)); + OString sButtonValue = getValueOrAny(rWalker.attribute("button-value"_ostr)); + OString sExtra = getValueOrAny(rWalker.attribute("extra"_ostr)); + + std::shared_ptr<WidgetDefinitionState> pState = std::make_shared<WidgetDefinitionState>( + sEnabled, sFocused, sPressed, sRollover, sDefault, sSelected, sButtonValue, sExtra); + + rpPart->maStates.push_back(pState); + readDrawingDefinition(rWalker, pState); + } + rWalker.next(); + } + rWalker.parent(); +} + +bool WidgetDefinitionReader::read(WidgetDefinition& rWidgetDefinition) +{ + if (!lcl_fileExists(m_rDefinitionFile)) + return false; + + auto pStyle = std::make_shared<WidgetDefinitionStyle>(); + + std::unordered_map<OString, Color*> aStyleColorMap = { + { "faceColor", &pStyle->maFaceColor }, + { "checkedColor", &pStyle->maCheckedColor }, + { "lightColor", &pStyle->maLightColor }, + { "lightBorderColor", &pStyle->maLightBorderColor }, + { "shadowColor", &pStyle->maShadowColor }, + { "darkShadowColor", &pStyle->maDarkShadowColor }, + { "buttonTextColor", &pStyle->maButtonTextColor }, + { "defaultActionButtonTextColor", &pStyle->maDefaultActionButtonTextColor }, + { "actionButtonTextColor", &pStyle->maActionButtonTextColor }, + { "actionButtonRolloverTextColor", &pStyle->maActionButtonRolloverTextColor }, + { "buttonRolloverTextColor", &pStyle->maButtonRolloverTextColor }, + { "radioCheckTextColor", &pStyle->maRadioCheckTextColor }, + { "groupTextColor", &pStyle->maGroupTextColor }, + { "labelTextColor", &pStyle->maLabelTextColor }, + { "windowColor", &pStyle->maWindowColor }, + { "windowTextColor", &pStyle->maWindowTextColor }, + { "dialogColor", &pStyle->maDialogColor }, + { "dialogTextColor", &pStyle->maDialogTextColor }, + { "workspaceColor", &pStyle->maWorkspaceColor }, + { "monoColor", &pStyle->maMonoColor }, + { "fieldColor", &pStyle->maFieldColor }, + { "fieldTextColor", &pStyle->maFieldTextColor }, + { "fieldRolloverTextColor", &pStyle->maFieldRolloverTextColor }, + { "activeColor", &pStyle->maActiveColor }, + { "activeTextColor", &pStyle->maActiveTextColor }, + { "activeBorderColor", &pStyle->maActiveBorderColor }, + { "deactiveColor", &pStyle->maDeactiveColor }, + { "deactiveTextColor", &pStyle->maDeactiveTextColor }, + { "deactiveBorderColor", &pStyle->maDeactiveBorderColor }, + { "menuColor", &pStyle->maMenuColor }, + { "menuBarColor", &pStyle->maMenuBarColor }, + { "menuBarRolloverColor", &pStyle->maMenuBarRolloverColor }, + { "menuBorderColor", &pStyle->maMenuBorderColor }, + { "menuTextColor", &pStyle->maMenuTextColor }, + { "menuBarTextColor", &pStyle->maMenuBarTextColor }, + { "menuBarRolloverTextColor", &pStyle->maMenuBarRolloverTextColor }, + { "menuBarHighlightTextColor", &pStyle->maMenuBarHighlightTextColor }, + { "menuHighlightColor", &pStyle->maMenuHighlightColor }, + { "menuHighlightTextColor", &pStyle->maMenuHighlightTextColor }, + { "highlightColor", &pStyle->maHighlightColor }, + { "highlightTextColor", &pStyle->maHighlightTextColor }, + { "activeTabColor", &pStyle->maActiveTabColor }, + { "inactiveTabColor", &pStyle->maInactiveTabColor }, + { "tabTextColor", &pStyle->maTabTextColor }, + { "tabRolloverTextColor", &pStyle->maTabRolloverTextColor }, + { "tabHighlightTextColor", &pStyle->maTabHighlightTextColor }, + { "disableColor", &pStyle->maDisableColor }, + { "helpColor", &pStyle->maHelpColor }, + { "helpTextColor", &pStyle->maHelpTextColor }, + { "linkColor", &pStyle->maLinkColor }, + { "visitedLinkColor", &pStyle->maVisitedLinkColor }, + { "toolTextColor", &pStyle->maToolTextColor }, + }; + + rWidgetDefinition.mpStyle = pStyle; + + auto pSettings = std::make_shared<WidgetDefinitionSettings>(); + + std::unordered_map<OString, OString*> aSettingMap = { + { "noActiveTabTextRaise", &pSettings->msNoActiveTabTextRaise }, + { "centeredTabs", &pSettings->msCenteredTabs }, + { "listBoxEntryMargin", &pSettings->msListBoxEntryMargin }, + { "defaultFontSize", &pSettings->msDefaultFontSize }, + { "titleHeight", &pSettings->msTitleHeight }, + { "floatTitleHeight", &pSettings->msFloatTitleHeight }, + { "listBoxPreviewDefaultLogicWidth", &pSettings->msListBoxPreviewDefaultLogicWidth }, + { "listBoxPreviewDefaultLogicHeight", &pSettings->msListBoxPreviewDefaultLogicHeight }, + }; + + rWidgetDefinition.mpSettings = pSettings; + + SvFileStream aFileStream(m_rDefinitionFile, StreamMode::READ); + + tools::XmlWalker aWalker; + if (!aWalker.open(&aFileStream)) + return false; + + if (aWalker.name() != "widgets") + return false; + + aWalker.children(); + while (aWalker.isValid()) + { + ControlType eType; + if (aWalker.name() == "style") + { + aWalker.children(); + while (aWalker.isValid()) + { + auto pair = aStyleColorMap.find(aWalker.name()); + if (pair != aStyleColorMap.end()) + { + readColor(aWalker.attribute("value"_ostr), *pair->second); + } + aWalker.next(); + } + aWalker.parent(); + } + if (aWalker.name() == "settings") + { + aWalker.children(); + while (aWalker.isValid()) + { + auto pair = aSettingMap.find(aWalker.name()); + if (pair != aSettingMap.end()) + { + readSetting(aWalker.attribute("value"_ostr), *pair->second); + } + aWalker.next(); + } + aWalker.parent(); + } + else if (getControlTypeForXmlString(aWalker.name(), eType)) + { + readDefinition(aWalker, rWidgetDefinition, eType); + } + aWalker.next(); + } + aWalker.parent(); + + return true; +} + +} // end vcl namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/configsettings.cxx b/vcl/source/gdi/configsettings.cxx new file mode 100644 index 0000000000..5586f67a61 --- /dev/null +++ b/vcl/source/gdi/configsettings.cxx @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <configsettings.hxx> + +#include <svdata.hxx> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <o3tl/any.hxx> +#include <sal/log.hxx> + +using namespace utl; +using namespace vcl; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; + +constexpr OUStringLiteral SETTINGS_CONFIGNODE = u"VCL/Settings"; + +SettingsConfigItem* SettingsConfigItem::get() +{ + ImplSVData* pSVData = ImplGetSVData(); + if( ! pSVData->mpSettingsConfigItem ) + pSVData->mpSettingsConfigItem.reset( new SettingsConfigItem() ); + return pSVData->mpSettingsConfigItem.get(); +} + +SettingsConfigItem::SettingsConfigItem() + : ConfigItem( SETTINGS_CONFIGNODE, ConfigItemMode::NONE ), + m_aSettings( 0 ) +{ + getValues(); +} + +SettingsConfigItem::~SettingsConfigItem() +{ + assert(!IsModified()); // should have been committed +} + +void SettingsConfigItem::ImplCommit() +{ + for (auto const& setting : m_aSettings) + { + OUString aKeyName( setting.first ); + /*bool bAdded =*/ AddNode( OUString(), aKeyName ); + Sequence< PropertyValue > aValues( setting.second.size() ); + PropertyValue* pValues = aValues.getArray(); + int nIndex = 0; + for (auto const& elem : setting.second) + { + pValues[nIndex].Name = aKeyName + "/" + elem.first; + pValues[nIndex].Handle = 0; + pValues[nIndex].Value <<= elem.second; + pValues[nIndex].State = PropertyState_DIRECT_VALUE; + nIndex++; + } + ReplaceSetProperties( aKeyName, aValues ); + } +} + +void SettingsConfigItem::Notify( const Sequence< OUString >& ) +{ + getValues(); +} + +void SettingsConfigItem::getValues() +{ + m_aSettings.clear(); + + const Sequence< OUString > aNames( GetNodeNames( OUString() ) ); + + for( const auto& aKeyName : aNames ) + { +#if OSL_DEBUG_LEVEL > 2 + SAL_INFO( "vcl", "found settings data for " << aKeyName ); +#endif + const Sequence< OUString > aKeys( GetNodeNames( aKeyName ) ); + Sequence< OUString > aSettingsKeys( aKeys.getLength() ); + std::transform(aKeys.begin(), aKeys.end(), aSettingsKeys.getArray(), + [&aKeyName](const OUString& rKey) -> OUString { return aKeyName + "/" + rKey; }); + const Sequence< Any > aValues( GetProperties( aSettingsKeys ) ); + for( int i = 0; i < aValues.getLength(); i++ ) + { + if( auto pLine = o3tl::tryAccess<OUString>(aValues[i]) ) + { + if( !pLine->isEmpty() ) + m_aSettings[ aKeyName ][ aKeys[i] ] = *pLine; +#if OSL_DEBUG_LEVEL > 2 + SAL_INFO( "vcl", " \"" << aKeys.getConstArray()[i] << "\"=\"" << *pLine << "\"" ); +#endif + } + } + } +} + +OUString SettingsConfigItem::getValue( const OUString& rGroup, const OUString& rKey ) const +{ + std::unordered_map< OUString, SmallOUStrMap >::const_iterator group = m_aSettings.find( rGroup ); + if( group == m_aSettings.end() || group->second.find( rKey ) == group->second.end() ) + { + return OUString(); + } + return group->second.find(rKey)->second; +} + +void SettingsConfigItem::setValue( const OUString& rGroup, const OUString& rKey, const OUString& rValue ) +{ + bool bModified = m_aSettings[ rGroup ][ rKey ] != rValue; + if( bModified ) + { + m_aSettings[ rGroup ][ rKey ] = rValue; + SetModified(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/cvtgrf.cxx b/vcl/source/gdi/cvtgrf.cxx new file mode 100644 index 0000000000..51fb6df10e --- /dev/null +++ b/vcl/source/gdi/cvtgrf.cxx @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/cvtgrf.hxx> +#include <tools/stream.hxx> + +#include <svdata.hxx> + +// Callback +GraphicConverter::GraphicConverter() +{ +} + +ErrCode GraphicConverter::Import( SvStream& rIStm, Graphic& rGraphic, ConvertDataFormat nFormat ) +{ + GraphicConverter* pCvt = ImplGetSVData()->maGDIData.mxGrfConverter.get(); + ErrCode nRet = ERRCODE_IO_GENERAL; + + if( pCvt && pCvt->GetFilterHdl().IsSet() ) + { + ConvertData aData( rGraphic, rIStm, nFormat ); + + if( pCvt->GetFilterHdl().Call( aData ) ) + { + rGraphic = aData.maGraphic; + nRet = ERRCODE_NONE; + } + else if( rIStm.GetError() ) + nRet = rIStm.GetError(); + } + + return nRet; +} + +ErrCode GraphicConverter::Export( SvStream& rOStm, const Graphic& rGraphic, ConvertDataFormat nFormat ) +{ + GraphicConverter* pCvt = ImplGetSVData()->maGDIData.mxGrfConverter.get(); + ErrCode nRet = ERRCODE_IO_GENERAL; + + if( pCvt && pCvt->GetFilterHdl().IsSet() ) + { + ConvertData aData( rGraphic, rOStm, nFormat ); + + if( pCvt->GetFilterHdl().Call( aData ) ) + nRet = ERRCODE_NONE; + else if( rOStm.GetError() ) + nRet = rOStm.GetError(); + } + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/embeddedfontshelper.cxx b/vcl/source/gdi/embeddedfontshelper.cxx new file mode 100644 index 0000000000..a8a4f7e983 --- /dev/null +++ b/vcl/source/gdi/embeddedfontshelper.cxx @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <memory> +#include <config_folders.h> +#include <config_eot.h> + +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/embeddedfontshelper.hxx> +#include <com/sun/star/io/XInputStream.hpp> + +#include <font/PhysicalFontFaceCollection.hxx> +#include <font/PhysicalFontCollection.hxx> +#include <salgdi.hxx> +#include <sft.hxx> + + +#if ENABLE_EOT +extern "C" +{ +namespace libeot +{ +#include <libeot/libeot.h> +} // namespace libeot +} // extern "C" +#endif + +using namespace com::sun::star; +using namespace vcl; + +static void clearDir( const OUString& path ) +{ + osl::Directory dir( path ); + if( dir.reset() == osl::Directory::E_None ) + { + for(;;) + { + osl::DirectoryItem item; + if( dir.getNextItem( item ) != osl::Directory::E_None ) + break; + osl::FileStatus status( osl_FileStatus_Mask_FileURL ); + if( item.getFileStatus( status ) == osl::File::E_None ) + osl::File::remove( status.getFileURL()); + } + } +} + +void EmbeddedFontsHelper::clearTemporaryFontFiles() +{ + OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros( path ); + path += "/user/temp/embeddedfonts/"; + clearDir( path + "fromdocs/" ); + clearDir( path + "fromsystem/" ); +} + +bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName, + std::u16string_view extra, std::vector< unsigned char > const & key, bool eot ) +{ + OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra ); + osl::File file( fileUrl ); + switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write )) + { + case osl::File::E_None: + break; // ok + case osl::File::E_EXIST: + return true; // Assume it's already been added correctly. + default: + SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" ); + return false; + } + size_t keyPos = 0; + std::vector< char > fontData; + fontData.reserve( 1000000 ); + for(;;) + { + uno::Sequence< sal_Int8 > buffer; + sal_uInt64 read = stream->readBytes( buffer, 1024 ); + auto bufferRange = asNonConstRange(buffer); + for( sal_uInt64 pos = 0; + pos < read && keyPos < key.size(); + ++pos ) + bufferRange[ pos ] ^= key[ keyPos++ ]; + // if eot, don't write the file out yet, since we need to unpack it first. + if( !eot && read > 0 ) + { + sal_uInt64 writtenTotal = 0; + while( writtenTotal < read ) + { + sal_uInt64 written; + file.write( buffer.getConstArray(), read, written ); + writtenTotal += written; + } + } + fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read ); + if( read <= 0 ) + break; + } + bool sufficientFontRights(false); +#if ENABLE_EOT + if( eot ) + { + unsigned uncompressedFontSize = 0; + unsigned char *nakedPointerToUncompressedFont = nullptr; + libeot::EOTMetadata eotMetadata; + libeot::EOTError uncompressError = + libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize ); + std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer ); + if( uncompressError != libeot::EOT_SUCCESS ) + { + SAL_WARN( "vcl.fonts", "Failed to uncompress font" ); + osl::File::remove( fileUrl ); + return false; + } + sal_uInt64 writtenTotal = 0; + while( writtenTotal < uncompressedFontSize ) + { + sal_uInt64 written; + if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None ) + { + SAL_WARN( "vcl.fonts", "Error writing temporary font file" ); + osl::File::remove( fileUrl ); + return false; + } + writtenTotal += written; + } + sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata ); + libeot::EOTfreeMetadata( &eotMetadata ); + } +#endif + + if( file.close() != osl::File::E_None ) + { + SAL_WARN( "vcl.fonts", "Writing temporary font file failed" ); + osl::File::remove( fileUrl ); + return false; + } + if( !eot ) + { + sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed); + } + if( !sufficientFontRights ) + { + // It would be actually better to open the document in read-only mode in this case, + // warn the user about this, and provide a button to drop the font(s) in order + // to switch to editing. + SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" ); + osl::File::remove( fileUrl ); + return false; + } + m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl)); + return true; +} + +namespace +{ + struct UpdateFontsGuard + { + UpdateFontsGuard() + { + OutputDevice::ImplClearAllFontData(true); + } + + ~UpdateFontsGuard() + { + OutputDevice::ImplRefreshAllFontData(true); + } + }; +} + +void EmbeddedFontsHelper::activateFonts() +{ + if (m_aAccumulatedFonts.empty()) + return; + UpdateFontsGuard aUpdateFontsGuard; + for (const auto& rEntry : m_aAccumulatedFonts) + EmbeddedFontsHelper::activateFont(rEntry.first, rEntry.second); + m_aAccumulatedFonts.clear(); +} + +OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, std::u16string_view extra ) +{ + OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros( path ); + path += "/user/temp/embeddedfonts/fromdocs/"; + osl::Directory::createPath( path ); + OUString filename = fontName; + static int uniqueCounter = 0; + if( extra == u"?" ) + filename += OUString::number( uniqueCounter++ ); + else + filename += extra; + filename += ".ttf"; // TODO is it always ttf? + return path + filename; +} + +void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl ) +{ + OutputDevice *pDevice = Application::GetDefaultDevice(); + pDevice->AddTempDevFont(fileUrl, fontName); +} + +// Check if it's (legally) allowed to embed the font file into a document +// (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears +// to have a different meaning (guessing from code, IsSubsettable() might +// possibly mean it's ttf, while IsEmbeddable() might mean it's type1). +// So just try to open the data as ttf and see. +bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, tools::Long size, FontRights rights ) +{ + TrueTypeFont* font; + if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SFErrCodes::Ok ) + { + TTGlobalFontInfo info; + GetTTGlobalFontInfo( font, &info ); + CloseTTFont( font ); + // https://www.microsoft.com/typography/otspec/os2.htm#fst + int copyright = info.typeFlags; + switch( rights ) + { + case FontRights::ViewingAllowed: + // Embedding not restricted completely. + return ( copyright & 0x02 ) != 0x02; + case FontRights::EditingAllowed: + // Font is installable or editable. + return copyright == 0 || ( copyright & 0x08 ); + } + } + return true; // no known restriction +} + +OUString EmbeddedFontsHelper::fontFileUrl( std::u16string_view familyName, FontFamily family, FontItalic italic, + FontWeight weight, FontPitch pitch, FontRights rights ) +{ + OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros( path ); + path += "/user/temp/embeddedfonts/fromsystem/"; + osl::Directory::createPath( path ); + OUString filename = OUString::Concat(familyName) + "_" + OUString::number( family ) + "_" + OUString::number( italic ) + + "_" + OUString::number( weight ) + "_" + OUString::number( pitch ) + + ".ttf"; // TODO is it always ttf? + OUString url = path + filename; + if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists() + { + // File with contents of the font file already exists, assume it's been created by a previous call. + return url; + } + bool ok = false; + SalGraphics* graphics = Application::GetDefaultDevice()->GetGraphics(); + vcl::font::PhysicalFontCollection fonts; + graphics->GetDevFontList( &fonts ); + std::unique_ptr< vcl::font::PhysicalFontFaceCollection > fontInfo( fonts.GetFontFaceCollection()); + vcl::font::PhysicalFontFace* selected = nullptr; + + // Maybe we don't find the perfect match for the font. E.G. we have fonts with the same family name + // but not same bold or italic etc... + // In this case we add all the fonts having the family name of the used font: + // - we store all these fonts in familyNameFonts during loop + // - if we haven't found the perfect match we store all fonts in familyNameFonts + typedef std::vector<vcl::font::PhysicalFontFace*> FontList; + FontList familyNameFonts; + + for( int i = 0; + i < fontInfo->Count(); + ++i ) + { + vcl::font::PhysicalFontFace* f = fontInfo->Get( i ); + if( f->GetFamilyName() == familyName ) + { + // Ignore comparing text encodings, at least for now. They cannot be trivially compared + // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding, + // and just having a unicode font doesn't say what glyphs it actually contains). + // It is possible that it still may be needed to do at least some checks here + // for some encodings (can one font have more font files for more encodings?). + if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family ) + && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic ) + && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight ) + && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch )) + { // Exact match, return it immediately. + selected = f; + break; + } + if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family ) + && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic ) + && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight ) + && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch )) + { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one. + selected = f; + } + // adding "not perfect match" to familyNameFonts vector + familyNameFonts.push_back(f); + + } + } + + // if we have found a perfect match we will add only "selected", otherwise all familyNameFonts + FontList fontsToAdd = (selected ? FontList(1, selected) : std::move(familyNameFonts)); + + for (vcl::font::PhysicalFontFace* f : fontsToAdd) + { + if (!selected) { // recalculate file not for "not perfect match" + filename = OUString::Concat(familyName) + "_" + OUString::number(f->GetFamilyType()) + "_" + + OUString::number(f->GetItalic()) + "_" + OUString::number(f->GetWeight()) + "_" + + OUString::number(f->GetPitch()) + ".ttf"; // TODO is it always ttf? + url = path + filename; + if (osl::File(url).open(osl_File_OpenFlag_Read) == osl::File::E_None) // = exists() + { + // File with contents of the font file already exists, assume it's been created by a previous call. + continue; + } + } + auto aFontData(f->GetRawFontData(0)); + if (!aFontData.empty()) + { + auto data = aFontData.data(); + auto size = aFontData.size(); + if( sufficientTTFRights( data, size, rights )) + { + osl::File file( url ); + if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None ) + { + sal_uInt64 written = 0; + sal_uInt64 totalSize = size; + bool error = false; + while( written < totalSize && !error) + { + sal_uInt64 nowWritten; + switch( file.write( data + written, size - written, nowWritten )) + { + case osl::File::E_None: + written += nowWritten; + break; + case osl::File::E_AGAIN: + case osl::File::E_INTR: + break; + default: + error = true; + break; + } + } + file.close(); + if( error ) + osl::File::remove( url ); + else + ok = true; + } + } + } + } + return ok ? url : ""; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/gdi/extoutdevdata.cxx b/vcl/source/gdi/extoutdevdata.cxx new file mode 100644 index 0000000000..1bb84a6694 --- /dev/null +++ b/vcl/source/gdi/extoutdevdata.cxx @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/extoutdevdata.hxx> + +namespace vcl +{ +ExtOutDevData::~ExtOutDevData() {} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/formpdfexport.cxx b/vcl/source/gdi/formpdfexport.cxx new file mode 100644 index 0000000000..ac952491a2 --- /dev/null +++ b/vcl/source/gdi/formpdfexport.cxx @@ -0,0 +1,824 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <memory> +#include <vcl/formpdfexport.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/lineend.hxx> +#include <unordered_map> +#include <sal/log.hxx> + +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/awt/TextAlign.hpp> +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <com/sun/star/form/FormButtonType.hpp> +#include <com/sun/star/form/FormSubmitMethod.hpp> + +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/pdfextoutdevdata.hxx> +#include <vcl/unohelp.hxx> + +#include <algorithm> +#include <iterator> + + +static vcl::Font CreateFont( const css::awt::FontDescriptor& rDescr ) +{ + vcl::Font aFont; + if ( !rDescr.Name.isEmpty() ) + aFont.SetFamilyName( rDescr.Name ); + if ( !rDescr.StyleName.isEmpty() ) + aFont.SetStyleName( rDescr.StyleName ); + if ( rDescr.Height ) + aFont.SetFontSize( Size( rDescr.Width, rDescr.Height ) ); + if ( static_cast<FontFamily>(rDescr.Family) != FAMILY_DONTKNOW ) + aFont.SetFamily( static_cast<FontFamily>(rDescr.Family) ); + if ( static_cast<rtl_TextEncoding>(rDescr.CharSet) != RTL_TEXTENCODING_DONTKNOW ) + aFont.SetCharSet( static_cast<rtl_TextEncoding>(rDescr.CharSet) ); + if ( static_cast<FontPitch>(rDescr.Pitch) != PITCH_DONTKNOW ) + aFont.SetPitch( static_cast<FontPitch>(rDescr.Pitch) ); + if ( rDescr.CharacterWidth ) + aFont.SetWidthType(vcl::unohelper::ConvertFontWidth(rDescr.CharacterWidth)); + if ( rDescr.Weight ) + aFont.SetWeight(vcl::unohelper::ConvertFontWeight(rDescr.Weight)); + if ( rDescr.Slant != css::awt::FontSlant_DONTKNOW ) + aFont.SetItalic(vcl::unohelper::ConvertFontSlant(rDescr.Slant)); + if ( static_cast<FontLineStyle>(rDescr.Underline) != LINESTYLE_DONTKNOW ) + aFont.SetUnderline( static_cast<FontLineStyle>(rDescr.Underline) ); + if ( static_cast<FontStrikeout>(rDescr.Strikeout) != STRIKEOUT_DONTKNOW ) + aFont.SetStrikeout( static_cast<FontStrikeout>(rDescr.Strikeout) ); + + // Not DONTKNOW + aFont.SetOrientation( Degree10(static_cast<sal_Int16>(rDescr.Orientation * 10)) ); + aFont.SetKerning( static_cast<FontKerning>(rDescr.Kerning) ); + aFont.SetWordLineMode( rDescr.WordLineMode ); + + return aFont; +} + +namespace toolkitform +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::style; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::container; + + constexpr OUString FM_PROP_NAME = u"Name"_ustr; + + namespace + { + + /** determines the FormComponentType of a form control + */ + sal_Int16 classifyFormControl( const Reference< XPropertySet >& _rxModel ) + { + static constexpr OUString FM_PROP_CLASSID = u"ClassId"_ustr; + sal_Int16 nControlType = FormComponentType::CONTROL; + + Reference< XPropertySetInfo > xPSI; + if ( _rxModel.is() ) + xPSI = _rxModel->getPropertySetInfo(); + if ( xPSI.is() && xPSI->hasPropertyByName( FM_PROP_CLASSID ) ) + { + if( ! (_rxModel->getPropertyValue( FM_PROP_CLASSID ) >>= nControlType) ) { + SAL_WARN("toolkit.helper", "classifyFormControl: unable to get property " << FM_PROP_CLASSID); + } + } + + return nControlType; + } + + + /** (default-)creates a PDF widget according to a given FormComponentType + */ + std::unique_ptr<vcl::PDFWriter::AnyWidget> createDefaultWidget( sal_Int16 _nFormComponentType ) + { + switch ( _nFormComponentType ) + { + case FormComponentType::COMMANDBUTTON: + return std::make_unique<vcl::PDFWriter::PushButtonWidget>(); + case FormComponentType::CHECKBOX: + return std::make_unique<vcl::PDFWriter::CheckBoxWidget>(); + case FormComponentType::RADIOBUTTON: + return std::make_unique<vcl::PDFWriter::RadioButtonWidget>(); + case FormComponentType::LISTBOX: + return std::make_unique<vcl::PDFWriter::ListBoxWidget>(); + case FormComponentType::COMBOBOX: + return std::make_unique<vcl::PDFWriter::ComboBoxWidget>(); + + case FormComponentType::TEXTFIELD: + case FormComponentType::FILECONTROL: + case FormComponentType::DATEFIELD: + case FormComponentType::TIMEFIELD: + case FormComponentType::NUMERICFIELD: + case FormComponentType::CURRENCYFIELD: + case FormComponentType::PATTERNFIELD: + return std::make_unique<vcl::PDFWriter::EditWidget>(); + } + return nullptr; + } + + + /** determines a unique number for the radio group which the given radio + button model belongs to + + The number is guaranteed to be + <ul><li>unique within the document in which the button lives</li> + <li>the same for subsequent calls with other radio button models, + which live in the same document, and belong to the same group</li> + </ul> + + @precond + the model must be part of the form component hierarchy in a document + */ + sal_Int32 determineRadioGroupId( const Reference< XPropertySet >& _rxRadioModel ) + { + OSL_ENSURE( classifyFormControl( _rxRadioModel ) == FormComponentType::RADIOBUTTON, + "determineRadioGroupId: this *is* no radio button model!" ); + // The fact that radio button groups need to be unique within the complete + // host document makes it somewhat difficult ... + // Problem is that two form radio buttons belong to the same group if + // - they have the same parent + // - AND they have the same name or group name + // This implies that we need some knowledge about (potentially) *all* radio button + // groups in the document. + + // get the root-level container + Reference< XChild > xChild( _rxRadioModel, UNO_QUERY ); + Reference< XForm > xParentForm( xChild.is() ? xChild->getParent() : Reference< XInterface >(), UNO_QUERY ); + OSL_ENSURE( xParentForm.is(), "determineRadioGroupId: no parent form -> group id!" ); + if ( !xParentForm.is() ) + return -1; + + while ( xParentForm.is() ) + { + xChild = xParentForm.get(); + xParentForm.set(xChild->getParent(), css::uno::UNO_QUERY); + } + Reference< XIndexAccess > xRoot( xChild->getParent(), UNO_QUERY ); + OSL_ENSURE( xRoot.is(), "determineRadioGroupId: unable to determine the root of the form component hierarchy!" ); + if ( !xRoot.is() ) + return -1; + + // count the leafs in the hierarchy, until we encounter radio button + ::std::vector< Reference< XIndexAccess > > aAncestors; + ::std::vector< sal_Int32 > aPath; + + Reference< XInterface > xNormalizedLookup( _rxRadioModel, UNO_QUERY ); + Reference< XIndexAccess > xCurrentContainer( xRoot ); + sal_Int32 nStartWithChild = 0; + sal_Int32 nGroupsEncountered = 0; + do + { + std::unordered_map<OUString,sal_Int32> GroupNameMap; + std::unordered_map<OUString,sal_Int32> SharedNameMap; + sal_Int32 nCount = xCurrentContainer->getCount(); + sal_Int32 i; + for ( i = nStartWithChild; i < nCount; ++i ) + { + Reference< XInterface > xElement( xCurrentContainer->getByIndex( i ), UNO_QUERY ); + if ( !xElement.is() ) + { + OSL_FAIL( "determineRadioGroupId: very suspicious!" ); + continue; + } + + Reference< XIndexAccess > xNewContainer( xElement, UNO_QUERY ); + if ( xNewContainer.is() ) + { + // step down the hierarchy + aAncestors.push_back( xCurrentContainer ); + xCurrentContainer = xNewContainer; + aPath.push_back( i ); + nStartWithChild = 0; + break; + // out of the inner loop, but continue with the outer loop + } + + if ( xElement.get() == xNormalizedLookup.get() ) + { + // Our radio button is in this container. + // Now take the time to ID this container's groups and return the button's groupId + for ( i = 0; i < nCount; ++i ) + { + try + { + xElement.set( xCurrentContainer->getByIndex( i ), UNO_QUERY_THROW ); + Reference< XServiceInfo > xModelSI( xElement, UNO_QUERY_THROW ); + if ( xModelSI->supportsService("com.sun.star.awt.UnoControlRadioButtonModel") ) + { + Reference< XPropertySet > aProps( xElement, UNO_QUERY_THROW ); + + OUString sGroupName; + aProps->getPropertyValue("GroupName") >>= sGroupName; + if ( !sGroupName.isEmpty() ) + { + // map: unique key is the group name, so attempts to add a different ID value + // for an existing group are ignored - keeping the first ID - perfect for this scenario. + GroupNameMap.emplace( sGroupName, nGroupsEncountered + i ); + + if ( xElement.get() == xNormalizedLookup.get() ) + return GroupNameMap[sGroupName]; + } + else + { + // Old implementation didn't have a GroupName, just identical Control names. + aProps->getPropertyValue( FM_PROP_NAME ) >>= sGroupName; + SharedNameMap.emplace( sGroupName, nGroupsEncountered + i ); + + if ( xElement.get() == xNormalizedLookup.get() ) + return SharedNameMap[sGroupName]; + } + + } + } + catch( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("toolkit"); + } + } + SAL_WARN("toolkit.helper","determineRadioGroupId: did not find the radios element's group!" ); + } + } + + // we encounter this container the first time. In particular, we did not just step up + if ( nStartWithChild == 0 ) + { + // Our control wasn't in this container, so consider every item to be a possible unique group. + // This is way too much: Not all of the elements in the current container will form groups. + // But anyway, this number is sufficient for our purpose, since sequential group ids are not required. + // Ultimately, the container contains *at most* this many groups. + nGroupsEncountered += nCount; + } + + if ( i >= nCount ) + { + // the loop terminated because there were no more elements + // -> step up, if possible + if ( aAncestors.empty() ) + break; + + xCurrentContainer = aAncestors.back(); aAncestors.pop_back(); + nStartWithChild = aPath.back() + 1; aPath.pop_back(); + } + } + while ( true ); + return -1; + } + + + /** copies a StringItemList to a PDF widget's list + */ + void getStringItemVector( const Reference< XPropertySet >& _rxModel, ::std::vector< OUString >& _rVector ) + { + Sequence< OUString > aListEntries; + if( ! (_rxModel->getPropertyValue( "StringItemList" ) >>= aListEntries) ) { + SAL_WARN("toolkit.helper", "getStringItemVector: unable to get property StringItemList"); + } + _rVector.insert( _rVector.end(), std::cbegin(aListEntries), std::cend(aListEntries) ); + } + } + + + /** creates a PDF compatible control descriptor for the given control + */ + std::unique_ptr<vcl::PDFWriter::AnyWidget> describePDFControl( const Reference< XControl >& _rxControl, + vcl::PDFExtOutDevData& i_pdfExportData ) + { + std::unique_ptr<vcl::PDFWriter::AnyWidget> Descriptor; + OSL_ENSURE( _rxControl.is(), "describePDFControl: invalid (NULL) control!" ); + if ( !_rxControl.is() ) + return Descriptor; + + try + { + Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY ); + sal_Int16 nControlType = classifyFormControl( xModelProps ); + Descriptor = createDefaultWidget( nControlType ); + if (!Descriptor) + // no PDF widget available for this + return Descriptor; + + Reference< XPropertySetInfo > xPSI( xModelProps->getPropertySetInfo() ); + Reference< XServiceInfo > xSI( xModelProps, UNO_QUERY ); + OSL_ENSURE( xSI.is(), "describePDFControl: no service info!" ); + // if we survived classifyFormControl, then it's a real form control, and they all have + // service infos + + + // set the common widget properties + + + // Name, Description, Text + if( ! (xModelProps->getPropertyValue( FM_PROP_NAME ) >>= Descriptor->Name) ) { + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_NAME); + } + if( ! (xModelProps->getPropertyValue( "HelpText" ) >>= Descriptor->Description) ) { + SAL_INFO("toolkit.helper", "describePDFControl: unable to get property HelpText"); + } + Any aText; + static constexpr OUString FM_PROP_TEXT = u"Text"_ustr; + static constexpr OUString FM_PROP_LABEL = u"Label"_ustr; + static constexpr OUString FM_PROP_VALUE = u"Value"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_TEXT ) ) + aText = xModelProps->getPropertyValue( FM_PROP_TEXT ); + else if ( xPSI->hasPropertyByName( FM_PROP_LABEL ) ) + aText = xModelProps->getPropertyValue( FM_PROP_LABEL ); + else if ( xPSI->hasPropertyByName( FM_PROP_VALUE ) ) + { + double aValue; + if (xModelProps->getPropertyValue( FM_PROP_VALUE ) >>= aValue) + aText <<= OUString::number(aValue); + } + + if ( aText.hasValue() ) { + if( ! (aText >>= Descriptor->Text) ) { + SAL_WARN("toolkit.helper", "describePDFControl: unable to assign aText to Descriptor->Text"); + } + } + + + // readonly + static constexpr OUString FM_PROP_READONLY = u"ReadOnly"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_READONLY ) ) + if( ! (xModelProps->getPropertyValue( FM_PROP_READONLY ) >>= Descriptor->ReadOnly) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_READONLY); + + + // border + { + static constexpr OUString FM_PROP_BORDER = u"Border"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_BORDER ) ) + { + sal_Int16 nBorderType = 0; + if( ! (xModelProps->getPropertyValue( FM_PROP_BORDER ) >>= nBorderType) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_BORDER); + Descriptor->Border = ( nBorderType != 0 ); + + OUString sBorderColorPropertyName( "BorderColor" ); + if ( xPSI->hasPropertyByName( sBorderColorPropertyName ) ) + { + Color nBorderColor = COL_TRANSPARENT; + if ( xModelProps->getPropertyValue( sBorderColorPropertyName ) >>= nBorderColor ) + Descriptor->BorderColor = nBorderColor; + else + Descriptor->BorderColor = COL_BLACK; + } + } + } + + + // background color + static constexpr OUString FM_PROP_BACKGROUNDCOLOR = u"BackgroundColor"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_BACKGROUNDCOLOR ) ) + { + Color nBackColor = COL_TRANSPARENT; + xModelProps->getPropertyValue( FM_PROP_BACKGROUNDCOLOR ) >>= nBackColor; + Descriptor->Background = true; + Descriptor->BackgroundColor = nBackColor; + } + + + // text color + static constexpr OUString FM_PROP_TEXTCOLOR = u"TextColor"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_TEXTCOLOR ) ) + { + Color nTextColor = COL_TRANSPARENT; + xModelProps->getPropertyValue( FM_PROP_TEXTCOLOR ) >>= nTextColor; + Descriptor->TextColor = nTextColor; + } + + + // text style + Descriptor->TextStyle = DrawTextFlags::NONE; + + // multi line and word break + // The MultiLine property of the control is mapped to both the "MULTILINE" and + // "WORDBREAK" style flags + static constexpr OUString FM_PROP_MULTILINE = u"MultiLine"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_MULTILINE ) ) + { + bool bMultiLine = false; + if( ! (xModelProps->getPropertyValue( FM_PROP_MULTILINE ) >>= bMultiLine) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_MULTILINE); + if ( bMultiLine ) + Descriptor->TextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; + } + + // horizontal alignment + static constexpr OUString FM_PROP_ALIGN = u"Align"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_ALIGN ) ) + { + sal_Int16 nAlign = awt::TextAlign::LEFT; + xModelProps->getPropertyValue( FM_PROP_ALIGN ) >>= nAlign; + // TODO: when the property is VOID - are there situations/UIs where this + // means something else than LEFT? + switch ( nAlign ) + { + case awt::TextAlign::LEFT: Descriptor->TextStyle |= DrawTextFlags::Left; break; + case awt::TextAlign::CENTER: Descriptor->TextStyle |= DrawTextFlags::Center; break; + case awt::TextAlign::RIGHT: Descriptor->TextStyle |= DrawTextFlags::Right; break; + default: + OSL_FAIL( "describePDFControl: invalid text align!" ); + } + } + + // vertical alignment + { + OUString sVertAlignPropertyName( "VerticalAlign" ); + if ( xPSI->hasPropertyByName( sVertAlignPropertyName ) ) + { + VerticalAlignment nAlign = VerticalAlignment_MIDDLE; + xModelProps->getPropertyValue( sVertAlignPropertyName ) >>= nAlign; + switch ( nAlign ) + { + case VerticalAlignment_TOP: Descriptor->TextStyle |= DrawTextFlags::Top; break; + case VerticalAlignment_MIDDLE: Descriptor->TextStyle |= DrawTextFlags::VCenter; break; + case VerticalAlignment_BOTTOM: Descriptor->TextStyle |= DrawTextFlags::Bottom; break; + default: + OSL_FAIL( "describePDFControl: invalid vertical text align!" ); + } + } + } + + // font + static constexpr OUString FM_PROP_FONT = u"FontDescriptor"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_FONT ) ) + { + FontDescriptor aUNOFont; + if( ! (xModelProps->getPropertyValue( FM_PROP_FONT ) >>= aUNOFont) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_FONT); + Descriptor->TextFont = CreateFont( aUNOFont ); + } + + // tab order + OUString aTabIndexString( "TabIndex" ); + if ( xPSI->hasPropertyByName( aTabIndexString ) ) + { + sal_Int16 nIndex = -1; + if( ! (xModelProps->getPropertyValue( aTabIndexString ) >>= nIndex) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << aTabIndexString); + Descriptor->TabOrder = nIndex; + } + + + // special widget properties + + // edits + if ( Descriptor->getType() == vcl::PDFWriter::Edit ) + { + vcl::PDFWriter::EditWidget* pEditWidget = static_cast< vcl::PDFWriter::EditWidget* >( Descriptor.get() ); + + // multiline (already flagged in the TextStyle) + pEditWidget->MultiLine = bool( Descriptor->TextStyle & DrawTextFlags::MultiLine ); + + // password input + OUString sEchoCharPropName( "EchoChar" ); + if ( xPSI->hasPropertyByName( sEchoCharPropName ) ) + { + sal_Int16 nEchoChar = 0; + if ( ( xModelProps->getPropertyValue( sEchoCharPropName ) >>= nEchoChar ) && ( nEchoChar != 0 ) ) + pEditWidget->Password = true; + } + + // file select + if ( xSI->supportsService( "com.sun.star.form.component.FileControl" ) ) + pEditWidget->FileSelect = true; + + // maximum text length + static constexpr OUString FM_PROP_MAXTEXTLEN = u"MaxTextLen"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_MAXTEXTLEN ) ) + { + sal_Int16 nMaxTextLength = 0; + if( ! (xModelProps->getPropertyValue( FM_PROP_MAXTEXTLEN ) >>= nMaxTextLength) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_MAXTEXTLEN); + if ( nMaxTextLength <= 0 ) + // "-1" has a special meaning for database-bound controls + nMaxTextLength = 0; + pEditWidget->MaxLen = nMaxTextLength; + } + + switch ( nControlType ) + { + case FormComponentType::CURRENCYFIELD: + case FormComponentType::NUMERICFIELD: + { + + pEditWidget->Format = vcl::PDFWriter::Number; + + static constexpr OUString FM_PROP_CURRENCYSYMBOL = u"CurrencySymbol"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_CURRENCYSYMBOL ) ) + { + OUString sCurrencySymbol; + if( ! (xModelProps->getPropertyValue( FM_PROP_CURRENCYSYMBOL ) >>= sCurrencySymbol) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_CURRENCYSYMBOL); + pEditWidget->CurrencySymbol = sCurrencySymbol; + } + + static constexpr OUString FM_PROP_DECIMALACCURACY = u"DecimalAccuracy"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_DECIMALACCURACY ) ) + { + sal_Int32 nDecimalAccuracy = 0; + if( ! (xModelProps->getPropertyValue( FM_PROP_DECIMALACCURACY ) >>= nDecimalAccuracy) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_DECIMALACCURACY); + pEditWidget->DecimalAccuracy = nDecimalAccuracy; + } + + static constexpr OUString FM_PROP_PREPENDCURRENCYSYMBOL = u"PrependCurrencySymbol"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_PREPENDCURRENCYSYMBOL ) ) + { + bool bPrependCurrencySymbol = true; + if( ! (xModelProps->getPropertyValue( FM_PROP_PREPENDCURRENCYSYMBOL ) >>= bPrependCurrencySymbol) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_PREPENDCURRENCYSYMBOL); + pEditWidget->PrependCurrencySymbol = bPrependCurrencySymbol; + } + } break; + case FormComponentType::TIMEFIELD: + { + pEditWidget->Format = vcl::PDFWriter::Time; + + static constexpr OUString FM_PROP_TIMEFORMAT = u"TimeFormat"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_TIMEFORMAT ) ) + { + sal_Int32 nTimeFormat = 0; + if( ! (xModelProps->getPropertyValue( FM_PROP_TIMEFORMAT ) >>= nTimeFormat) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TIMEFORMAT); + + switch ( nTimeFormat ) + { + case 0: + pEditWidget->TimeFormat = "HH:MM"; //13:45 + break; + case 1: + pEditWidget->TimeFormat = "HH:MM:ss"; //13:45:00 + break; + case 2: + pEditWidget->TimeFormat = "h:MMtt"; //01:45 PM + break; + case 3: + pEditWidget->TimeFormat = "h:MM:sstt"; //01:45:00 PM + break; + } + } + } break; + case FormComponentType::DATEFIELD: + { + pEditWidget->Format = vcl::PDFWriter::Date; + + static constexpr OUString FM_PROP_DATEFORMAT = u"DateFormat"_ustr; + if ( xPSI->hasPropertyByName( FM_PROP_DATEFORMAT ) ) + { + sal_Int32 nDateFormat = 0; + if( ! (xModelProps->getPropertyValue( FM_PROP_DATEFORMAT ) >>= nDateFormat) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_DATEFORMAT); + + switch ( nDateFormat ) + { + case 0: + case 1: + pEditWidget->DateFormat = "mm/dd/yy"; // Standard (short) + break; + case 2: + case 3: + pEditWidget->DateFormat = "mm/dd/yyyy"; // Standard (long) + break; + case 4: + pEditWidget->DateFormat = "dd/mm/yy"; // DD/MM/YY + break; + case 5: + pEditWidget->DateFormat = "mm/dd/yy"; // MM/DD/YY + break; + case 6: + pEditWidget->DateFormat = "yy/mm/dd"; // YY/MM/DD + break; + case 7: + pEditWidget->DateFormat = "dd/mm/yyyy"; // DD/MM/YYYY + break; + case 8: + pEditWidget->DateFormat = "mm/dd/yyyy"; // MM/DD/YYYY + break; + case 9: + pEditWidget->DateFormat = "yyyy/mm/dd"; // YYYY/MM/DD + break; + case 10: + pEditWidget->DateFormat = "yy-mm-dd"; // YY-MM-DD + break; + case 11: + pEditWidget->DateFormat = "yyyy-mm-dd"; // YYYY-MM-DD + break; + } + } + } break; + } + } + + // buttons + if ( Descriptor->getType() == vcl::PDFWriter::PushButton ) + { + vcl::PDFWriter::PushButtonWidget* pButtonWidget = static_cast< vcl::PDFWriter::PushButtonWidget* >( Descriptor.get() ); + FormButtonType eButtonType = FormButtonType_PUSH; + if( ! (xModelProps->getPropertyValue("ButtonType") >>= eButtonType) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property ButtonType"); + static constexpr OUString FM_PROP_TARGET_URL = u"TargetURL"_ustr; + if ( eButtonType == FormButtonType_SUBMIT ) + { + // if a button is a submit button, then it uses the URL at its parent form + Reference< XChild > xChild( xModelProps, UNO_QUERY ); + Reference < XPropertySet > xParentProps; + if ( xChild.is() ) + xParentProps.set(xChild->getParent(), css::uno::UNO_QUERY); + if ( xParentProps.is() ) + { + Reference< XServiceInfo > xParentSI( xParentProps, UNO_QUERY ); + if ( xParentSI.is() && xParentSI->supportsService("com.sun.star.form.component.HTMLForm") ) + { + if( ! (xParentProps->getPropertyValue( FM_PROP_TARGET_URL ) >>= pButtonWidget->URL) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL); + pButtonWidget->Submit = true; + FormSubmitMethod eMethod = FormSubmitMethod_POST; + if( ! (xParentProps->getPropertyValue("SubmitMethod") >>= eMethod) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL); + pButtonWidget->SubmitGet = (eMethod == FormSubmitMethod_GET); + } + } + } + else if ( eButtonType == FormButtonType_URL ) + { + OUString sURL; + if( ! (xModelProps->getPropertyValue( FM_PROP_TARGET_URL ) >>= sURL) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_TARGET_URL); + const bool bDocumentLocalTarget = sURL.startsWith("#"); + if ( bDocumentLocalTarget ) + { + // Register the destination for future handling ... + pButtonWidget->Dest = i_pdfExportData.RegisterDest(); + + // and put it into the bookmarks, to ensure the future handling really happens + ::std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks( i_pdfExportData.GetBookmarks() ); + vcl::PDFExtOutDevBookmarkEntry aBookmark; + aBookmark.nDestId = pButtonWidget->Dest; + aBookmark.aBookmark = sURL; + rBookmarks.push_back( aBookmark ); + } + else + pButtonWidget->URL = sURL; + + pButtonWidget->Submit = false; + } + + // TODO: + // In PDF files, buttons are either reset, url or submit buttons. So if we have a simple PUSH button + // in a document, then this means that we do not export a SubmitToURL, which means that in PDF, + // the button is used as reset button. + // Is this desired? If no, we would have to reset Descriptor to NULL here, in case eButtonType + // != FormButtonType_SUBMIT && != FormButtonType_RESET + + // the PDF exporter defaults the text style, if 0. To prevent this, we have to transfer the UNO + // defaults to the PDF widget + if ( pButtonWidget->TextStyle == DrawTextFlags::NONE ) + pButtonWidget->TextStyle = DrawTextFlags::Center | DrawTextFlags::VCenter; + } + + + // check boxes + static constexpr OUString FM_PROP_STATE = u"State"_ustr; + if ( Descriptor->getType() == vcl::PDFWriter::CheckBox ) + { + vcl::PDFWriter::CheckBoxWidget* pCheckBoxWidget = static_cast< vcl::PDFWriter::CheckBoxWidget* >( Descriptor.get() ); + sal_Int16 nState = 0; + if( ! (xModelProps->getPropertyValue( FM_PROP_STATE ) >>= nState) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_STATE); + pCheckBoxWidget->Checked = ( nState != 0 ); + + try + { + xModelProps->getPropertyValue( "RefValue" ) >>= pCheckBoxWidget->OnValue; + } + catch(...) + { + } + + try + { + xModelProps->getPropertyValue( "SecondaryRefValue" ) >>= pCheckBoxWidget->OffValue; + } + catch(...) + { + } + } + + + // radio buttons + if ( Descriptor->getType() == vcl::PDFWriter::RadioButton ) + { + vcl::PDFWriter::RadioButtonWidget* pRadioWidget = static_cast< vcl::PDFWriter::RadioButtonWidget* >( Descriptor.get() ); + sal_Int16 nState = 0; + if( ! (xModelProps->getPropertyValue( FM_PROP_STATE ) >>= nState) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property " << FM_PROP_STATE); + pRadioWidget->Selected = ( nState != 0 ); + pRadioWidget->RadioGroup = determineRadioGroupId( xModelProps ); + try + { + xModelProps->getPropertyValue( "RefValue" ) >>= pRadioWidget->OnValue; + } + catch(...) + { + } + + try + { + xModelProps->getPropertyValue( "SecondaryRefValue" ) >>= pRadioWidget->OffValue; + } + catch(...) + { + } + } + + + // list boxes + if ( Descriptor->getType() == vcl::PDFWriter::ListBox ) + { + vcl::PDFWriter::ListBoxWidget* pListWidget = static_cast< vcl::PDFWriter::ListBoxWidget* >( Descriptor.get() ); + + // drop down + if( ! (xModelProps->getPropertyValue( "Dropdown" ) >>= pListWidget->DropDown) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property Dropdown"); + + // multi selection + if( ! (xModelProps->getPropertyValue("MultiSelection") >>= pListWidget->MultiSelect) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property MultiSelection"); + + // entries + getStringItemVector( xModelProps, pListWidget->Entries ); + + // get selected items + Sequence< sal_Int16 > aSelectIndices; + if( ! (xModelProps->getPropertyValue("SelectedItems") >>= aSelectIndices) ) + SAL_WARN("toolkit.helper", "describePDFControl: unable to get property SelectedItems"); + if( aSelectIndices.hasElements() ) + { + pListWidget->SelectedEntries.resize( 0 ); + auto nEntriesSize = static_cast<sal_Int16>(pListWidget->Entries.size()); + std::copy_if(std::cbegin(aSelectIndices), std::cend(aSelectIndices), std::back_inserter(pListWidget->SelectedEntries), + [&nEntriesSize](const sal_Int16 nIndex) { return nIndex >= 0 && nIndex < nEntriesSize; }); + } + } + + + // combo boxes + if ( Descriptor->getType() == vcl::PDFWriter::ComboBox ) + { + vcl::PDFWriter::ComboBoxWidget* pComboWidget = static_cast< vcl::PDFWriter::ComboBoxWidget* >( Descriptor.get() ); + + // entries + getStringItemVector( xModelProps, pComboWidget->Entries ); + } + + + // some post-processing + + // text line ends + // some controls may (always or dependent on other settings) return UNIX line ends + Descriptor->Text = convertLineEnd(Descriptor->Text, LINEEND_CRLF); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "toolkit", "describePDFControl" ); + } + return Descriptor; + } + + +} // namespace toolkitform + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/gdimetafiletools.cxx b/vcl/source/gdi/gdimetafiletools.cxx new file mode 100644 index 0000000000..95c42c5df7 --- /dev/null +++ b/vcl/source/gdi/gdimetafiletools.cxx @@ -0,0 +1,1086 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/gdimetafiletools.hxx> +#include <vcl/metaact.hxx> +#include <vcl/canvastools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/graphictools.hxx> +#include <osl/diagnose.h> +#include <tools/stream.hxx> + +// helpers + +namespace +{ + bool handleGeometricContent( + const basegfx::B2DPolyPolygon& rClip, + const basegfx::B2DPolyPolygon& rSource, + GDIMetaFile& rTarget, + bool bStroke) + { + if(rSource.count() && rClip.count()) + { + const basegfx::B2DPolyPolygon aResult( + basegfx::utils::clipPolyPolygonOnPolyPolygon( + rSource, + rClip, + true, // inside + bStroke)); + + if(aResult.count()) + { + if(aResult == rSource) + { + // not clipped, but inside. Add original + return false; + } + else + { + // add clipped geometry + if(bStroke) + { + for(auto const& rB2DPolygon : aResult) + { + rTarget.AddAction( + new MetaPolyLineAction( + tools::Polygon(rB2DPolygon))); + } + } + else + { + rTarget.AddAction( + new MetaPolyPolygonAction( + tools::PolyPolygon(aResult))); + } + } + } + } + + return true; + } + + bool handleGradientContent( + const basegfx::B2DPolyPolygon& rClip, + const basegfx::B2DPolyPolygon& rSource, + const Gradient& rGradient, + GDIMetaFile& rTarget) + { + if(rSource.count() && rClip.count()) + { + const basegfx::B2DPolyPolygon aResult( + basegfx::utils::clipPolyPolygonOnPolyPolygon( + rSource, + rClip, + true, // inside + false)); // stroke + + if(aResult.count()) + { + if(aResult == rSource) + { + // not clipped, but inside. Add original + return false; + } + else + { + // add clipped geometry + rTarget.AddAction( + new MetaGradientExAction( + tools::PolyPolygon(aResult), + rGradient)); + } + } + } + + return true; + } + + bool handleBitmapContent( + const basegfx::B2DPolyPolygon& rClip, + const Point& rPoint, + const Size& rSize, + const BitmapEx& rBitmapEx, + GDIMetaFile& rTarget) + { + if(!rSize.Width() || !rSize.Height() || rBitmapEx.IsEmpty()) + { + // bitmap or size is empty + return true; + } + + const basegfx::B2DRange aLogicBitmapRange( + rPoint.X(), rPoint.Y(), + rPoint.X() + rSize.Width(), rPoint.Y() + rSize.Height()); + const basegfx::B2DPolyPolygon aClipOfBitmap( + basegfx::utils::clipPolyPolygonOnRange( + rClip, + aLogicBitmapRange, + true, + false)); // stroke + + if(!aClipOfBitmap.count()) + { + // outside clip region + return true; + } + + // inside or overlapping. Use area to find out if it is completely + // covering (inside) or overlapping + const double fClipArea(basegfx::utils::getArea(aClipOfBitmap)); + const double fBitmapArea( + aLogicBitmapRange.getWidth() * aLogicBitmapRange.getWidth() + + aLogicBitmapRange.getHeight() * aLogicBitmapRange.getHeight()); + const double fFactor(fClipArea / fBitmapArea); + + if(basegfx::fTools::more(fFactor, 1.0 - 0.001)) + { + // completely covering (with 0.1% tolerance) + return false; + } + + // needs clipping (with 0.1% tolerance). Prepare VirtualDevice + // in pixel mode for alpha channel painting (black is transparent, + // white to paint 100% opacity) + const Size aSizePixel(rBitmapEx.GetSizePixel()); + ScopedVclPtrInstance< VirtualDevice > aVDev; + + aVDev->SetOutputSizePixel(aSizePixel); + aVDev->EnableMapMode(false); + aVDev->SetFillColor( COL_WHITE); + aVDev->SetLineColor(); + + if(rBitmapEx.IsAlpha()) + { + // use given alpha channel + aVDev->DrawBitmap(Point(0, 0), rBitmapEx.GetAlphaMask().GetBitmap()); + } + else + { + // reset alpha channel + aVDev->SetBackground(Wallpaper(COL_BLACK)); + aVDev->Erase(); + } + + // transform polygon from clipping to pixel coordinates + basegfx::B2DPolyPolygon aPixelPoly(aClipOfBitmap); + basegfx::B2DHomMatrix aTransform; + + aTransform.translate(-aLogicBitmapRange.getMinX(), -aLogicBitmapRange.getMinY()); + aTransform.scale( + static_cast< double >(aSizePixel.Width()) / aLogicBitmapRange.getWidth(), + static_cast< double >(aSizePixel.Height()) / aLogicBitmapRange.getHeight()); + aPixelPoly.transform(aTransform); + + // to fill the non-covered parts, use the Xor fill rule of + // tools::PolyPolygon painting. Start with an all-covering polygon and + // add the clip polygon one + basegfx::B2DPolyPolygon aInvertPixelPoly; + + aInvertPixelPoly.append( + basegfx::utils::createPolygonFromRect( + basegfx::B2DRange( + 0.0, 0.0, + aSizePixel.Width(), aSizePixel.Height()))); + aInvertPixelPoly.append(aPixelPoly); + + // paint as alpha + aVDev->DrawPolyPolygon(aInvertPixelPoly); + + // get created alpha mask and set defaults + AlphaMask aAlpha( + aVDev->GetBitmap( + Point(0, 0), + aSizePixel)); + + aAlpha.SetPrefSize(rBitmapEx.GetPrefSize()); + aAlpha.SetPrefMapMode(rBitmapEx.GetPrefMapMode()); + + // add new action replacing the old one + rTarget.AddAction( + new MetaBmpExScaleAction( + Point( + basegfx::fround(aLogicBitmapRange.getMinX()), + basegfx::fround(aLogicBitmapRange.getMinY())), + Size( + basegfx::fround(aLogicBitmapRange.getWidth()), + basegfx::fround(aLogicBitmapRange.getHeight())), + BitmapEx(rBitmapEx.GetBitmap(), aAlpha))); + + return true; + } + + void addSvtGraphicStroke(const SvtGraphicStroke& rStroke, GDIMetaFile& rTarget) + { + // write SvtGraphicFill + SvMemoryStream aMemStm; + WriteSvtGraphicStroke( aMemStm, rStroke ); + rTarget.AddAction( + new MetaCommentAction( + "XPATHSTROKE_SEQ_BEGIN"_ostr, + 0, + static_cast< const sal_uInt8* >(aMemStm.GetData()), + aMemStm.TellEnd())); + } + + void addSvtGraphicFill(const SvtGraphicFill &rFilling, GDIMetaFile& rTarget) + { + // write SvtGraphicFill + SvMemoryStream aMemStm; + WriteSvtGraphicFill( aMemStm, rFilling ); + rTarget.AddAction( + new MetaCommentAction( + "XPATHFILL_SEQ_BEGIN"_ostr, + 0, + static_cast< const sal_uInt8* >(aMemStm.GetData()), + aMemStm.TellEnd())); + } +} // end of anonymous namespace + +// #i121267# Tooling to internally clip geometry against internal clip regions + +void clipMetafileContentAgainstOwnRegions(GDIMetaFile& rSource) +{ + const sal_uLong nObjCount(rSource.GetActionSize()); + + if(!nObjCount) + { + return; + } + + // prepare target data container and push/pop stack data + GDIMetaFile aTarget; + bool bChanged(false); + std::vector< basegfx::B2DPolyPolygon > aClips; + std::vector< vcl::PushFlags > aPushFlags; + std::vector< MapMode > aMapModes; + + // start with empty region + aClips.emplace_back(); + + // start with default MapMode (MapUnit::MapPixel) + aMapModes.emplace_back(); + + for(sal_uLong i(0); i < nObjCount; ++i) + { + const MetaAction* pAction(rSource.GetAction(i)); + const MetaActionType nType(pAction->GetType()); + bool bDone(false); + + // basic operation takes care of clipregion actions (four) and push/pop of these + // to steer the currently set clip region. There *is* an active + // clip region when (aClips.size() && aClips.back().count()), see + // below + switch(nType) + { + case MetaActionType::CLIPREGION : + { + const MetaClipRegionAction* pA = static_cast< const MetaClipRegionAction* >(pAction); + + if(pA->IsClipping()) + { + const vcl::Region& rRegion = pA->GetRegion(); + const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon()); + + aClips.back() = aNewClip; + } + else + { + aClips.back() = basegfx::B2DPolyPolygon(); + } + + break; + } + + case MetaActionType::ISECTRECTCLIPREGION : + { + const MetaISectRectClipRegionAction* pA = static_cast< const MetaISectRectClipRegionAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(!rRect.IsEmpty() && !aClips.empty() && aClips.back().count()) + { + const basegfx::B2DRange aClipRange(vcl::unotools::b2DRectangleFromRectangle(rRect)); + + aClips.back() = basegfx::utils::clipPolyPolygonOnRange( + aClips.back(), + aClipRange, + true, // inside + false); // stroke + } + break; + } + + case MetaActionType::ISECTREGIONCLIPREGION : + { + const MetaISectRegionClipRegionAction* pA = static_cast< const MetaISectRegionClipRegionAction* >(pAction); + const vcl::Region& rRegion = pA->GetRegion(); + + if(!rRegion.IsEmpty() && !aClips.empty() && aClips.back().count()) + { + const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon()); + + aClips.back() = basegfx::utils::clipPolyPolygonOnPolyPolygon( + aClips.back(), + aNewClip, + true, // inside + false); // stroke + } + break; + } + + case MetaActionType::MOVECLIPREGION : + { + const MetaMoveClipRegionAction* pA = static_cast< const MetaMoveClipRegionAction* >(pAction); + const tools::Long aHorMove(pA->GetHorzMove()); + const tools::Long aVerMove(pA->GetVertMove()); + + if((aHorMove || aVerMove) && !aClips.empty() && aClips.back().count()) + { + aClips.back().transform( + basegfx::utils::createTranslateB2DHomMatrix( + aHorMove, + aVerMove)); + } + break; + } + + case MetaActionType::PUSH : + { + const MetaPushAction* pA = static_cast< const MetaPushAction* >(pAction); + const vcl::PushFlags nFlags(pA->GetFlags()); + + aPushFlags.push_back(nFlags); + + if(nFlags & vcl::PushFlags::CLIPREGION) + { + aClips.push_back(aClips.back()); + } + + if(nFlags & vcl::PushFlags::MAPMODE) + { + aMapModes.push_back(aMapModes.back()); + } + break; + } + + case MetaActionType::POP : + { + + if(!aPushFlags.empty()) + { + const vcl::PushFlags nFlags(aPushFlags.back()); + aPushFlags.pop_back(); + + if(nFlags & vcl::PushFlags::CLIPREGION) + { + if(aClips.size() > 1) + { + aClips.pop_back(); + } + else + { + OSL_ENSURE(false, "Wrong POP() in ClipRegions (!)"); + } + } + + if(nFlags & vcl::PushFlags::MAPMODE) + { + if(aMapModes.size() > 1) + { + aMapModes.pop_back(); + } + else + { + OSL_ENSURE(false, "Wrong POP() in MapModes (!)"); + } + } + } + else + { + OSL_ENSURE(false, "Invalid pop() without push() (!)"); + } + + break; + } + + case MetaActionType::MAPMODE : + { + const MetaMapModeAction* pA = static_cast< const MetaMapModeAction* >(pAction); + + aMapModes.back() = pA->GetMapMode(); + break; + } + + default: + { + break; + } + } + + // this area contains all actions which could potentially be clipped. Since + // this tooling is only a fallback (see comments in header), only the needed + // actions will be implemented. Extend using the pattern for the already + // implemented actions. + if(!aClips.empty() && aClips.back().count()) + { + switch(nType) + { + + // pixel actions, just check on inside + + case MetaActionType::PIXEL : + { + const MetaPixelAction* pA = static_cast< const MetaPixelAction* >(pAction); + const Point& rPoint = pA->GetPoint(); + + if(!basegfx::utils::isInside( + aClips.back(), + basegfx::B2DPoint(rPoint.X(), rPoint.Y()))) + { + // when not inside, do not add original + bDone = true; + } + break; + } + + case MetaActionType::POINT : + { + const MetaPointAction* pA = static_cast< const MetaPointAction* >(pAction); + const Point& rPoint = pA->GetPoint(); + + if(!basegfx::utils::isInside( + aClips.back(), + basegfx::B2DPoint(rPoint.X(), rPoint.Y()))) + { + // when not inside, do not add original + bDone = true; + } + break; + } + + // geometry actions + + case MetaActionType::LINE : + { + const MetaLineAction* pA = static_cast< const MetaLineAction* >(pAction); + const Point& rStart(pA->GetStartPoint()); + const Point& rEnd(pA->GetEndPoint()); + basegfx::B2DPolygon aLine; + + aLine.append(basegfx::B2DPoint(rStart.X(), rStart.Y())); + aLine.append(basegfx::B2DPoint(rEnd.X(), rEnd.Y())); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aLine), + aTarget, + true); // stroke + break; + } + + case MetaActionType::RECT : + { + const MetaRectAction* pA = static_cast< const MetaRectAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rRect))), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::ROUNDRECT : + { + const MetaRoundRectAction* pA = static_cast< const MetaRoundRectAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const sal_uInt32 nHor(pA->GetHorzRound()); + const sal_uInt32 nVer(pA->GetVertRound()); + const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect)); + basegfx::B2DPolygon aOutline; + + if(nHor || nVer) + { + double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0)); + double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0)); + fRadiusX = std::clamp(fRadiusX, 0.0, 1.0); + fRadiusY = std::clamp(fRadiusY, 0.0, 1.0); + + aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY); + } + else + { + aOutline = basegfx::utils::createPolygonFromRect(aRange); + } + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aOutline), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::ELLIPSE : + { + const MetaEllipseAction* pA = static_cast< const MetaEllipseAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect)); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromEllipse( + aRange.getCenter(), + aRange.getWidth() * 0.5, + aRange.getHeight() * 0.5)), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::ARC : + { + const MetaArcAction* pA = static_cast< const MetaArcAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const tools::Polygon aToolsPoly( + rRect, + pA->GetStartPoint(), + pA->GetEndPoint(), + PolyStyle::Arc); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()), + aTarget, + true); // stroke + } + break; + } + + case MetaActionType::PIE : + { + const MetaPieAction* pA = static_cast< const MetaPieAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const tools::Polygon aToolsPoly( + rRect, + pA->GetStartPoint(), + pA->GetEndPoint(), + PolyStyle::Pie); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::CHORD : + { + const MetaChordAction* pA = static_cast< const MetaChordAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const tools::Polygon aToolsPoly( + rRect, + pA->GetStartPoint(), + pA->GetEndPoint(), + PolyStyle::Chord); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::POLYLINE : + { + const MetaPolyLineAction* pA = static_cast< const MetaPolyLineAction* >(pAction); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()), + aTarget, + true); // stroke + break; + } + + case MetaActionType::POLYGON : + { + const MetaPolygonAction* pA = static_cast< const MetaPolygonAction* >(pAction); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()), + aTarget, + false); // stroke + break; + } + + case MetaActionType::POLYPOLYGON : + { + const MetaPolyPolygonAction* pA = static_cast< const MetaPolyPolygonAction* >(pAction); + const tools::PolyPolygon& rPoly = pA->GetPolyPolygon(); + + bDone = handleGeometricContent( + aClips.back(), + rPoly.getB2DPolyPolygon(), + aTarget, + false); // stroke + break; + } + + // bitmap actions, create BitmapEx with alpha channel derived + // from clipping + + case MetaActionType::BMPEX : + { + const MetaBmpExAction* pA = static_cast< const MetaBmpExAction* >(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + // the logical size depends on the PrefSize of the given bitmap in + // combination with the current MapMode + Size aLogicalSize(rBitmapEx.GetPrefSize()); + + if(MapUnit::MapPixel == rBitmapEx.GetPrefMapMode().GetMapUnit()) + { + aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back()); + } + else + { + aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmapEx.GetPrefMapMode(), aMapModes.back()); + } + + bDone = handleBitmapContent( + aClips.back(), + pA->GetPoint(), + aLogicalSize, + rBitmapEx, + aTarget); + break; + } + + case MetaActionType::BMP : + { + const MetaBmpAction* pA = static_cast< const MetaBmpAction* >(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + + // the logical size depends on the PrefSize of the given bitmap in + // combination with the current MapMode + Size aLogicalSize(rBitmap.GetPrefSize()); + + if(MapUnit::MapPixel == rBitmap.GetPrefMapMode().GetMapUnit()) + { + aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back()); + } + else + { + aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmap.GetPrefMapMode(), aMapModes.back()); + } + + bDone = handleBitmapContent( + aClips.back(), + pA->GetPoint(), + aLogicalSize, + BitmapEx(rBitmap), + aTarget); + break; + } + + case MetaActionType::BMPEXSCALE : + { + const MetaBmpExScaleAction* pA = static_cast< const MetaBmpExScaleAction* >(pAction); + + bDone = handleBitmapContent( + aClips.back(), + pA->GetPoint(), + pA->GetSize(), + pA->GetBitmapEx(), + aTarget); + break; + } + + case MetaActionType::BMPSCALE : + { + const MetaBmpScaleAction* pA = static_cast< const MetaBmpScaleAction* >(pAction); + + bDone = handleBitmapContent( + aClips.back(), + pA->GetPoint(), + pA->GetSize(), + BitmapEx(pA->GetBitmap()), + aTarget); + break; + } + + case MetaActionType::BMPEXSCALEPART : + { + const MetaBmpExScalePartAction* pA = static_cast< const MetaBmpExScalePartAction* >(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + if(rBitmapEx.IsEmpty()) + { + // empty content + bDone = true; + } + else + { + BitmapEx aCroppedBitmapEx(rBitmapEx); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(aCropRectangle.IsEmpty()) + { + // empty content + bDone = true; + } + else + { + aCroppedBitmapEx.Crop(aCropRectangle); + bDone = handleBitmapContent( + aClips.back(), + pA->GetDestPoint(), + pA->GetDestSize(), + aCroppedBitmapEx, + aTarget); + } + } + break; + } + + case MetaActionType::BMPSCALEPART : + { + const MetaBmpScalePartAction* pA = static_cast< const MetaBmpScalePartAction* >(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + + if(rBitmap.IsEmpty()) + { + // empty content + bDone = true; + } + else + { + Bitmap aCroppedBitmap(rBitmap); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(aCropRectangle.IsEmpty()) + { + // empty content + bDone = true; + } + else + { + aCroppedBitmap.Crop(aCropRectangle); + bDone = handleBitmapContent( + aClips.back(), + pA->GetDestPoint(), + pA->GetDestSize(), + BitmapEx(aCroppedBitmap), + aTarget); + } + } + break; + } + + // need to handle all those 'hacks' which hide data in comments + + case MetaActionType::COMMENT : + { + const MetaCommentAction* pA = static_cast< const MetaCommentAction* >(pAction); + const OString& rComment = pA->GetComment(); + + if(rComment.equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN")) + { + // nothing to do; this just means that between here and XGRAD_SEQ_END + // exists a MetaActionType::GRADIENTEX mixed with Xor-tricked painting + // commands. This comment is used to scan over these and filter for + // the gradient action. It is needed to support MetaActionType::GRADIENTEX + // in this processor to solve usages. + } + else if(rComment.equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN")) + { + SvtGraphicFill aFilling; + tools::PolyPolygon aPath; + + { // read SvtGraphicFill + SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ); + ReadSvtGraphicFill( aMemStm, aFilling ); + } + + aFilling.getPath(aPath); + + if(aPath.Count()) + { + const basegfx::B2DPolyPolygon aSource(aPath.getB2DPolyPolygon()); + const basegfx::B2DPolyPolygon aResult( + basegfx::utils::clipPolyPolygonOnPolyPolygon( + aSource, + aClips.back(), + true, // inside + false)); // stroke + + if(aResult.count()) + { + if(aResult != aSource) + { + // add clipped geometry + aFilling.setPath(tools::PolyPolygon(aResult)); + addSvtGraphicFill(aFilling, aTarget); + bDone = true; + } + } + else + { + // exchange with empty polygon + aFilling.setPath(tools::PolyPolygon()); + addSvtGraphicFill(aFilling, aTarget); + bDone = true; + } + } + } + else if(rComment.equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN")) + { + SvtGraphicStroke aStroke; + tools::Polygon aPath; + + { // read SvtGraphicFill + SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ); + ReadSvtGraphicStroke( aMemStm, aStroke ); + } + + aStroke.getPath(aPath); + + if(aPath.GetSize()) + { + const basegfx::B2DPolygon aSource(aPath.getB2DPolygon()); + const basegfx::B2DPolyPolygon aResult( + basegfx::utils::clipPolygonOnPolyPolygon( + aSource, + aClips.back(), + true, // inside + true)); // stroke + + if(aResult.count()) + { + if(aResult.count() > 1 || aResult.getB2DPolygon(0) != aSource) + { + // add clipped geometry + for(auto const& rB2DPolygon : aResult) + { + aStroke.setPath(tools::Polygon(rB2DPolygon)); + addSvtGraphicStroke(aStroke, aTarget); + } + + bDone = true; + } + } + else + { + // exchange with empty polygon + aStroke.setPath(tools::Polygon()); + addSvtGraphicStroke(aStroke, aTarget); + bDone = true; + } + + } + } + break; + } + + // need to handle gradient fills (hopefully only unrotated ones) + + case MetaActionType::GRADIENT : + { + const MetaGradientAction* pA = static_cast< const MetaGradientAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + bDone = handleGradientContent( + aClips.back(), + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rRect))), + pA->GetGradient(), + aTarget); + } + + break; + } + + case MetaActionType::GRADIENTEX : + { + const MetaGradientExAction* pA = static_cast< const MetaGradientExAction* >(pAction); + const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon(); + + bDone = handleGradientContent( + aClips.back(), + rPolyPoly.getB2DPolyPolygon(), + pA->GetGradient(), + aTarget); + break; + } + + // not (yet) supported actions + + // MetaActionType::NONE + // MetaActionType::TEXT + // MetaActionType::TEXTARRAY + // MetaActionType::STRETCHTEXT + // MetaActionType::TEXTRECT + // MetaActionType::MASK + // MetaActionType::MASKSCALE + // MetaActionType::MASKSCALEPART + // MetaActionType::HATCH + // MetaActionType::WALLPAPER + // MetaActionType::FILLCOLOR + // MetaActionType::TEXTCOLOR + // MetaActionType::TEXTFILLCOLOR + // MetaActionType::TEXTALIGN + // MetaActionType::MAPMODE + // MetaActionType::FONT + // MetaActionType::Transparent + // MetaActionType::EPS + // MetaActionType::REFPOINT + // MetaActionType::TEXTLINECOLOR + // MetaActionType::TEXTLINE + // MetaActionType::FLOATTRANSPARENT + // MetaActionType::LAYOUTMODE + // MetaActionType::TEXTLANGUAGE + // MetaActionType::OVERLINECOLOR + + // if an action is not handled at all, it will simply get copied to the + // target (see below). This is the default for all non-implemented actions + default: + { + break; + } + } + } + + if(bDone) + { + bChanged = true; + } + else + { + aTarget.AddAction(const_cast< MetaAction* >(pAction)); + } + } + + if(bChanged) + { + // when changed, copy back and do not forget to set MapMode + // and PrefSize + aTarget.SetPrefMapMode(rSource.GetPrefMapMode()); + aTarget.SetPrefSize(rSource.GetPrefSize()); + rSource = aTarget; + } +} + +bool usesClipActions(const GDIMetaFile& rSource) +{ + const sal_uLong nObjCount(rSource.GetActionSize()); + + for(sal_uLong i(0); i < nObjCount; ++i) + { + const MetaAction* pAction(rSource.GetAction(i)); + const MetaActionType nType(pAction->GetType()); + + switch(nType) + { + case MetaActionType::CLIPREGION : + case MetaActionType::ISECTRECTCLIPREGION : + case MetaActionType::ISECTREGIONCLIPREGION : + case MetaActionType::MOVECLIPREGION : + { + return true; + } + + default: break; + } + } + + return false; +} + +MetafileAccessor::~MetafileAccessor() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx new file mode 100644 index 0000000000..fb4ff37dfa --- /dev/null +++ b/vcl/source/gdi/gdimtf.cxx @@ -0,0 +1,2357 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cstdlib> +#include <memory> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> +#include <tools/helpers.hxx> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <tools/fract.hxx> +#include <vcl/BitmapPalette.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/window.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/graphictools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/mtfxmldump.hxx> + +#include <vcl/TypeSerializer.hxx> + +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <com/sun/star/rendering/MtfRenderer.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <comphelper/processfactory.hxx> + +using namespace com::sun::star; + +namespace { + +struct ImplColAdjustParam +{ + std::unique_ptr<sal_uInt8[]> pMapR; + std::unique_ptr<sal_uInt8[]> pMapG; + std::unique_ptr<sal_uInt8[]> pMapB; +}; + +struct ImplBmpAdjustParam +{ + short nLuminancePercent; + short nContrastPercent; + short nChannelRPercent; + short nChannelGPercent; + short nChannelBPercent; + double fGamma; + bool bInvert; +}; + +struct ImplColConvertParam +{ + MtfConversion eConversion; +}; + +struct ImplBmpConvertParam +{ + BmpConversion eConversion; +}; + +struct ImplColMonoParam +{ + Color aColor; +}; + +struct ImplBmpMonoParam +{ + Color aColor; +}; + +struct ImplColReplaceParam +{ + std::unique_ptr<sal_uLong[]> pMinR; + std::unique_ptr<sal_uLong[]> pMaxR; + std::unique_ptr<sal_uLong[]> pMinG; + std::unique_ptr<sal_uLong[]> pMaxG; + std::unique_ptr<sal_uLong[]> pMinB; + std::unique_ptr<sal_uLong[]> pMaxB; + const Color * pDstCols; + sal_uLong nCount; +}; + +struct ImplBmpReplaceParam +{ + const Color* pSrcCols; + const Color* pDstCols; + sal_uLong nCount; +}; + +} + +GDIMetaFile::GDIMetaFile() : + m_nCurrentActionElement( 0 ), + m_aPrefSize ( 1, 1 ), + m_pPrev ( nullptr ), + m_pNext ( nullptr ), + m_pOutDev ( nullptr ), + m_bPause ( false ), + m_bRecord ( false ), + m_bUseCanvas ( false ), + m_bSVG ( false ) +{ +} + +GDIMetaFile::GDIMetaFile( const GDIMetaFile& rMtf ) : + m_nCurrentActionElement( rMtf.m_nCurrentActionElement ), + m_aPrefMapMode ( rMtf.m_aPrefMapMode ), + m_aPrefSize ( rMtf.m_aPrefSize ), + m_pPrev ( rMtf.m_pPrev ), + m_pNext ( rMtf.m_pNext ), + m_pOutDev ( nullptr ), + m_bPause ( false ), + m_bRecord ( false ), + m_bUseCanvas ( rMtf.m_bUseCanvas ), + m_bSVG ( rMtf.m_bSVG ) +{ + for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i ) + { + m_aList.push_back( rMtf.GetAction( i ) ); + } + + if( rMtf.m_bRecord ) + { + Record( rMtf.m_pOutDev ); + + if ( rMtf.m_bPause ) + Pause( true ); + } +} + +GDIMetaFile::~GDIMetaFile() +{ + Clear(); +} + +bool GDIMetaFile::HasTransparentActions() const +{ + MetaAction* pCurrAct; + + // watch for transparent drawing actions + for(pCurrAct = const_cast<GDIMetaFile*>(this)->FirstAction(); + pCurrAct; + pCurrAct = const_cast<GDIMetaFile*>(this)->NextAction()) + { + // #i10613# determine if the action is transparency capable + + // #107169# Also examine metafiles with masked bitmaps in + // detail. Further down, this is optimized in such a way + // that there's no unnecessary painting of masked bitmaps + // (which are _always_ subdivided into rectangular regions + // of uniform opacity): if a masked bitmap is printed over + // empty background, we convert to a plain bitmap with + // white background. + if (pCurrAct->IsTransparent()) + return true; + } + + return false; +} + +size_t GDIMetaFile::GetActionSize() const +{ + return m_aList.size(); +} + +MetaAction* GDIMetaFile::GetAction( size_t nAction ) const +{ + return (nAction < m_aList.size()) ? m_aList[ nAction ].get() : nullptr; +} + +MetaAction* GDIMetaFile::FirstAction() +{ + m_nCurrentActionElement = 0; + return m_aList.empty() ? nullptr : m_aList[ 0 ].get(); +} + +MetaAction* GDIMetaFile::NextAction() +{ + return ( m_nCurrentActionElement + 1 < m_aList.size() ) ? m_aList[ ++m_nCurrentActionElement ].get() : nullptr; +} + +void GDIMetaFile::ReplaceAction( rtl::Reference<MetaAction> pAction, size_t nAction ) +{ + if ( nAction >= m_aList.size() ) + { + return; + } + //fdo#39995 This doesn't increment the incoming action ref-count nor does it + //decrement the outgoing action ref-count + std::swap(pAction, m_aList[nAction]); +} + +GDIMetaFile& GDIMetaFile::operator=( const GDIMetaFile& rMtf ) +{ + if( this != &rMtf ) + { + Clear(); + + // Increment RefCount of MetaActions + for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i ) + { + m_aList.push_back( rMtf.GetAction( i ) ); + } + + m_aPrefMapMode = rMtf.m_aPrefMapMode; + m_aPrefSize = rMtf.m_aPrefSize; + m_pPrev = rMtf.m_pPrev; + m_pNext = rMtf.m_pNext; + m_pOutDev = nullptr; + m_bPause = false; + m_bRecord = false; + m_bUseCanvas = rMtf.m_bUseCanvas; + m_bSVG = rMtf.m_bSVG; + + if( rMtf.m_bRecord ) + { + Record( rMtf.m_pOutDev ); + + if( rMtf.m_bPause ) + Pause( true ); + } + } + + return *this; +} + +bool GDIMetaFile::operator==( const GDIMetaFile& rMtf ) const +{ + const size_t nObjCount = m_aList.size(); + bool bRet = false; + + if( this == &rMtf ) + bRet = true; + else if( rMtf.GetActionSize() == nObjCount && + rMtf.GetPrefSize() == m_aPrefSize && + rMtf.GetPrefMapMode() == m_aPrefMapMode ) + { + bRet = true; + + for( size_t n = 0; n < nObjCount; n++ ) + { + if( m_aList[ n ] != rMtf.GetAction( n ) ) + { + bRet = false; + break; + } + } + } + + return bRet; +} + +void GDIMetaFile::Clear() +{ + if( m_bRecord ) + Stop(); + + m_aList.clear(); +} + +void GDIMetaFile::Linker( OutputDevice* pOut, bool bLink ) +{ + if( bLink ) + { + m_pNext = nullptr; + m_pPrev = pOut->GetConnectMetaFile(); + pOut->SetConnectMetaFile( this ); + + if( m_pPrev ) + m_pPrev->m_pNext = this; + } + else + { + if( m_pNext ) + { + m_pNext->m_pPrev = m_pPrev; + + if( m_pPrev ) + m_pPrev->m_pNext = m_pNext; + } + else + { + if( m_pPrev ) + m_pPrev->m_pNext = nullptr; + + pOut->SetConnectMetaFile( m_pPrev ); + } + + m_pPrev = nullptr; + m_pNext = nullptr; + } +} + +void GDIMetaFile::Record( OutputDevice* pOut ) +{ + if( m_bRecord ) + Stop(); + + m_nCurrentActionElement = m_aList.empty() ? 0 : (m_aList.size() - 1); + m_pOutDev = pOut; + m_bRecord = true; + Linker( pOut, true ); +} + +void GDIMetaFile::Play( GDIMetaFile& rMtf ) +{ + if (m_bRecord || rMtf.m_bRecord) + return; + + MetaAction* pAction = GetCurAction(); + const size_t nObjCount = m_aList.size(); + + rMtf.UseCanvas( rMtf.GetUseCanvas() || m_bUseCanvas ); + rMtf.setSVG( rMtf.getSVG() || m_bSVG ); + + for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nObjCount; nCurPos++ ) + { + if( pAction ) + { + rMtf.AddAction( pAction ); + } + + pAction = NextAction(); + } +} + +void GDIMetaFile::Play(OutputDevice& rOut, size_t nPos) +{ + if( m_bRecord ) + return; + + MetaAction* pAction = GetCurAction(); + const size_t nObjCount = m_aList.size(); + size_t nSyncCount = rOut.GetSyncCount(); + + if( nPos > nObjCount ) + nPos = nObjCount; + + // #i23407# Set backwards-compatible text language and layout mode + // This is necessary, since old metafiles don't even know of these + // recent add-ons. Newer metafiles must of course explicitly set + // those states. + rOut.Push(vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE); + rOut.SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + rOut.SetDigitLanguage(LANGUAGE_SYSTEM); + + SAL_INFO( "vcl.gdi", "GDIMetaFile::Play on device of size: " << rOut.GetOutputSizePixel().Width() << " " << rOut.GetOutputSizePixel().Height()); + + if (!ImplPlayWithRenderer(rOut, Point(0,0), rOut.GetOutputSize())) { + size_t i = 0; + for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nPos; nCurPos++ ) + { + if( pAction ) + { + pAction->Execute(&rOut); + + // flush output from time to time + if( i++ > nSyncCount ) + { + rOut.Flush(); + i = 0; + } + } + + pAction = NextAction(); + } + } + rOut.Pop(); +} + +bool GDIMetaFile::ImplPlayWithRenderer(OutputDevice& rOut, const Point& rPos, Size rLogicDestSize) +{ + if (!m_bUseCanvas) + return false; + + Size rDestSize(rOut.LogicToPixel(rLogicDestSize)); + + const vcl::Window* win = rOut.GetOwnerWindow(); + + if (!win) + win = Application::GetActiveTopWindow(); + if (!win) + win = Application::GetFirstTopLevelWindow(); + + if (!win) + return false; + + try + { + uno::Reference<rendering::XCanvas> xCanvas = win->GetOutDev()->GetCanvas (); + + if (!xCanvas.is()) + return false; + + Size aSize (rDestSize.Width () + 1, rDestSize.Height () + 1); + uno::Reference<rendering::XBitmap> xBitmap = xCanvas->getDevice ()->createCompatibleAlphaBitmap (vcl::unotools::integerSize2DFromSize( aSize)); + if( xBitmap.is () ) + { + uno::Reference< rendering::XBitmapCanvas > xBitmapCanvas( xBitmap, uno::UNO_QUERY ); + if( xBitmapCanvas.is() ) + { + uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + uno::Reference< rendering::XMtfRenderer > xMtfRenderer = rendering::MtfRenderer::createWithBitmapCanvas( xContext, xBitmapCanvas ); + + xBitmapCanvas->clear(); + uno::Reference< beans::XFastPropertySet > xMtfFastPropertySet( xMtfRenderer, uno::UNO_QUERY ); + if( xMtfFastPropertySet.is() ) + // set this metafile to the renderer to + // speedup things (instead of copying data to + // sequence of bytes passed to renderer) + xMtfFastPropertySet->setFastPropertyValue( 0, uno::Any( reinterpret_cast<sal_Int64>( this ) ) ); + + xMtfRenderer->draw( rDestSize.Width(), rDestSize.Height() ); + + BitmapEx aBitmapEx; + if( aBitmapEx.Create( xBitmapCanvas, aSize ) ) + { + if (rOut.GetMapMode().GetMapUnit() == MapUnit::MapPixel) + rOut.DrawBitmapEx( rPos, aBitmapEx ); + else + rOut.DrawBitmapEx( rPos, rLogicDestSize, aBitmapEx ); + return true; + } + } + } + } + catch (const uno::RuntimeException& ) + { + throw; // runtime errors are fatal + } + catch (const uno::Exception&) + { + // ignore errors, no way of reporting them here + TOOLS_WARN_EXCEPTION("vcl.gdi", "GDIMetaFile::ImplPlayWithRenderer"); + } + + return false; +} + +void GDIMetaFile::Play(OutputDevice& rOut, const Point& rPos, + const Size& rSize) +{ + MapMode aDrawMap( GetPrefMapMode() ); + Size aDestSize(rOut.LogicToPixel(rSize)); + + if (aDestSize.Width() <= 0 || aDestSize.Height() <= 0) + return; + + if (aDestSize.Width() > std::numeric_limits<sal_Int32>::max() || + aDestSize.Height() > std::numeric_limits<sal_Int32>::max()) + return; + + GDIMetaFile* pMtf = rOut.GetConnectMetaFile(); + + if (ImplPlayWithRenderer(rOut, rPos, rSize)) + return; + + Size aTmpPrefSize(rOut.LogicToPixel(GetPrefSize(), aDrawMap)); + + if( !aTmpPrefSize.Width() ) + aTmpPrefSize.setWidth( aDestSize.Width() ); + + if( !aTmpPrefSize.Height() ) + aTmpPrefSize.setHeight( aDestSize.Height() ); + + Fraction aScaleX( aDestSize.Width(), aTmpPrefSize.Width() ); + Fraction aScaleY( aDestSize.Height(), aTmpPrefSize.Height() ); + + aScaleX *= aDrawMap.GetScaleX(); + aScaleY *= aDrawMap.GetScaleY(); + // try reducing inaccurary first and abandon if the scaling + // still cannot be achieved + if (TooLargeScaleForMapMode(aScaleX, rOut.GetDPIX())) + aScaleX.ReduceInaccurate(10); + if (TooLargeScaleForMapMode(aScaleY, rOut.GetDPIY())) + aScaleY.ReduceInaccurate(10); + if (TooLargeScaleForMapMode(aScaleX, rOut.GetDPIX()) || + TooLargeScaleForMapMode(aScaleY, rOut.GetDPIY())) + { + SAL_WARN("vcl", "GDIMetaFile Scaling is too high"); + return; + } + + aDrawMap.SetScaleX(aScaleX); + aDrawMap.SetScaleY(aScaleY); + + // #i47260# Convert logical output position to offset within + // the metafile's mapmode. Therefore, disable pixel offset on + // outdev, it's inverse mnOutOffLogicX/Y is calculated for a + // different mapmode (the one currently set on rOut, that is) + // - thus, aDrawMap's origin would generally be wrong. And + // even _if_ aDrawMap is similar to pOutDev's current mapmode, + // it's _still_ undesirable to have pixel offset unequal zero, + // because one would still get round-off errors (the + // round-trip error for LogicToPixel( PixelToLogic() ) was the + // reason for having pixel offset in the first place). + const Size& rOldOffset(rOut.GetPixelOffset()); + const Size aEmptySize; + rOut.SetPixelOffset(aEmptySize); + aDrawMap.SetOrigin(rOut.PixelToLogic(rOut.LogicToPixel(rPos), aDrawMap)); + rOut.SetPixelOffset(rOldOffset); + + rOut.Push(); + + bool bIsRecord = (pMtf && pMtf->IsRecord()); + rOut.SetMetafileMapMode(aDrawMap, bIsRecord); + + // #i23407# Set backwards-compatible text language and layout mode + // This is necessary, since old metafiles don't even know of these + // recent add-ons. Newer metafiles must of course explicitly set + // those states. + rOut.SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + rOut.SetDigitLanguage(LANGUAGE_SYSTEM); + + Play(rOut); + + rOut.Pop(); +} + +void GDIMetaFile::Pause( bool _bPause ) +{ + if( !m_bRecord ) + return; + + if( _bPause ) + { + if( !m_bPause ) + Linker( m_pOutDev, false ); + } + else + { + if( m_bPause ) + Linker( m_pOutDev, true ); + } + + m_bPause = _bPause; +} + +void GDIMetaFile::Stop() +{ + if( m_bRecord ) + { + m_bRecord = false; + + if( !m_bPause ) + Linker( m_pOutDev, false ); + else + m_bPause = false; + } +} + +void GDIMetaFile::WindStart() +{ + if( !m_bRecord ) + m_nCurrentActionElement = 0; +} + +void GDIMetaFile::WindPrev() +{ + if( !m_bRecord ) + if ( m_nCurrentActionElement > 0 ) + --m_nCurrentActionElement; +} + +void GDIMetaFile::AddAction(const rtl::Reference<MetaAction>& pAction) +{ + m_aList.push_back( pAction ); + + if( m_pPrev ) + { + m_pPrev->AddAction( pAction ); + } +} + +void GDIMetaFile::AddAction(const rtl::Reference<MetaAction>& pAction, size_t nPos) +{ + if ( nPos < m_aList.size() ) + { + m_aList.insert( m_aList.begin() + nPos, pAction ); + } + else + { + m_aList.push_back( pAction ); + } + + if( m_pPrev ) + { + m_pPrev->AddAction( pAction, nPos ); + } +} + +void GDIMetaFile::push_back(const rtl::Reference<MetaAction>& pAction) +{ + m_aList.push_back( pAction ); +} + +void GDIMetaFile::Mirror( BmpMirrorFlags nMirrorFlags ) +{ + const Size aOldPrefSize( GetPrefSize() ); + tools::Long nMoveX, nMoveY; + double fScaleX, fScaleY; + + if( nMirrorFlags & BmpMirrorFlags::Horizontal ) + { + nMoveX = std::abs( aOldPrefSize.Width() ) - 1; + fScaleX = -1.0; + } + else + { + nMoveX = 0; + fScaleX = 1.0; + } + + if( nMirrorFlags & BmpMirrorFlags::Vertical ) + { + nMoveY = std::abs( aOldPrefSize.Height() ) - 1; + fScaleY = -1.0; + } + else + { + nMoveY = 0; + fScaleY = 1.0; + } + + if( ( fScaleX != 1.0 ) || ( fScaleY != 1.0 ) ) + { + Scale( fScaleX, fScaleY ); + Move( nMoveX, nMoveY ); + SetPrefSize( aOldPrefSize ); + } +} + +void GDIMetaFile::Move( tools::Long nX, tools::Long nY ) +{ + const Size aBaseOffset( nX, nY ); + Size aOffset( aBaseOffset ); + ScopedVclPtrInstance< VirtualDevice > aMapVDev; + + aMapVDev->EnableOutput( false ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() ) + { + const MetaActionType nType = pAct->GetType(); + MetaAction* pModAct; + + if( pAct->GetRefCount() > 1 ) + { + m_aList[ m_nCurrentActionElement ] = pAct->Clone(); + pModAct = m_aList[ m_nCurrentActionElement ].get(); + } + else + pModAct = pAct; + + if( ( MetaActionType::MAPMODE == nType ) || + ( MetaActionType::PUSH == nType ) || + ( MetaActionType::POP == nType ) ) + { + pModAct->Execute( aMapVDev.get() ); + aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() ); + } + + pModAct->Move( aOffset.Width(), aOffset.Height() ); + } +} + +void GDIMetaFile::Move( tools::Long nX, tools::Long nY, tools::Long nDPIX, tools::Long nDPIY ) +{ + const Size aBaseOffset( nX, nY ); + Size aOffset( aBaseOffset ); + ScopedVclPtrInstance< VirtualDevice > aMapVDev; + + aMapVDev->EnableOutput( false ); + aMapVDev->SetReferenceDevice( nDPIX, nDPIY ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() ) + { + const MetaActionType nType = pAct->GetType(); + MetaAction* pModAct; + + if( pAct->GetRefCount() > 1 ) + { + m_aList[ m_nCurrentActionElement ] = pAct->Clone(); + pModAct = m_aList[ m_nCurrentActionElement ].get(); + } + else + pModAct = pAct; + + if( ( MetaActionType::MAPMODE == nType ) || + ( MetaActionType::PUSH == nType ) || + ( MetaActionType::POP == nType ) ) + { + pModAct->Execute( aMapVDev.get() ); + if( aMapVDev->GetMapMode().GetMapUnit() == MapUnit::MapPixel ) + { + aOffset = aMapVDev->LogicToPixel( aBaseOffset, GetPrefMapMode() ); + MapMode aMap( aMapVDev->GetMapMode() ); + aOffset.setWidth( static_cast<tools::Long>(aOffset.Width() * static_cast<double>(aMap.GetScaleX())) ); + aOffset.setHeight( static_cast<tools::Long>(aOffset.Height() * static_cast<double>(aMap.GetScaleY())) ); + } + else + aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() ); + } + + pModAct->Move( aOffset.Width(), aOffset.Height() ); + } +} + +void GDIMetaFile::ScaleActions(double const fScaleX, double const fScaleY) +{ + for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() ) + { + MetaAction* pModAct; + + if( pAct->GetRefCount() > 1 ) + { + m_aList[ m_nCurrentActionElement ] = pAct->Clone(); + pModAct = m_aList[ m_nCurrentActionElement ].get(); + } + else + pModAct = pAct; + + pModAct->Scale( fScaleX, fScaleY ); + } +} + +void GDIMetaFile::Scale( double fScaleX, double fScaleY ) +{ + ScaleActions(fScaleX, fScaleY); + + m_aPrefSize.setWidth( FRound( m_aPrefSize.Width() * fScaleX ) ); + m_aPrefSize.setHeight( FRound( m_aPrefSize.Height() * fScaleY ) ); +} + +void GDIMetaFile::Scale( const Fraction& rScaleX, const Fraction& rScaleY ) +{ + Scale( static_cast<double>(rScaleX), static_cast<double>(rScaleY) ); +} + +void GDIMetaFile::Clip( const tools::Rectangle& i_rClipRect ) +{ + tools::Rectangle aCurRect( i_rClipRect ); + ScopedVclPtrInstance< VirtualDevice > aMapVDev; + + aMapVDev->EnableOutput( false ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() ) + { + const MetaActionType nType = pAct->GetType(); + + if( ( MetaActionType::MAPMODE == nType ) || + ( MetaActionType::PUSH == nType ) || + ( MetaActionType::POP == nType ) ) + { + pAct->Execute( aMapVDev.get() ); + aCurRect = OutputDevice::LogicToLogic( i_rClipRect, GetPrefMapMode(), aMapVDev->GetMapMode() ); + } + else if( nType == MetaActionType::CLIPREGION ) + { + MetaClipRegionAction* pOldAct = static_cast<MetaClipRegionAction*>(pAct); + vcl::Region aNewReg( aCurRect ); + if( pOldAct->IsClipping() ) + aNewReg.Intersect( pOldAct->GetRegion() ); + MetaClipRegionAction* pNewAct = new MetaClipRegionAction( std::move(aNewReg), true ); + m_aList[ m_nCurrentActionElement ] = pNewAct; + } + } +} + +Point GDIMetaFile::ImplGetRotatedPoint( const Point& rPt, const Point& rRotatePt, + const Size& rOffset, double fSin, double fCos ) +{ + const tools::Long nX = rPt.X() - rRotatePt.X(); + const tools::Long nY = rPt.Y() - rRotatePt.Y(); + + return Point( FRound( fCos * nX + fSin * nY ) + rRotatePt.X() + rOffset.Width(), + -FRound( fSin * nX - fCos * nY ) + rRotatePt.Y() + rOffset.Height() ); +} + +tools::Polygon GDIMetaFile::ImplGetRotatedPolygon( const tools::Polygon& rPoly, const Point& rRotatePt, + const Size& rOffset, double fSin, double fCos ) +{ + tools::Polygon aRet( rPoly ); + + aRet.Rotate( rRotatePt, fSin, fCos ); + aRet.Move( rOffset.Width(), rOffset.Height() ); + + return aRet; +} + +tools::PolyPolygon GDIMetaFile::ImplGetRotatedPolyPolygon( const tools::PolyPolygon& rPolyPoly, const Point& rRotatePt, + const Size& rOffset, double fSin, double fCos ) +{ + tools::PolyPolygon aRet( rPolyPoly ); + + aRet.Rotate( rRotatePt, fSin, fCos ); + aRet.Move( rOffset.Width(), rOffset.Height() ); + + return aRet; +} + +void GDIMetaFile::ImplAddGradientEx( GDIMetaFile& rMtf, + const OutputDevice& rMapDev, + const tools::PolyPolygon& rPolyPoly, + const Gradient& rGrad ) +{ + // Generate comment, GradientEx and Gradient actions (within DrawGradient) + ScopedVclPtrInstance< VirtualDevice > aVDev(rMapDev, DeviceFormat::WITHOUT_ALPHA); + aVDev->EnableOutput( false ); + GDIMetaFile aGradMtf; + + aGradMtf.Record( aVDev.get() ); + aVDev->DrawGradient( rPolyPoly, rGrad ); + aGradMtf.Stop(); + + size_t i, nAct( aGradMtf.GetActionSize() ); + for( i=0; i < nAct; ++i ) + { + MetaAction* pMetaAct = aGradMtf.GetAction( i ); + rMtf.AddAction( pMetaAct ); + } +} + +void GDIMetaFile::Rotate( Degree10 nAngle10 ) +{ + nAngle10 %= 3600_deg10; + nAngle10 = ( nAngle10 < 0_deg10 ) ? ( Degree10(3599) + nAngle10 ) : nAngle10; + + if( !nAngle10 ) + return; + + GDIMetaFile aMtf; + ScopedVclPtrInstance< VirtualDevice > aMapVDev; + const double fAngle = toRadians(nAngle10); + const double fSin = sin( fAngle ); + const double fCos = cos( fAngle ); + tools::Rectangle aRect( Point(), GetPrefSize() ); + tools::Polygon aPoly( aRect ); + + aPoly.Rotate( Point(), fSin, fCos ); + + aMapVDev->EnableOutput( false ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + const tools::Rectangle aNewBound( aPoly.GetBoundRect() ); + + const Point aOrigin( GetPrefMapMode().GetOrigin().X(), GetPrefMapMode().GetOrigin().Y() ); + const Size aOffset( -aNewBound.Left(), -aNewBound.Top() ); + + Point aRotAnchor( aOrigin ); + Size aRotOffset( aOffset ); + + for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() ) + { + const MetaActionType nActionType = pAction->GetType(); + + switch( nActionType ) + { + case MetaActionType::PIXEL: + { + MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction); + aMtf.AddAction( new MetaPixelAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetColor() ) ); + } + break; + + case MetaActionType::POINT: + { + MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction); + aMtf.AddAction( new MetaPointAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::LINE: + { + MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction); + aMtf.AddAction( new MetaLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + ImplGetRotatedPoint( pAct->GetEndPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetLineInfo() ) ); + } + break; + + case MetaActionType::RECT: + { + MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction); + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::ROUNDRECT: + { + MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction); + const tools::Polygon aRoundRectPoly( pAct->GetRect(), pAct->GetHorzRound(), pAct->GetVertRound() ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aRoundRectPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::ELLIPSE: + { + MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction); + const tools::Polygon aEllipsePoly( pAct->GetRect().Center(), pAct->GetRect().GetWidth() >> 1, pAct->GetRect().GetHeight() >> 1 ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aEllipsePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::ARC: + { + MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction); + const tools::Polygon aArcPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Arc ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aArcPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::PIE: + { + MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction); + const tools::Polygon aPiePoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Pie ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aPiePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::CHORD: + { + MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction); + const tools::Polygon aChordPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Chord ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aChordPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::POLYLINE: + { + MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction); + aMtf.AddAction( new MetaPolyLineAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->GetLineInfo() ) ); + } + break; + + case MetaActionType::POLYGON: + { + MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction); + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::POLYPOLYGON: + { + MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction); + aMtf.AddAction( new MetaPolyPolygonAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::TEXT: + { + MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction); + aMtf.AddAction( new MetaTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) ); + } + break; + + case MetaActionType::TEXTARRAY: + { + MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction); + aMtf.AddAction( new MetaTextArrayAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetText(), pAct->GetDXArray(), pAct->GetKashidaArray(), pAct->GetIndex(), pAct->GetLen() ) ); + } + break; + + case MetaActionType::STRETCHTEXT: + { + MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction); + aMtf.AddAction( new MetaStretchTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetWidth(), pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) ); + } + break; + + case MetaActionType::TEXTLINE: + { + MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction); + aMtf.AddAction( new MetaTextLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetWidth(), pAct->GetStrikeout(), pAct->GetUnderline(), pAct->GetOverline() ) ); + } + break; + + case MetaActionType::BMPSCALE: + { + MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction); + tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() ); + BitmapEx aBmpEx( pAct->GetBitmap() ); + + aBmpEx.Rotate( nAngle10, COL_TRANSPARENT ); + aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), + aBmpEx ) ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction); + tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() ); + BitmapEx aBmpEx( pAct->GetBitmap() ); + + aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) ); + aBmpEx.Rotate( nAngle10, COL_TRANSPARENT ); + + aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction); + tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() ); + BitmapEx aBmpEx( pAct->GetBitmapEx() ); + + aBmpEx.Rotate( nAngle10, COL_TRANSPARENT ); + + aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction); + tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() ); + BitmapEx aBmpEx( pAct->GetBitmapEx() ); + + aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) ); + aBmpEx.Rotate( nAngle10, COL_TRANSPARENT ); + + aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) ); + } + break; + + case MetaActionType::GRADIENT: + { + MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction); + + ImplAddGradientEx( aMtf, *aMapVDev, + tools::PolyPolygon(ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor, aRotOffset, fSin, fCos )), + pAct->GetGradient() ); + } + break; + + case MetaActionType::GRADIENTEX: + { + MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction); + aMtf.AddAction( new MetaGradientExAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetGradient() ) ); + } + break; + + // Handle gradientex comment block correctly + case MetaActionType::COMMENT: + { + MetaCommentAction* pCommentAct = static_cast<MetaCommentAction*>(pAction); + if( pCommentAct->GetComment() == "XGRAD_SEQ_BEGIN" ) + { + int nBeginComments( 1 ); + pAction = NextAction(); + + // skip everything, except gradientex action + while( pAction ) + { + const MetaActionType nType = pAction->GetType(); + + if( MetaActionType::GRADIENTEX == nType ) + { + // Add rotated gradientex + MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction); + ImplAddGradientEx( aMtf, *aMapVDev, + ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetGradient() ); + } + else if( MetaActionType::COMMENT == nType) + { + MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pAction); + if( pAct->GetComment() == "XGRAD_SEQ_END" ) + { + // handle nested blocks + --nBeginComments; + + // gradientex comment block: end reached, done. + if( !nBeginComments ) + break; + } + else if( pAct->GetComment() == "XGRAD_SEQ_BEGIN" ) + { + // handle nested blocks + ++nBeginComments; + } + + } + + pAction =NextAction(); + } + } + else + { + bool bPathStroke = (pCommentAct->GetComment() == "XPATHSTROKE_SEQ_BEGIN"); + if ( bPathStroke || pCommentAct->GetComment() == "XPATHFILL_SEQ_BEGIN" ) + { + if ( pCommentAct->GetDataSize() ) + { + SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pCommentAct->GetData()), pCommentAct->GetDataSize(), StreamMode::READ ); + SvMemoryStream aDest; + if ( bPathStroke ) + { + SvtGraphicStroke aStroke; + ReadSvtGraphicStroke( aMemStm, aStroke ); + tools::Polygon aPath; + aStroke.getPath( aPath ); + aStroke.setPath( ImplGetRotatedPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) ); + WriteSvtGraphicStroke( aDest, aStroke ); + aMtf.AddAction( new MetaCommentAction( "XPATHSTROKE_SEQ_BEGIN"_ostr, 0, + static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) ); + } + else + { + SvtGraphicFill aFill; + ReadSvtGraphicFill( aMemStm, aFill ); + tools::PolyPolygon aPath; + aFill.getPath( aPath ); + aFill.setPath( ImplGetRotatedPolyPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) ); + WriteSvtGraphicFill( aDest, aFill ); + aMtf.AddAction( new MetaCommentAction( "XPATHFILL_SEQ_BEGIN"_ostr, 0, + static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) ); + } + } + } + else if ( pCommentAct->GetComment() == "XPATHSTROKE_SEQ_END" + || pCommentAct->GetComment() == "XPATHFILL_SEQ_END" ) + { + pAction->Execute( aMapVDev.get() ); + aMtf.AddAction( pAction ); + } + } + } + break; + + case MetaActionType::HATCH: + { + MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction); + Hatch aHatch( pAct->GetHatch() ); + + aHatch.SetAngle( aHatch.GetAngle() + nAngle10 ); + aMtf.AddAction( new MetaHatchAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), + aHatch ) ); + } + break; + + case MetaActionType::Transparent: + { + MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction); + aMtf.AddAction( new MetaTransparentAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetTransparence() ) ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction); + GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() ); + tools::Polygon aMtfPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aMtfRect( aMtfPoly.GetBoundRect() ); + + aTransMtf.Rotate( nAngle10 ); + aMtf.AddAction( new MetaFloatTransparentAction( aTransMtf, aMtfRect.TopLeft(), aMtfRect.GetSize(), + pAct->GetGradient() ) ); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction); + GDIMetaFile aEPSMtf( pAct->GetSubstitute() ); + tools::Polygon aEPSPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aEPSRect( aEPSPoly.GetBoundRect() ); + + aEPSMtf.Rotate( nAngle10 ); + aMtf.AddAction( new MetaEPSAction( aEPSRect.TopLeft(), aEPSRect.GetSize(), + pAct->GetLink(), aEPSMtf ) ); + } + break; + + case MetaActionType::CLIPREGION: + { + MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction); + + if( pAct->IsClipping() && pAct->GetRegion().HasPolyPolygonOrB2DPolyPolygon() ) + aMtf.AddAction( new MetaClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( pAct->GetRegion().GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ), true ) ); + else + { + aMtf.AddAction( pAction ); + } + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction); + aMtf.AddAction( new MetaISectRegionClipRegionAction(vcl::Region( + ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor, + aRotOffset, fSin, fCos )) ) ); + } + break; + + case MetaActionType::ISECTREGIONCLIPREGION: + { + MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction); + const vcl::Region& rRegion = pAct->GetRegion(); + + if( rRegion.HasPolyPolygonOrB2DPolyPolygon() ) + aMtf.AddAction( new MetaISectRegionClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( rRegion.GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ) ); + else + { + aMtf.AddAction( pAction ); + } + } + break; + + case MetaActionType::REFPOINT: + { + MetaRefPointAction* pAct = static_cast<MetaRefPointAction*>(pAction); + aMtf.AddAction( new MetaRefPointAction( ImplGetRotatedPoint( pAct->GetRefPoint(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->IsSetting() ) ); + } + break; + + case MetaActionType::FONT: + { + MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction); + vcl::Font aFont( pAct->GetFont() ); + + aFont.SetOrientation( aFont.GetOrientation() + nAngle10 ); + aMtf.AddAction( new MetaFontAction( std::move(aFont) ) ); + } + break; + + case MetaActionType::BMP: + case MetaActionType::BMPEX: + case MetaActionType::MASK: + case MetaActionType::MASKSCALE: + case MetaActionType::MASKSCALEPART: + case MetaActionType::WALLPAPER: + case MetaActionType::TEXTRECT: + case MetaActionType::MOVECLIPREGION: + { + OSL_FAIL( "GDIMetaFile::Rotate(): unsupported action" ); + } + break; + + default: + { + pAction->Execute( aMapVDev.get() ); + aMtf.AddAction( pAction ); + + // update rotation point and offset, if necessary + if( ( MetaActionType::MAPMODE == nActionType ) || + ( MetaActionType::PUSH == nActionType ) || + ( MetaActionType::POP == nActionType ) ) + { + aRotAnchor = OutputDevice::LogicToLogic( aOrigin, m_aPrefMapMode, aMapVDev->GetMapMode() ); + aRotOffset = OutputDevice::LogicToLogic( aOffset, m_aPrefMapMode, aMapVDev->GetMapMode() ); + } + } + break; + } + } + + aMtf.m_aPrefMapMode = m_aPrefMapMode; + aMtf.m_aPrefSize = aNewBound.GetSize(); + + *this = aMtf; + +} + +static void ImplActionBounds( tools::Rectangle& o_rOutBounds, + const tools::Rectangle& i_rInBounds, + const std::vector<tools::Rectangle>& i_rClipStack ) +{ + tools::Rectangle aBounds( i_rInBounds ); + if( ! i_rInBounds.IsEmpty() && ! i_rClipStack.empty() && ! i_rClipStack.back().IsEmpty() ) + aBounds.Intersection( i_rClipStack.back() ); + if( aBounds.IsEmpty() ) + return; + + if( ! o_rOutBounds.IsEmpty() ) + o_rOutBounds.Union( aBounds ); + else + o_rOutBounds = aBounds; +} + +tools::Rectangle GDIMetaFile::GetBoundRect( OutputDevice& i_rReference ) const +{ + ScopedVclPtrInstance< VirtualDevice > aMapVDev( i_rReference ); + + aMapVDev->EnableOutput( false ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + std::vector<tools::Rectangle> aClipStack( 1, tools::Rectangle() ); + std::vector<vcl::PushFlags> aPushFlagStack; + + tools::Rectangle aBound; + const sal_uLong nCount(GetActionSize()); + + for(sal_uLong a(0); a < nCount; a++) + { + MetaAction* pAction = GetAction(a); + const MetaActionType nActionType = pAction->GetType(); + + switch( nActionType ) + { + case MetaActionType::PIXEL: + { + MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction); + ImplActionBounds( aBound, + tools::Rectangle( OutputDevice::LogicToLogic( pAct->GetPoint(), aMapVDev->GetMapMode(), GetPrefMapMode() ), + aMapVDev->PixelToLogic( Size( 1, 1 ), GetPrefMapMode() ) ), + aClipStack ); + } + break; + + case MetaActionType::POINT: + { + MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction); + ImplActionBounds( aBound, + tools::Rectangle( OutputDevice::LogicToLogic( pAct->GetPoint(), aMapVDev->GetMapMode(), GetPrefMapMode() ), + aMapVDev->PixelToLogic( Size( 1, 1 ), GetPrefMapMode() ) ), + aClipStack ); + } + break; + + case MetaActionType::LINE: + { + MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction); + Point aP1( pAct->GetStartPoint() ), aP2( pAct->GetEndPoint() ); + tools::Rectangle aRect( aP1, aP2 ); + aRect.Normalize(); + + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::RECT: + { + MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::ROUNDRECT: + { + MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::ELLIPSE: + { + MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::ARC: + { + MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction); + // FIXME: this is imprecise + // e.g. for small arcs the whole rectangle is WAY too large + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::PIE: + { + MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction); + // FIXME: this is imprecise + // e.g. for small arcs the whole rectangle is WAY too large + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::CHORD: + { + MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction); + // FIXME: this is imprecise + // e.g. for small arcs the whole rectangle is WAY too large + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::POLYLINE: + { + MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() ); + + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::POLYGON: + { + MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::POLYPOLYGON: + { + MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::TEXT: + { + MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction); + tools::Rectangle aRect; + // hdu said base = index + aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen() ); + Point aPt( pAct->GetPoint() ); + aRect.Move( aPt.X(), aPt.Y() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::TEXTARRAY: + { + MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction); + tools::Rectangle aRect; + // hdu said base = index + aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(), + 0, pAct->GetDXArray(), pAct->GetKashidaArray() ); + Point aPt( pAct->GetPoint() ); + aRect.Move( aPt.X(), aPt.Y() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::STRETCHTEXT: + { + MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction); + tools::Rectangle aRect; + // hdu said base = index + aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(), + pAct->GetWidth() ); + Point aPt( pAct->GetPoint() ); + aRect.Move( aPt.X(), aPt.Y() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::TEXTLINE: + { + MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction); + // measure a test string to get ascend and descent right + static constexpr OUStringLiteral pStr = u"\u00c4g"; + OUString aStr( pStr ); + + tools::Rectangle aRect; + aMapVDev->GetTextBoundRect( aRect, aStr, 0, 0, aStr.getLength() ); + Point aPt( pAct->GetStartPoint() ); + aRect.Move( aPt.X(), aPt.Y() ); + aRect.SetRight( aRect.Left() + pAct->GetWidth() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPSCALE: + { + MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction); + tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction); + tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::GRADIENT: + { + MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction); + tools::Rectangle aRect( pAct->GetRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::GRADIENTEX: + { + MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::COMMENT: + { + // nothing to do + }; + break; + + case MetaActionType::HATCH: + { + MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::Transparent: + { + MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction); + // MetaFloatTransparentAction is defined limiting its content Metafile + // to its geometry definition(Point, Size), so use these directly + const tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::CLIPREGION: + { + MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction); + if( pAct->IsClipping() ) + aClipStack.back() = OutputDevice::LogicToLogic( pAct->GetRegion().GetBoundRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ); + else + aClipStack.back() = tools::Rectangle(); + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction); + tools::Rectangle aRect( OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ) ); + if( aClipStack.back().IsEmpty() ) + aClipStack.back() = aRect; + else + aClipStack.back().Intersection( aRect ); + } + break; + + case MetaActionType::ISECTREGIONCLIPREGION: + { + MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction); + tools::Rectangle aRect( OutputDevice::LogicToLogic( pAct->GetRegion().GetBoundRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ) ); + if( aClipStack.back().IsEmpty() ) + aClipStack.back() = aRect; + else + aClipStack.back().Intersection( aRect ); + } + break; + + case MetaActionType::BMP: + { + MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmap().GetSizePixel() ) ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPEX: + { + MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmapEx().GetSizePixel() ) ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::MASK: + { + MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmap().GetSizePixel() ) ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::MASKSCALE: + { + MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction); + tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::MASKSCALEPART: + { + MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction); + tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::WALLPAPER: + { + MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction); + tools::Rectangle aRect( pAct->GetRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::TEXTRECT: + { + MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pAction); + tools::Rectangle aRect( pAct->GetRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::MOVECLIPREGION: + { + MetaMoveClipRegionAction* pAct = static_cast<MetaMoveClipRegionAction*>(pAction); + if( ! aClipStack.back().IsEmpty() ) + { + Size aDelta( pAct->GetHorzMove(), pAct->GetVertMove() ); + aDelta = OutputDevice::LogicToLogic( aDelta, aMapVDev->GetMapMode(), GetPrefMapMode() ); + aClipStack.back().Move( aDelta.Width(), aDelta.Width() ); + } + } + break; + + default: + { + pAction->Execute( aMapVDev.get() ); + + if( nActionType == MetaActionType::PUSH ) + { + MetaPushAction* pAct = static_cast<MetaPushAction*>(pAction); + aPushFlagStack.push_back( pAct->GetFlags() ); + if( aPushFlagStack.back() & vcl::PushFlags::CLIPREGION ) + { + tools::Rectangle aRect( aClipStack.back() ); + aClipStack.push_back( aRect ); + } + } + else if( nActionType == MetaActionType::POP ) + { + // sanity check + if( ! aPushFlagStack.empty() ) + { + if( aPushFlagStack.back() & vcl::PushFlags::CLIPREGION ) + { + if( aClipStack.size() > 1 ) + aClipStack.pop_back(); + } + aPushFlagStack.pop_back(); + } + } + } + break; + } + } + return aBound; +} + +Color GDIMetaFile::ImplColAdjustFnc( const Color& rColor, const void* pColParam ) +{ + return Color( ColorAlpha, rColor.GetAlpha(), + static_cast<const ImplColAdjustParam*>(pColParam)->pMapR[ rColor.GetRed() ], + static_cast<const ImplColAdjustParam*>(pColParam)->pMapG[ rColor.GetGreen() ], + static_cast<const ImplColAdjustParam*>(pColParam)->pMapB[ rColor.GetBlue() ] ); + +} + +BitmapEx GDIMetaFile::ImplBmpAdjustFnc( const BitmapEx& rBmpEx, const void* pBmpParam ) +{ + const ImplBmpAdjustParam* p = static_cast<const ImplBmpAdjustParam*>(pBmpParam); + BitmapEx aRet( rBmpEx ); + + aRet.Adjust( p->nLuminancePercent, p->nContrastPercent, + p->nChannelRPercent, p->nChannelGPercent, p->nChannelBPercent, + p->fGamma, p->bInvert ); + + return aRet; +} + +Color GDIMetaFile::ImplColConvertFnc( const Color& rColor, const void* pColParam ) +{ + sal_uInt8 cLum = rColor.GetLuminance(); + + if( MtfConversion::N1BitThreshold == static_cast<const ImplColConvertParam*>(pColParam)->eConversion ) + cLum = ( cLum < 128 ) ? 0 : 255; + + return Color( ColorAlpha, rColor.GetAlpha(), cLum, cLum, cLum ); +} + +BitmapEx GDIMetaFile::ImplBmpConvertFnc( const BitmapEx& rBmpEx, const void* pBmpParam ) +{ + BitmapEx aRet( rBmpEx ); + + aRet.Convert( static_cast<const ImplBmpConvertParam*>(pBmpParam)->eConversion ); + + return aRet; +} + +Color GDIMetaFile::ImplColMonoFnc( const Color&, const void* pColParam ) +{ + return static_cast<const ImplColMonoParam*>(pColParam)->aColor; +} + +BitmapEx GDIMetaFile::ImplBmpMonoFnc( const BitmapEx& rBmpEx, const void* pBmpParam ) +{ + BitmapPalette aPal( 3 ); + aPal[ 0 ] = COL_BLACK; + aPal[ 1 ] = COL_WHITE; + aPal[ 2 ] = static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor; + + Bitmap aBmp(rBmpEx.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPal); + aBmp.Erase( static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor ); + + if( rBmpEx.IsAlpha() ) + return BitmapEx( aBmp, rBmpEx.GetAlphaMask() ); + else + return BitmapEx( aBmp ); +} + +Color GDIMetaFile::ImplColReplaceFnc( const Color& rColor, const void* pColParam ) +{ + const sal_uLong nR = rColor.GetRed(), nG = rColor.GetGreen(), nB = rColor.GetBlue(); + + for( sal_uLong i = 0; i < static_cast<const ImplColReplaceParam*>(pColParam)->nCount; i++ ) + { + if( ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinR[ i ] <= nR ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxR[ i ] >= nR ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinG[ i ] <= nG ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxG[ i ] >= nG ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinB[ i ] <= nB ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxB[ i ] >= nB ) ) + { + return static_cast<const ImplColReplaceParam*>(pColParam)->pDstCols[ i ]; + } + } + + return rColor; +} + +BitmapEx GDIMetaFile::ImplBmpReplaceFnc( const BitmapEx& rBmpEx, const void* pBmpParam ) +{ + const ImplBmpReplaceParam* p = static_cast<const ImplBmpReplaceParam*>(pBmpParam); + BitmapEx aRet( rBmpEx ); + + aRet.Replace( p->pSrcCols, p->pDstCols, p->nCount ); + + return aRet; +} + +void GDIMetaFile::ImplExchangeColors( ColorExchangeFnc pFncCol, const void* pColParam, + BmpExchangeFnc pFncBmp, const void* pBmpParam ) +{ + GDIMetaFile aMtf; + + aMtf.m_aPrefSize = m_aPrefSize; + aMtf.m_aPrefMapMode = m_aPrefMapMode; + aMtf.m_bUseCanvas = m_bUseCanvas; + aMtf.m_bSVG = m_bSVG; + + for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() ) + { + const MetaActionType nType = pAction->GetType(); + + switch( nType ) + { + case MetaActionType::PIXEL: + { + MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction); + aMtf.push_back( new MetaPixelAction( pAct->GetPoint(), pFncCol( pAct->GetColor(), pColParam ) ) ); + } + break; + + case MetaActionType::LINECOLOR: + { + MetaLineColorAction* pAct = static_cast<MetaLineColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::FILLCOLOR: + { + MetaFillColorAction* pAct = static_cast<MetaFillColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::TEXTCOLOR: + { + MetaTextColorAction* pAct = static_cast<MetaTextColorAction*>(pAction); + aMtf.push_back( new MetaTextColorAction( pFncCol( pAct->GetColor(), pColParam ) ) ); + } + break; + + case MetaActionType::TEXTFILLCOLOR: + { + MetaTextFillColorAction* pAct = static_cast<MetaTextFillColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaTextFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::TEXTLINECOLOR: + { + MetaTextLineColorAction* pAct = static_cast<MetaTextLineColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaTextLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::OVERLINECOLOR: + { + MetaOverlineColorAction* pAct = static_cast<MetaOverlineColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaOverlineColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::FONT: + { + MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction); + vcl::Font aFont( pAct->GetFont() ); + + aFont.SetColor( pFncCol( aFont.GetColor(), pColParam ) ); + aFont.SetFillColor( pFncCol( aFont.GetFillColor(), pColParam ) ); + aMtf.push_back( new MetaFontAction( std::move(aFont) ) ); + } + break; + + case MetaActionType::WALLPAPER: + { + MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction); + Wallpaper aWall( pAct->GetWallpaper() ); + const tools::Rectangle& rRect = pAct->GetRect(); + + aWall.SetColor( pFncCol( aWall.GetColor(), pColParam ) ); + + if( aWall.IsBitmap() ) + aWall.SetBitmap( pFncBmp( aWall.GetBitmap(), pBmpParam ) ); + + if( aWall.IsGradient() ) + { + Gradient aGradient( aWall.GetGradient() ); + + aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) ); + aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) ); + aWall.SetGradient( aGradient ); + } + + aMtf.push_back( new MetaWallpaperAction( rRect, std::move(aWall) ) ); + } + break; + + case MetaActionType::BMP: + case MetaActionType::BMPEX: + case MetaActionType::MASK: + { + OSL_FAIL( "Don't use bitmap actions of this type in metafiles!" ); + } + break; + + case MetaActionType::BMPSCALE: + { + MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction); + aMtf.push_back( new MetaBmpScaleAction( pAct->GetPoint(), pAct->GetSize(), + pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() ) ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction); + aMtf.push_back( new MetaBmpScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(), + pAct->GetSrcPoint(), pAct->GetSrcSize(), + pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() ) + ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction); + aMtf.push_back( new MetaBmpExScaleAction( pAct->GetPoint(), pAct->GetSize(), + pFncBmp( pAct->GetBitmapEx(), pBmpParam ) ) + ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction); + aMtf.push_back( new MetaBmpExScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(), + pAct->GetSrcPoint(), pAct->GetSrcSize(), + pFncBmp( pAct->GetBitmapEx(), pBmpParam ) ) + ); + } + break; + + case MetaActionType::MASKSCALE: + { + MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction); + aMtf.push_back( new MetaMaskScaleAction( pAct->GetPoint(), pAct->GetSize(), + pAct->GetBitmap(), + pFncCol( pAct->GetColor(), pColParam ) ) + ); + } + break; + + case MetaActionType::MASKSCALEPART: + { + MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction); + aMtf.push_back( new MetaMaskScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(), + pAct->GetSrcPoint(), pAct->GetSrcSize(), + pAct->GetBitmap(), + pFncCol( pAct->GetColor(), pColParam ) ) + ); + } + break; + + case MetaActionType::GRADIENT: + { + MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction); + Gradient aGradient( pAct->GetGradient() ); + + aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) ); + aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) ); + aMtf.push_back( new MetaGradientAction( pAct->GetRect(), std::move(aGradient) ) ); + } + break; + + case MetaActionType::GRADIENTEX: + { + MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction); + Gradient aGradient( pAct->GetGradient() ); + + aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) ); + aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) ); + aMtf.push_back( new MetaGradientExAction( pAct->GetPolyPolygon(), std::move(aGradient) ) ); + } + break; + + case MetaActionType::HATCH: + { + MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction); + Hatch aHatch( pAct->GetHatch() ); + + aHatch.SetColor( pFncCol( aHatch.GetColor(), pColParam ) ); + aMtf.push_back( new MetaHatchAction( pAct->GetPolyPolygon(), aHatch ) ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction); + GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() ); + + aTransMtf.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam ); + aMtf.push_back( new MetaFloatTransparentAction( aTransMtf, + pAct->GetPoint(), pAct->GetSize(), + pAct->GetGradient() ) + ); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction); + GDIMetaFile aSubst( pAct->GetSubstitute() ); + + aSubst.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam ); + aMtf.push_back( new MetaEPSAction( pAct->GetPoint(), pAct->GetSize(), + pAct->GetLink(), aSubst ) + ); + } + break; + + default: + { + aMtf.push_back( pAction ); + } + break; + } + } + + *this = aMtf; +} + +void GDIMetaFile::Adjust( short nLuminancePercent, short nContrastPercent, + short nChannelRPercent, short nChannelGPercent, + short nChannelBPercent, double fGamma, bool bInvert, bool msoBrightness ) +{ + // nothing to do? => return quickly + if( !(nLuminancePercent || nContrastPercent || + nChannelRPercent || nChannelGPercent || nChannelBPercent || + ( fGamma != 1.0 ) || bInvert) ) + return; + + double fM, fROff, fGOff, fBOff, fOff; + ImplColAdjustParam aColParam; + ImplBmpAdjustParam aBmpParam; + + aColParam.pMapR.reset(new sal_uInt8[ 256 ]); + aColParam.pMapG.reset(new sal_uInt8[ 256 ]); + aColParam.pMapB.reset(new sal_uInt8[ 256 ]); + + // calculate slope + if( nContrastPercent >= 0 ) + fM = 128.0 / ( 128.0 - 1.27 * std::clamp( nContrastPercent, short(0), short(100) ) ); + else + fM = ( 128.0 + 1.27 * std::clamp( nContrastPercent, short(-100), short(0) ) ) / 128.0; + + if(!msoBrightness) + // total offset = luminance offset + contrast offset + fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55 + 128.0 - fM * 128.0; + else + fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55; + + // channel offset = channel offset + total offset + fROff = nChannelRPercent * 2.55 + fOff; + fGOff = nChannelGPercent * 2.55 + fOff; + fBOff = nChannelBPercent * 2.55 + fOff; + + // calculate gamma value + fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma ); + const bool bGamma = ( fGamma != 1.0 ); + + // create mapping table + for( tools::Long nX = 0; nX < 256; nX++ ) + { + if(!msoBrightness) + { + aColParam.pMapR[ nX ] = FRound(std::clamp( nX * fM + fROff, 0.0, 255.0 )); + aColParam.pMapG[ nX ] = FRound(std::clamp( nX * fM + fGOff, 0.0, 255.0 )); + aColParam.pMapB[ nX ] = FRound(std::clamp( nX * fM + fBOff, 0.0, 255.0 )); + } + else + { + aColParam.pMapR[ nX ] = FRound(std::clamp( (nX+fROff/2-128) * fM + 128 + fROff/2, 0.0, 255.0 )); + aColParam.pMapG[ nX ] = FRound(std::clamp( (nX+fGOff/2-128) * fM + 128 + fGOff/2, 0.0, 255.0 )); + aColParam.pMapB[ nX ] = FRound(std::clamp( (nX+fBOff/2-128) * fM + 128 + fBOff/2, 0.0, 255.0 )); + } + if( bGamma ) + { + aColParam.pMapR[ nX ] = GAMMA( aColParam.pMapR[ nX ], fGamma ); + aColParam.pMapG[ nX ] = GAMMA( aColParam.pMapG[ nX ], fGamma ); + aColParam.pMapB[ nX ] = GAMMA( aColParam.pMapB[ nX ], fGamma ); + } + + if( bInvert ) + { + aColParam.pMapR[ nX ] = ~aColParam.pMapR[ nX ]; + aColParam.pMapG[ nX ] = ~aColParam.pMapG[ nX ]; + aColParam.pMapB[ nX ] = ~aColParam.pMapB[ nX ]; + } + } + + aBmpParam.nLuminancePercent = nLuminancePercent; + aBmpParam.nContrastPercent = nContrastPercent; + aBmpParam.nChannelRPercent = nChannelRPercent; + aBmpParam.nChannelGPercent = nChannelGPercent; + aBmpParam.nChannelBPercent = nChannelBPercent; + aBmpParam.fGamma = fGamma; + aBmpParam.bInvert = bInvert; + + // do color adjustment + ImplExchangeColors( ImplColAdjustFnc, &aColParam, ImplBmpAdjustFnc, &aBmpParam ); +} + +void GDIMetaFile::Convert( MtfConversion eConversion ) +{ + ImplColConvertParam aColParam; + ImplBmpConvertParam aBmpParam; + + aColParam.eConversion = eConversion; + aBmpParam.eConversion = ( MtfConversion::N1BitThreshold == eConversion ) ? BmpConversion::N1BitThreshold : BmpConversion::N8BitGreys; + + ImplExchangeColors( ImplColConvertFnc, &aColParam, ImplBmpConvertFnc, &aBmpParam ); +} + +void GDIMetaFile::ReplaceColors( const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount ) +{ + ImplColReplaceParam aColParam; + ImplBmpReplaceParam aBmpParam; + + aColParam.pMinR.reset(new sal_uLong[ nColorCount ]); + aColParam.pMaxR.reset(new sal_uLong[ nColorCount ]); + aColParam.pMinG.reset(new sal_uLong[ nColorCount ]); + aColParam.pMaxG.reset(new sal_uLong[ nColorCount ]); + aColParam.pMinB.reset(new sal_uLong[ nColorCount ]); + aColParam.pMaxB.reset(new sal_uLong[ nColorCount ]); + + for( sal_uLong i = 0; i < nColorCount; i++ ) + { + tools::Long nVal; + + nVal = pSearchColors[ i ].GetRed(); + aColParam.pMinR[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) )); + aColParam.pMaxR[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) )); + + nVal = pSearchColors[ i ].GetGreen(); + aColParam.pMinG[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) )); + aColParam.pMaxG[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) )); + + nVal = pSearchColors[ i ].GetBlue(); + aColParam.pMinB[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) )); + aColParam.pMaxB[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) )); + } + + aColParam.pDstCols = pReplaceColors; + aColParam.nCount = nColorCount; + + aBmpParam.pSrcCols = pSearchColors; + aBmpParam.pDstCols = pReplaceColors; + aBmpParam.nCount = nColorCount; + + ImplExchangeColors( ImplColReplaceFnc, &aColParam, ImplBmpReplaceFnc, &aBmpParam ); +}; + +GDIMetaFile GDIMetaFile::GetMonochromeMtf( const Color& rColor ) const +{ + GDIMetaFile aRet( *this ); + + ImplColMonoParam aColParam; + ImplBmpMonoParam aBmpParam; + + aColParam.aColor = rColor; + aBmpParam.aColor = rColor; + + aRet.ImplExchangeColors( ImplColMonoFnc, &aColParam, ImplBmpMonoFnc, &aBmpParam ); + + return aRet; +} + +sal_uLong GDIMetaFile::GetSizeBytes() const +{ + sal_uLong nSizeBytes = 0; + + for( size_t i = 0, nObjCount = GetActionSize(); i < nObjCount; ++i ) + { + MetaAction* pAction = GetAction( i ); + + // default action size is set to 32 (=> not the exact value) + nSizeBytes += 32; + + // add sizes for large action content + switch( pAction->GetType() ) + { + case MetaActionType::BMP: nSizeBytes += static_cast<MetaBmpAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + case MetaActionType::BMPSCALE: nSizeBytes += static_cast<MetaBmpScaleAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + case MetaActionType::BMPSCALEPART: nSizeBytes += static_cast<MetaBmpScalePartAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + + case MetaActionType::BMPEX: nSizeBytes += static_cast<MetaBmpExAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break; + case MetaActionType::BMPEXSCALE: nSizeBytes += static_cast<MetaBmpExScaleAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break; + case MetaActionType::BMPEXSCALEPART: nSizeBytes += static_cast<MetaBmpExScalePartAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break; + + case MetaActionType::MASK: nSizeBytes += static_cast<MetaMaskAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + case MetaActionType::MASKSCALE: nSizeBytes += static_cast<MetaMaskScaleAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + case MetaActionType::MASKSCALEPART: nSizeBytes += static_cast<MetaMaskScalePartAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + + case MetaActionType::POLYLINE: nSizeBytes += static_cast<MetaPolyLineAction*>( pAction )->GetPolygon().GetSize() * sizeof( Point ); break; + case MetaActionType::POLYGON: nSizeBytes += static_cast<MetaPolygonAction*>( pAction )->GetPolygon().GetSize() * sizeof( Point ); break; + case MetaActionType::POLYPOLYGON: + { + const tools::PolyPolygon& rPolyPoly = static_cast<MetaPolyPolygonAction*>( pAction )->GetPolyPolygon(); + + for( sal_uInt16 n = 0; n < rPolyPoly.Count(); ++n ) + nSizeBytes += ( rPolyPoly[ n ].GetSize() * sizeof( Point ) ); + } + break; + + case MetaActionType::TEXT: nSizeBytes += static_cast<MetaTextAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break; + case MetaActionType::STRETCHTEXT: nSizeBytes += static_cast<MetaStretchTextAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break; + case MetaActionType::TEXTRECT: nSizeBytes += static_cast<MetaTextRectAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break; + case MetaActionType::TEXTARRAY: + { + MetaTextArrayAction* pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction); + + nSizeBytes += ( pTextArrayAction->GetText().getLength() * sizeof( sal_Unicode ) ); + + if( !pTextArrayAction->GetDXArray().empty() ) + nSizeBytes += ( pTextArrayAction->GetLen() << 2 ); + } + break; + default: break; + } + } + + return nSizeBytes; +} + +bool GDIMetaFile::CreateThumbnail(BitmapEx& rBitmapEx, BmpConversion eColorConversion, BmpScaleFlag nScaleFlag) const +{ + // initialization seems to be complicated but is used to avoid rounding errors + ScopedVclPtrInstance< VirtualDevice > aVDev; + // set Enable to tease the rendering down the code paths which use B2DPolygon and + // avoid integer overflows on scaling tools::Polygon, e.g. moz1545040-1.svg + // note: this is similar to DocumentToGraphicRenderer::renderToGraphic + aVDev->SetAntialiasing(AntialiasingFlags::Enable | aVDev->GetAntialiasing()); + const Point aNullPt; + const Point aTLPix( aVDev->LogicToPixel( aNullPt, GetPrefMapMode() ) ); + const Point aBRPix( aVDev->LogicToPixel( Point( GetPrefSize().Width() - 1, GetPrefSize().Height() - 1 ), GetPrefMapMode() ) ); + Size aDrawSize( aVDev->LogicToPixel( GetPrefSize(), GetPrefMapMode() ) ); + Size aSizePix( std::abs( aBRPix.X() - aTLPix.X() ) + 1, std::abs( aBRPix.Y() - aTLPix.Y() ) + 1 ); + sal_uInt32 nMaximumExtent = 512; + + if (!rBitmapEx.IsEmpty()) + rBitmapEx.SetEmpty(); + + // determine size that has the same aspect ratio as image size and + // fits into the rectangle determined by nMaximumExtent + if ( aSizePix.Width() && aSizePix.Height() + && ( sal::static_int_cast< tools::ULong >(aSizePix.Width()) > + nMaximumExtent || + sal::static_int_cast< tools::ULong >(aSizePix.Height()) > + nMaximumExtent ) ) + { + const Size aOldSizePix( aSizePix ); + double fWH = static_cast< double >( aSizePix.Width() ) / aSizePix.Height(); + + if ( fWH <= 1.0 ) + { + aSizePix.setWidth( FRound( nMaximumExtent * fWH ) ); + aSizePix.setHeight( nMaximumExtent ); + } + else + { + aSizePix.setWidth( nMaximumExtent ); + aSizePix.setHeight( FRound( nMaximumExtent / fWH ) ); + } + + aDrawSize.setWidth( FRound( ( static_cast< double >( aDrawSize.Width() ) * aSizePix.Width() ) / aOldSizePix.Width() ) ); + aDrawSize.setHeight( FRound( ( static_cast< double >( aDrawSize.Height() ) * aSizePix.Height() ) / aOldSizePix.Height() ) ); + } + + // draw image(s) into VDev and get resulting image + // do it 4x larger to be able to scale it down & get beautiful antialias + Size aAntialiasSize(aSizePix.Width() * 4, aSizePix.Height() * 4); + if (aVDev->SetOutputSizePixel(aAntialiasSize)) + { + // antialias: provide 4x larger size, and then scale down the result + Size aAntialias(aDrawSize.Width() * 4, aDrawSize.Height() * 4); + + // draw metafile into VDev + const_cast<GDIMetaFile *>(this)->WindStart(); + const_cast<GDIMetaFile *>(this)->Play(*aVDev, Point(), aAntialias); + + // get paint bitmap + BitmapEx aBitmap( aVDev->GetBitmapEx( aNullPt, aVDev->GetOutputSizePixel() ) ); + + // scale down the image to the desired size - use the input scaler for the scaling operation + aBitmap.Scale(aDrawSize, nScaleFlag); + + // convert to desired bitmap color format + Size aSize(aBitmap.GetSizePixel()); + if (aSize.Width() && aSize.Height()) + aBitmap.Convert(eColorConversion); + + rBitmapEx = aBitmap; + } + + return !rBitmapEx.IsEmpty(); +} + +void GDIMetaFile::UseCanvas( bool _bUseCanvas ) +{ + m_bUseCanvas = _bUseCanvas; +} + +void GDIMetaFile::dumpAsXml(const char* pFileName) const +{ + SvFileStream aStream(pFileName ? OUString::fromUtf8(pFileName) : OUString("file:///tmp/metafile.xml"), + StreamMode::STD_READWRITE | StreamMode::TRUNC); + assert(aStream.good()); + MetafileXmlDump aDumper; + aDumper.dump(*this, aStream); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/gfxlink.cxx b/vcl/source/gdi/gfxlink.cxx new file mode 100644 index 0000000000..c6ca4678b0 --- /dev/null +++ b/vcl/source/gdi/gfxlink.cxx @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <utility> +#include <vcl/graph.hxx> +#include <vcl/gfxlink.hxx> +#include <vcl/graphicfilter.hxx> +#include <memory> +#include <o3tl/hash_combine.hxx> + +GfxLink::GfxLink() + : meType(GfxLinkType::NONE) + , mnUserId(0) + , maHash(0) + , mbPrefMapModeValid(false) + , mbPrefSizeValid(false) +{ +} + +GfxLink::GfxLink(BinaryDataContainer aDataConainer, GfxLinkType nType) + : meType(nType) + , mnUserId(0) + , maDataContainer(std::move(aDataConainer)) + , maHash(0) + , mbPrefMapModeValid(false) + , mbPrefSizeValid(false) +{ +} + +size_t GfxLink::GetHash() const +{ + if (!maHash) + { + std::size_t seed = maDataContainer.calculateHash(); + o3tl::hash_combine(seed, meType); + maHash = seed; + } + return maHash; +} + +bool GfxLink::operator==( const GfxLink& rGfxLink ) const +{ + if (GetDataSize() != rGfxLink.GetDataSize() + || meType != rGfxLink.meType + || GetHash() != rGfxLink.GetHash()) + return false; + + const sal_uInt8* pSource = GetData(); + const sal_uInt8* pDestination = rGfxLink.GetData(); + + if (pSource == pDestination) + return true; + + sal_uInt32 nSourceSize = GetDataSize(); + sal_uInt32 nDestSize = rGfxLink.GetDataSize(); + + if (pSource && pDestination && (nSourceSize == nDestSize)) + return memcmp(pSource, pDestination, nSourceSize) == 0; + + return false; +} + +bool GfxLink::IsNative() const +{ + return meType >= GfxLinkType::NativeFirst && meType <= GfxLinkType::NativeLast; +} + +const sal_uInt8* GfxLink::GetData() const +{ + return maDataContainer.getData(); +} + +void GfxLink::SetPrefSize( const Size& rPrefSize ) +{ + maPrefSize = rPrefSize; + mbPrefSizeValid = true; +} + +void GfxLink::SetPrefMapMode( const MapMode& rPrefMapMode ) +{ + maPrefMapMode = rPrefMapMode; + mbPrefMapModeValid = true; +} + +bool GfxLink::LoadNative( Graphic& rGraphic ) const +{ + bool bRet = false; + + if (IsNative() && !maDataContainer.isEmpty()) + { + const sal_uInt8* pData = GetData(); + if (pData) + { + SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(pData), GetDataSize(), StreamMode::READ | StreamMode::WRITE); + OUString aShortName; + + switch (meType) + { + case GfxLinkType::NativeGif: aShortName = GIF_SHORTNAME; break; + case GfxLinkType::NativeJpg: aShortName = JPG_SHORTNAME; break; + case GfxLinkType::NativePng: aShortName = PNG_SHORTNAME; break; + case GfxLinkType::NativeTif: aShortName = TIF_SHORTNAME; break; + case GfxLinkType::NativeWmf: aShortName = WMF_SHORTNAME; break; + case GfxLinkType::NativeMet: aShortName = MET_SHORTNAME; break; + case GfxLinkType::NativePct: aShortName = PCT_SHORTNAME; break; + case GfxLinkType::NativeSvg: aShortName = SVG_SHORTNAME; break; + case GfxLinkType::NativeBmp: aShortName = BMP_SHORTNAME; break; + case GfxLinkType::NativePdf: aShortName = PDF_SHORTNAME; break; + case GfxLinkType::NativeWebp: aShortName = WEBP_SHORTNAME; break; + default: break; + } + if (!aShortName.isEmpty()) + { + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + sal_uInt16 nFormat = rFilter.GetImportFormatNumberForShortName(aShortName); + ErrCode nResult = rFilter.ImportGraphic(rGraphic, u"", aMemoryStream, nFormat); + if (nResult == ERRCODE_NONE) + bRet = true; + } + } + } + + return bRet; +} + +bool GfxLink::ExportNative( SvStream& rOStream ) const +{ + if( GetDataSize() ) + { + auto pData = GetData(); + if (pData) + rOStream.WriteBytes(pData, GetDataSize()); + } + + return ( rOStream.GetError() == ERRCODE_NONE ); +} + +bool GfxLink::IsEMF() const +{ + const sal_uInt8* pGraphicAry = GetData(); + if ((GetType() == GfxLinkType::NativeWmf) && pGraphicAry && (GetDataSize() > 0x2c)) + { + // check the magic number + if ((pGraphicAry[0x28] == 0x20) && (pGraphicAry[0x29] == 0x45) + && (pGraphicAry[0x2a] == 0x4d) && (pGraphicAry[0x2b] == 0x46)) + { + //emf detected + return true; + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/gradient.cxx b/vcl/source/gdi/gradient.cxx new file mode 100644 index 0000000000..75a53a2a93 --- /dev/null +++ b/vcl/source/gdi/gradient.cxx @@ -0,0 +1,675 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/gen.hxx> + +#include <vcl/gradient.hxx> +#include <vcl/metaact.hxx> +#include <cmath> + +class Gradient::Impl +{ +public: + css::awt::GradientStyle meStyle; + Color maStartColor; + Color maEndColor; + Degree10 mnAngle; + sal_uInt16 mnBorder; + sal_uInt16 mnOfsX; + sal_uInt16 mnOfsY; + sal_uInt16 mnIntensityStart; + sal_uInt16 mnIntensityEnd; + sal_uInt16 mnStepCount; + + Impl() + : meStyle (css::awt::GradientStyle_LINEAR) + , maStartColor(COL_BLACK) + , maEndColor(COL_WHITE) + , mnAngle(0) + , mnBorder(0) + , mnOfsX(50) + , mnOfsY(50) + , mnIntensityStart(100) + , mnIntensityEnd(100) + , mnStepCount(0) + { + } + + Impl(const Impl& rImplGradient) + : meStyle (rImplGradient.meStyle) + , maStartColor(rImplGradient.maStartColor) + , maEndColor(rImplGradient.maEndColor) + , mnAngle(rImplGradient.mnAngle) + , mnBorder(rImplGradient.mnBorder) + , mnOfsX(rImplGradient.mnOfsX) + , mnOfsY(rImplGradient.mnOfsY) + , mnIntensityStart(rImplGradient.mnIntensityStart) + , mnIntensityEnd(rImplGradient.mnIntensityEnd) + , mnStepCount(rImplGradient.mnStepCount) + { + } + + bool operator==(const Impl& rImpl_Gradient) const + { + return (meStyle == rImpl_Gradient.meStyle) + && (mnAngle == rImpl_Gradient.mnAngle) + && (mnBorder == rImpl_Gradient.mnBorder) + && (mnOfsX == rImpl_Gradient.mnOfsX) + && (mnOfsY == rImpl_Gradient.mnOfsY) + && (mnStepCount == rImpl_Gradient.mnStepCount) + && (mnIntensityStart == rImpl_Gradient.mnIntensityStart) + && (mnIntensityEnd == rImpl_Gradient.mnIntensityEnd) + && (maStartColor == rImpl_Gradient.maStartColor) + && (maEndColor == rImpl_Gradient.maEndColor); + } +}; + +Gradient::Gradient() = default; + +Gradient::Gradient( const Gradient& ) = default; + +Gradient::Gradient( Gradient&& ) = default; + +Gradient::Gradient( css::awt::GradientStyle eStyle, + const Color& rStartColor, const Color& rEndColor ) +{ + mpImplGradient->meStyle = eStyle; + mpImplGradient->maStartColor = rStartColor; + mpImplGradient->maEndColor = rEndColor; +} + +Gradient::~Gradient() = default; + + +css::awt::GradientStyle Gradient::GetStyle() const +{ + return mpImplGradient->meStyle; +} + +void Gradient::SetStyle( css::awt::GradientStyle eStyle ) +{ + mpImplGradient->meStyle = eStyle; +} + +const Color& Gradient::GetStartColor() const +{ + return mpImplGradient->maStartColor; +} + +void Gradient::SetStartColor( const Color& rColor ) +{ + mpImplGradient->maStartColor = rColor; +} + +const Color& Gradient::GetEndColor() const +{ + return mpImplGradient->maEndColor; +} + +void Gradient::SetEndColor( const Color& rColor ) +{ + mpImplGradient->maEndColor = rColor; +} + +Degree10 Gradient::GetAngle() const +{ + return mpImplGradient->mnAngle; +} + +void Gradient::SetAngle( Degree10 nAngle ) +{ + mpImplGradient->mnAngle = nAngle; +} + +sal_uInt16 Gradient::GetBorder() const +{ + return mpImplGradient->mnBorder; +} + +void Gradient::SetBorder( sal_uInt16 nBorder ) +{ + mpImplGradient->mnBorder = nBorder; +} + +sal_uInt16 Gradient::GetOfsX() const +{ + return mpImplGradient->mnOfsX; +} + +void Gradient::SetOfsX( sal_uInt16 nOfsX ) +{ + mpImplGradient->mnOfsX = nOfsX; +} + +sal_uInt16 Gradient::GetOfsY() const +{ + return mpImplGradient->mnOfsY; +} + +void Gradient::SetOfsY( sal_uInt16 nOfsY ) +{ + mpImplGradient->mnOfsY = nOfsY; +} + +sal_uInt16 Gradient::GetStartIntensity() const +{ + return mpImplGradient->mnIntensityStart; +} + +void Gradient::SetStartIntensity( sal_uInt16 nIntens ) +{ + mpImplGradient->mnIntensityStart = nIntens; +} + +sal_uInt16 Gradient::GetEndIntensity() const +{ + return mpImplGradient->mnIntensityEnd; +} + +void Gradient::SetEndIntensity( sal_uInt16 nIntens ) +{ + mpImplGradient->mnIntensityEnd = nIntens; +} + +sal_uInt16 Gradient::GetSteps() const +{ + return mpImplGradient->mnStepCount; +} + +void Gradient::SetSteps( sal_uInt16 nSteps ) +{ + mpImplGradient->mnStepCount = nSteps; +} + +void Gradient::GetBoundRect( const tools::Rectangle& rRect, tools::Rectangle& rBoundRect, Point& rCenter ) const +{ + tools::Rectangle aRect( rRect ); + Degree10 nAngle = GetAngle() % 3600_deg10; + + if( GetStyle() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::GradientStyle_AXIAL ) + { + const double fAngle = toRadians(nAngle); + const double fWidth = aRect.GetWidth(); + const double fHeight = aRect.GetHeight(); + double fDX = fWidth * fabs( cos( fAngle ) ) + + fHeight * fabs( sin( fAngle ) ); + double fDY = fHeight * fabs( cos( fAngle ) ) + + fWidth * fabs( sin( fAngle ) ); + fDX = (fDX - fWidth) * 0.5 + 0.5; + fDY = (fDY - fHeight) * 0.5 + 0.5; + aRect.AdjustLeft( -static_cast<tools::Long>(fDX) ); + aRect.AdjustRight(static_cast<tools::Long>(fDX) ); + aRect.AdjustTop( -static_cast<tools::Long>(fDY) ); + aRect.AdjustBottom(static_cast<tools::Long>(fDY) ); + + rBoundRect = aRect; + rCenter = rRect.Center(); + } + else + { + if( GetStyle() == css::awt::GradientStyle_SQUARE || GetStyle() == css::awt::GradientStyle_RECT ) + { + const double fAngle = toRadians(nAngle); + const double fWidth = aRect.GetWidth(); + const double fHeight = aRect.GetHeight(); + double fDX = fWidth * fabs( cos( fAngle ) ) + fHeight * fabs( sin( fAngle ) ); + double fDY = fHeight * fabs( cos( fAngle ) ) + fWidth * fabs( sin( fAngle ) ); + + fDX = ( fDX - fWidth ) * 0.5 + 0.5; + fDY = ( fDY - fHeight ) * 0.5 + 0.5; + + aRect.AdjustLeft( -static_cast<tools::Long>(fDX) ); + aRect.AdjustRight(static_cast<tools::Long>(fDX) ); + aRect.AdjustTop( -static_cast<tools::Long>(fDY) ); + aRect.AdjustBottom(static_cast<tools::Long>(fDY) ); + } + + Size aSize( aRect.GetSize() ); + + if( GetStyle() == css::awt::GradientStyle_RADIAL ) + { + // Calculation of radii for circle + aSize.setWidth( static_cast<tools::Long>(0.5 + std::hypot(aSize.Width(), aSize.Height())) ); + aSize.setHeight( aSize.Width() ); + } + else if( GetStyle() == css::awt::GradientStyle_ELLIPTICAL ) + { + // Calculation of radii for ellipse + aSize.setWidth( static_cast<tools::Long>( 0.5 + static_cast<double>(aSize.Width()) * M_SQRT2 ) ); + aSize.setHeight( static_cast<tools::Long>( 0.5 + static_cast<double>(aSize.Height()) * M_SQRT2) ); + } + + // Calculate new centers + tools::Long nZWidth = aRect.GetWidth() * static_cast<tools::Long>(GetOfsX()) / 100; + tools::Long nZHeight = aRect.GetHeight() * static_cast<tools::Long>(GetOfsY()) / 100; + tools::Long nBorderX = static_cast<tools::Long>(GetBorder()) * aSize.Width() / 100; + tools::Long nBorderY = static_cast<tools::Long>(GetBorder()) * aSize.Height() / 100; + rCenter = Point( aRect.Left() + nZWidth, aRect.Top() + nZHeight ); + + // Respect borders + aSize.AdjustWidth( -nBorderX ); + aSize.AdjustHeight( -nBorderY ); + + // Recalculate output rectangle + aRect.SetLeft( rCenter.X() - ( aSize.Width() >> 1 ) ); + aRect.SetTop( rCenter.Y() - ( aSize.Height() >> 1 ) ); + + aRect.SetSize( aSize ); + rBoundRect = aRect; + } +} + +void Gradient::MakeGrayscale() +{ + Color aStartCol(GetStartColor()); + Color aEndCol(GetEndColor()); + sal_uInt8 cStartLum = aStartCol.GetLuminance(); + sal_uInt8 cEndLum = aEndCol.GetLuminance(); + + aStartCol = Color(cStartLum, cStartLum, cStartLum); + aEndCol = Color(cEndLum, cEndLum, cEndLum); + + SetStartColor(aStartCol); + SetEndColor(aEndCol); +} + +Gradient& Gradient::operator=( const Gradient& ) = default; + +Gradient& Gradient::operator=( Gradient&& ) = default; + +bool Gradient::operator==( const Gradient& rGradient ) const +{ + return mpImplGradient == rGradient.mpImplGradient; +} + +const sal_uInt32 GRADIENT_DEFAULT_STEPCOUNT = 0; + +void Gradient::AddGradientActions(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) +{ + tools::Rectangle aRect(rRect); + aRect.Normalize(); + + // do nothing if the rectangle is empty + if (aRect.IsEmpty()) + return; + + rMetaFile.AddAction(new MetaPushAction(vcl::PushFlags::ALL)); + rMetaFile.AddAction(new MetaISectRectClipRegionAction( aRect)); + rMetaFile.AddAction(new MetaLineColorAction(Color(), false)); + + // because we draw with no border line, we have to expand gradient + // rect to avoid missing lines on the right and bottom edge + aRect.AdjustLeft( -1 ); + aRect.AdjustTop( -1 ); + aRect.AdjustRight( 1 ); + aRect.AdjustBottom( 1 ); + + // calculate step count if necessary + if (!GetSteps()) + SetSteps(GRADIENT_DEFAULT_STEPCOUNT); + + if (GetStyle() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::GradientStyle_AXIAL) + DrawLinearGradientToMetafile(aRect, rMetaFile); + else + DrawComplexGradientToMetafile(aRect, rMetaFile); + + rMetaFile.AddAction(new MetaPopAction()); +} + +tools::Long Gradient::GetMetafileSteps(tools::Rectangle const& rRect) const +{ + // calculate step count + tools::Long nStepCount = GetSteps(); + + if (nStepCount) + return nStepCount; + + if (GetStyle() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::GradientStyle_AXIAL) + return rRect.GetHeight(); + else + return std::min(rRect.GetWidth(), rRect.GetHeight()); +} + + +static sal_uInt8 GetGradientColorValue(tools::Long nValue) +{ + if ( nValue < 0 ) + return 0; + else if ( nValue > 0xFF ) + return 0xFF; + else + return static_cast<sal_uInt8>(nValue); +} + +void Gradient::DrawLinearGradientToMetafile(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const +{ + // get BoundRect of rotated rectangle + tools::Rectangle aRect; + Point aCenter; + Degree10 nAngle = GetAngle() % 3600_deg10; + + GetBoundRect(rRect, aRect, aCenter); + + bool bLinear = (GetStyle() == css::awt::GradientStyle_LINEAR); + double fBorder = GetBorder() * aRect.GetHeight() / 100.0; + if ( !bLinear ) + { + fBorder /= 2.0; + } + tools::Rectangle aMirrorRect = aRect; // used in style axial + aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 ); + if ( !bLinear ) + { + aRect.SetBottom( aMirrorRect.Top() ); + } + + // colour-intensities of start- and finish; change if needed + tools::Long nFactor; + Color aStartCol = GetStartColor(); + Color aEndCol = GetEndColor(); + tools::Long nStartRed = aStartCol.GetRed(); + tools::Long nStartGreen = aStartCol.GetGreen(); + tools::Long nStartBlue = aStartCol.GetBlue(); + tools::Long nEndRed = aEndCol.GetRed(); + tools::Long nEndGreen = aEndCol.GetGreen(); + tools::Long nEndBlue = aEndCol.GetBlue(); + nFactor = GetStartIntensity(); + nStartRed = (nStartRed * nFactor) / 100; + nStartGreen = (nStartGreen * nFactor) / 100; + nStartBlue = (nStartBlue * nFactor) / 100; + nFactor = GetEndIntensity(); + nEndRed = (nEndRed * nFactor) / 100; + nEndGreen = (nEndGreen * nFactor) / 100; + nEndBlue = (nEndBlue * nFactor) / 100; + + // gradient style axial has exchanged start and end colors + if ( !bLinear) + { + std::swap( nStartRed, nEndRed ); + std::swap( nStartGreen, nEndGreen ); + std::swap( nStartBlue, nEndBlue ); + } + + sal_uInt8 nRed; + sal_uInt8 nGreen; + sal_uInt8 nBlue; + + // Create border + tools::Rectangle aBorderRect = aRect; + tools::Polygon aPoly( 4 ); + if (fBorder > 0.0) + { + nRed = static_cast<sal_uInt8>(nStartRed); + nGreen = static_cast<sal_uInt8>(nStartGreen); + nBlue = static_cast<sal_uInt8>(nStartBlue); + + rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + + aBorderRect.SetBottom( static_cast<tools::Long>( aBorderRect.Top() + fBorder ) ); + aRect.SetTop( aBorderRect.Bottom() ); + aPoly[0] = aBorderRect.TopLeft(); + aPoly[1] = aBorderRect.TopRight(); + aPoly[2] = aBorderRect.BottomRight(); + aPoly[3] = aBorderRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); + + if ( !bLinear) + { + aBorderRect = aMirrorRect; + aBorderRect.SetTop( static_cast<tools::Long>( aBorderRect.Bottom() - fBorder ) ); + aMirrorRect.SetBottom( aBorderRect.Top() ); + aPoly[0] = aBorderRect.TopLeft(); + aPoly[1] = aBorderRect.TopRight(); + aPoly[2] = aBorderRect.BottomRight(); + aPoly[3] = aBorderRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); + } + } + + tools::Long nStepCount = GetMetafileSteps(aRect); + + // minimal three steps and maximal as max color steps + tools::Long nAbsRedSteps = std::abs( nEndRed - nStartRed ); + tools::Long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen ); + tools::Long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue ); + tools::Long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps ); + nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps ); + tools::Long nSteps = std::min( nStepCount, nMaxColorSteps ); + if ( nSteps < 3) + { + nSteps = 3; + } + + double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps); + double fGradientLine = static_cast<double>(aRect.Top()); + double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom()); + + const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0; + if ( !bLinear) + { + nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap + } + for ( tools::Long i = 0; i < nSteps; i++ ) + { + // linear interpolation of color + double fAlpha = static_cast<double>(i) / fStepsMinus1; + double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha; + nRed = GetGradientColorValue(static_cast<tools::Long>(fTempColor)); + fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha; + nGreen = GetGradientColorValue(static_cast<tools::Long>(fTempColor)); + fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha; + nBlue = GetGradientColorValue(static_cast<tools::Long>(fTempColor)); + + rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + + // Polygon for this color step + aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(i) * fScanInc ) ); + aRect.SetBottom( static_cast<tools::Long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) ); + aPoly[0] = aRect.TopLeft(); + aPoly[1] = aRect.TopRight(); + aPoly[2] = aRect.BottomRight(); + aPoly[3] = aRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); + + if ( !bLinear ) + { + aMirrorRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) ); + aMirrorRect.SetTop( static_cast<tools::Long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) ); + aPoly[0] = aMirrorRect.TopLeft(); + aPoly[1] = aMirrorRect.TopRight(); + aPoly[2] = aMirrorRect.BottomRight(); + aPoly[3] = aMirrorRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); + } + } + if ( bLinear) + return; + + // draw middle polygon with end color + nRed = GetGradientColorValue(nEndRed); + nGreen = GetGradientColorValue(nEndGreen); + nBlue = GetGradientColorValue(nEndBlue); + + rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + + aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) ); + aRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) ); + aPoly[0] = aRect.TopLeft(); + aPoly[1] = aRect.TopRight(); + aPoly[2] = aRect.BottomRight(); + aPoly[3] = aRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + rMetaFile.AddAction( new MetaPolygonAction( std::move(aPoly) ) ); + +} + +void Gradient::DrawComplexGradientToMetafile(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const +{ + // Determine if we output via Polygon or PolyPolygon + // For all rasteroperations other than Overpaint always use PolyPolygon, + // as we will get wrong results if we output multiple times on top of each other. + // Also for printers always use PolyPolygon, as not all printers + // can print polygons on top of each other. + + tools::Rectangle aRect; + Point aCenter; + GetBoundRect(rRect, aRect, aCenter); + + std::optional<tools::PolyPolygon> xPolyPoly; + xPolyPoly = tools::PolyPolygon( 2 ); + + // last parameter - true if complex gradient, false if linear + tools::Long nStepCount = GetMetafileSteps(rRect); + + // at least three steps and at most the number of colour differences + tools::Long nSteps = std::max(nStepCount, tools::Long(2)); + + Color aStartCol(GetStartColor()); + Color aEndCol(GetEndColor()); + + tools::Long nStartRed = (static_cast<tools::Long>(aStartCol.GetRed()) * GetStartIntensity()) / 100; + tools::Long nStartGreen = (static_cast<tools::Long>(aStartCol.GetGreen()) * GetStartIntensity()) / 100; + tools::Long nStartBlue = (static_cast<tools::Long>(aStartCol.GetBlue()) * GetStartIntensity()) / 100; + + tools::Long nEndRed = (static_cast<tools::Long>(aEndCol.GetRed()) * GetEndIntensity()) / 100; + tools::Long nEndGreen = (static_cast<tools::Long>(aEndCol.GetGreen()) * GetEndIntensity()) / 100; + tools::Long nEndBlue = (static_cast<tools::Long>(aEndCol.GetBlue()) * GetEndIntensity()) / 100; + + tools::Long nRedSteps = nEndRed - nStartRed; + tools::Long nGreenSteps = nEndGreen - nStartGreen; + tools::Long nBlueSteps = nEndBlue - nStartBlue; + + tools::Long nCalcSteps = std::abs(nRedSteps); + tools::Long nTempSteps = std::abs(nGreenSteps); + + if (nTempSteps > nCalcSteps) + nCalcSteps = nTempSteps; + + nTempSteps = std::abs( nBlueSteps ); + + if (nTempSteps > nCalcSteps) + nCalcSteps = nTempSteps; + + if (nCalcSteps < nSteps) + nSteps = nCalcSteps; + + if ( !nSteps ) + nSteps = 1; + + // determine output limits and stepsizes for all directions + tools::Polygon aPoly; + double fScanLeft = aRect.Left(); + double fScanTop = aRect.Top(); + double fScanRight = aRect.Right(); + double fScanBottom = aRect.Bottom(); + double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5; + double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5; + + // all gradients are rendered as nested rectangles which shrink + // equally in each dimension - except for 'square' gradients + // which shrink to a central vertex but are not per-se square. + if (GetStyle() != css::awt::GradientStyle_SQUARE) + { + fScanIncY = std::min( fScanIncY, fScanIncX ); + fScanIncX = fScanIncY; + } + sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue); + bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output + + rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + + aPoly = tools::Polygon(rRect); + xPolyPoly->Insert( aPoly ); + xPolyPoly->Insert( aPoly ); + + // loop to output Polygon/PolyPolygon sequentially + for( tools::Long i = 1; i < nSteps; i++ ) + { + // calculate new Polygon + fScanLeft += fScanIncX; + aRect.SetLeft( static_cast<tools::Long>( fScanLeft ) ); + fScanTop += fScanIncY; + aRect.SetTop( static_cast<tools::Long>( fScanTop ) ); + fScanRight -= fScanIncX; + aRect.SetRight( static_cast<tools::Long>( fScanRight ) ); + fScanBottom -= fScanIncY; + aRect.SetBottom( static_cast<tools::Long>( fScanBottom ) ); + + if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) ) + break; + + if (GetStyle() == css::awt::GradientStyle_RADIAL || GetStyle() == css::awt::GradientStyle_ELLIPTICAL) + aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 ); + else + aPoly = tools::Polygon( aRect ); + + aPoly.Rotate(aCenter, GetAngle() % 3600_deg10); + + // adapt colour accordingly + const tools::Long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) ); + nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) ); + nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) ); + nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) ); + + bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output + + xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 ); + xPolyPoly->Replace( aPoly, 1 ); + + rMetaFile.AddAction( new MetaPolyPolygonAction( *xPolyPoly ) ); + + // #107349# Set fill color _after_ geometry painting: + // xPolyPoly's geometry is the band from last iteration's + // aPoly to current iteration's aPoly. The window outdev + // path (see else below), on the other hand, paints the + // full aPoly. Thus, here, we're painting the band before + // the one painted in the window outdev path below. To get + // matching colors, have to delay color setting here. + rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + } + + const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 ); + + if( rPoly.GetBoundRect().IsEmpty() ) + return; + + // #107349# Paint last polygon with end color only if loop + // has generated output. Otherwise, the current + // (i.e. start) color is taken, to generate _any_ output. + if( bPaintLastPolygon ) + { + nRed = GetGradientColorValue( nEndRed ); + nGreen = GetGradientColorValue( nEndGreen ); + nBlue = GetGradientColorValue( nEndBlue ); + } + + rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + rMetaFile.AddAction( new MetaPolygonAction( rPoly ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/graph.cxx b/vcl/source/gdi/graph.cxx new file mode 100644 index 0000000000..4c0efa56be --- /dev/null +++ b/vcl/source/gdi/graph.cxx @@ -0,0 +1,560 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/fract.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/graph.hxx> +#include <vcl/image.hxx> +#include <impgraph.hxx> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <comphelper/servicehelper.hxx> +#include <graphic/UnoGraphic.hxx> +#include <vcl/GraphicExternalLink.hxx> + +using namespace ::com::sun::star; + +namespace +{ + +void ImplDrawDefault(OutputDevice& rOutDev, const OUString* pText, + vcl::Font* pFont, const BitmapEx* pBitmapEx, + const Point& rDestPt, const Size& rDestSize) +{ + sal_uInt16 nPixel = static_cast<sal_uInt16>(rOutDev.PixelToLogic( Size( 1, 1 ) ).Width()); + sal_uInt16 nPixelWidth = nPixel; + Point aPoint( rDestPt.X() + nPixelWidth, rDestPt.Y() + nPixelWidth ); + Size aSize( rDestSize.Width() - ( nPixelWidth << 1 ), rDestSize.Height() - ( nPixelWidth << 1 ) ); + bool bFilled = ( pBitmapEx != nullptr || pFont != nullptr ); + tools::Rectangle aBorderRect( aPoint, aSize ); + + rOutDev.Push(); + + rOutDev.SetFillColor(); + + // On the printer a black rectangle and on the screen one with 3D effect + rOutDev.DrawBorder(aBorderRect); + + aPoint.AdjustX(nPixelWidth + 2*nPixel ); + aPoint.AdjustY(nPixelWidth + 2*nPixel ); + aSize.AdjustWidth( -(2*nPixelWidth + 4*nPixel) ); + aSize.AdjustHeight( -(2*nPixelWidth + 4*nPixel) ); + + if( !aSize.IsEmpty() && pBitmapEx && !pBitmapEx->IsEmpty() ) + { + Size aBitmapSize( rOutDev.PixelToLogic( pBitmapEx->GetSizePixel() ) ); + + if( aSize.Height() > aBitmapSize.Height() && aSize.Width() > aBitmapSize.Width() ) + { + rOutDev.DrawBitmapEx( aPoint, *pBitmapEx ); + aPoint.AdjustX(aBitmapSize.Width() + 2*nPixel ); + aSize.AdjustWidth( -(aBitmapSize.Width() + 2*nPixel) ); + } + } + + if ( !aSize.IsEmpty() && pFont && pText && pText->getLength() && rOutDev.IsOutputEnabled() ) + { + MapMode aMapMode( MapUnit::MapPoint ); + Size aSz = rOutDev.LogicToLogic( Size( 0, 12 ), &aMapMode, nullptr ); + tools::Long nThreshold = aSz.Height() / 2; + tools::Long nStep = nThreshold / 3; + + if ( !nStep ) + nStep = aSz.Height() - nThreshold; + + for(;; aSz.AdjustHeight( -nStep ) ) + { + pFont->SetFontSize( aSz ); + rOutDev.SetFont( *pFont ); + + tools::Long nTextHeight = rOutDev.GetTextHeight(); + tools::Long nTextWidth = rOutDev.GetTextWidth( *pText ); + if ( nTextHeight ) + { + // The approximation does not respect imprecisions caused + // by word wraps + tools::Long nLines = aSize.Height() / nTextHeight; + tools::Long nWidth = aSize.Width() * nLines; // Approximation!!! + + if ( nTextWidth <= nWidth || aSz.Height() <= nThreshold ) + { + sal_Int32 nStart = 0; + sal_Int32 nLen = 0; + + while( nStart < pText->getLength() && (*pText)[nStart] == ' ' ) + nStart++; + while( nStart+nLen < pText->getLength() && (*pText)[nStart+nLen] != ' ' ) + nLen++; + while( nStart < pText->getLength() && nLines-- ) + { + sal_Int32 nNext = nLen; + do + { + while ( nStart+nNext < pText->getLength() && (*pText)[nStart+nNext] == ' ' ) + nNext++; + while ( nStart+nNext < pText->getLength() && (*pText)[nStart+nNext] != ' ' ) + nNext++; + nTextWidth = rOutDev.GetTextWidth( *pText, nStart, nNext ); + if ( nTextWidth > aSize.Width() ) + break; + nLen = nNext; + } + while ( nStart+nNext < pText->getLength() ); + + sal_Int32 n = nLen; + nTextWidth = rOutDev.GetTextWidth( *pText, nStart, n ); + while( nTextWidth > aSize.Width() ) + nTextWidth = rOutDev.GetTextWidth( *pText, nStart, --n ); + rOutDev.DrawText( aPoint, *pText, nStart, n ); + + aPoint.AdjustY(nTextHeight ); + nStart = sal::static_int_cast<sal_uInt16>(nStart + nLen); + nLen = nNext-nLen; + while( nStart < pText->getLength() && (*pText)[nStart] == ' ' ) + { + nStart++; + nLen--; + } + } + break; + } + } + else + break; + } + } + + // If the default graphic does not have content, we draw a red rectangle + if( !bFilled ) + { + aBorderRect.AdjustLeft( 1 ); + aBorderRect.AdjustTop( 1 ); + aBorderRect.AdjustRight( -1 ); + aBorderRect.AdjustBottom( -1 ); + + rOutDev.SetLineColor( COL_LIGHTRED ); + rOutDev.DrawLine( aBorderRect.TopLeft(), aBorderRect.BottomRight() ); + rOutDev.DrawLine( aBorderRect.TopRight(), aBorderRect.BottomLeft() ); + } + + rOutDev.Pop(); +} + +} // end anonymous namespace + +Graphic::Graphic() + : mxImpGraphic(vcl::graphic::Manager::get().newInstance()) +{ +} + +Graphic::Graphic(const Graphic& rGraphic) +{ + if( rGraphic.IsAnimated() ) + mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic); + else + mxImpGraphic = rGraphic.mxImpGraphic; +} + +Graphic::Graphic(Graphic&& rGraphic) noexcept + : mxImpGraphic(std::move(rGraphic.mxImpGraphic)) +{ +} + +Graphic::Graphic(std::shared_ptr<GfxLink> const & rGfxLink, sal_Int32 nPageIndex) + : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGfxLink, nPageIndex)) +{ +} + +Graphic::Graphic(GraphicExternalLink const & rGraphicExternalLink) + : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGraphicExternalLink)) +{ +} + +Graphic::Graphic(const BitmapEx& rBmpEx) + : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rBmpEx)) +{ +} + +// We use XGraphic for passing toolbar images across app UNO aps +// and we need to be able to see and preserve 'stock' images too. +Graphic::Graphic(const Image& rImage) + // FIXME: should really defer the BitmapEx load. + : mxImpGraphic(std::make_shared<ImpGraphic>(rImage.GetBitmapEx())) +{ + OUString aStock = rImage.GetStock(); + if (aStock.getLength()) + mxImpGraphic->setOriginURL("private:graphicrepository/" + aStock); +} + +Graphic::Graphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr) + : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rVectorGraphicDataPtr)) +{ +} + +Graphic::Graphic(const Animation& rAnimation) + : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rAnimation)) +{ +} + +Graphic::Graphic(const GDIMetaFile& rMtf) + : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rMtf)) +{ +} + +Graphic::Graphic( const css::uno::Reference< css::graphic::XGraphic >& rxGraphic ) +{ + const ::unographic::Graphic* pUnoGraphic = dynamic_cast<::unographic::Graphic*>(rxGraphic.get()); + const ::Graphic* pGraphic = pUnoGraphic ? &pUnoGraphic->GetGraphic() : nullptr; + + if( pGraphic ) + { + if (pGraphic->IsAnimated()) + mxImpGraphic = vcl::graphic::Manager::get().copy(pGraphic->mxImpGraphic); + else + mxImpGraphic = pGraphic->mxImpGraphic; + } + else + mxImpGraphic = vcl::graphic::Manager::get().newInstance(); +} + +void Graphic::ImplTestRefCount() +{ + if (mxImpGraphic.use_count() > 1) + { + mxImpGraphic = vcl::graphic::Manager::get().copy(mxImpGraphic); + } +} + +bool Graphic::isAvailable() const +{ + return mxImpGraphic->isAvailable(); +} + +bool Graphic::makeAvailable() +{ + return mxImpGraphic->makeAvailable(); +} + +Graphic& Graphic::operator=( const Graphic& rGraphic ) +{ + if( &rGraphic != this ) + { + if( rGraphic.IsAnimated() ) + mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic); + else + mxImpGraphic = rGraphic.mxImpGraphic; + } + + return *this; +} + +Graphic& Graphic::operator=(Graphic&& rGraphic) noexcept +{ + mxImpGraphic = std::move(rGraphic.mxImpGraphic); + return *this; +} + +bool Graphic::operator==( const Graphic& rGraphic ) const +{ + return (*mxImpGraphic == *rGraphic.mxImpGraphic); +} + +bool Graphic::operator!=( const Graphic& rGraphic ) const +{ + return (*mxImpGraphic != *rGraphic.mxImpGraphic); +} + +bool Graphic::IsNone() const +{ + return GraphicType::NONE == mxImpGraphic->getType(); +} + +void Graphic::Clear() +{ + ImplTestRefCount(); + mxImpGraphic->clear(); +} + +GraphicType Graphic::GetType() const +{ + return mxImpGraphic->getType(); +} + +void Graphic::SetDefaultType() +{ + ImplTestRefCount(); + mxImpGraphic->setDefaultType(); +} + +bool Graphic::IsSupportedGraphic() const +{ + return mxImpGraphic->isSupportedGraphic(); +} + +bool Graphic::IsTransparent() const +{ + return mxImpGraphic->isTransparent(); +} + +bool Graphic::IsAlpha() const +{ + return mxImpGraphic->isAlpha(); +} + +bool Graphic::IsAnimated() const +{ + return mxImpGraphic->isAnimated(); +} + +bool Graphic::IsEPS() const +{ + return mxImpGraphic->isEPS(); +} + +BitmapEx Graphic::GetBitmapEx(const GraphicConversionParameters& rParameters) const +{ + return mxImpGraphic->getBitmapEx(rParameters); +} + +Animation Graphic::GetAnimation() const +{ + return mxImpGraphic->getAnimation(); +} + +const GDIMetaFile& Graphic::GetGDIMetaFile() const +{ + return mxImpGraphic->getGDIMetaFile(); +} + +const BitmapEx& Graphic::GetBitmapExRef() const +{ + return mxImpGraphic->getBitmapExRef(); +} + +uno::Reference<css::graphic::XGraphic> Graphic::GetXGraphic() const +{ + uno::Reference<css::graphic::XGraphic> xGraphic; + + if (GetType() != GraphicType::NONE) + { + rtl::Reference<unographic::Graphic> pUnoGraphic = new unographic::Graphic; + pUnoGraphic->init(*this); + xGraphic = pUnoGraphic; + } + + return xGraphic; +} + +Size Graphic::GetPrefSize() const +{ + return mxImpGraphic->getPrefSize(); +} + +void Graphic::SetPrefSize( const Size& rPrefSize ) +{ + ImplTestRefCount(); + mxImpGraphic->setPrefSize( rPrefSize ); +} + +MapMode Graphic::GetPrefMapMode() const +{ + return mxImpGraphic->getPrefMapMode(); +} + +void Graphic::SetPrefMapMode( const MapMode& rPrefMapMode ) +{ + ImplTestRefCount(); + mxImpGraphic->setPrefMapMode( rPrefMapMode ); +} + +basegfx::B2DSize Graphic::GetPPI() const +{ + double nGrfDPIx; + double nGrfDPIy; + + const MapMode aGrfMap(GetPrefMapMode()); + const Size aGrfPixelSize(GetSizePixel()); + const Size aGrfPrefMapModeSize(GetPrefSize()); + if (aGrfMap.GetMapUnit() == MapUnit::MapInch) + { + nGrfDPIx = aGrfPixelSize.Width() / ( static_cast<double>(aGrfMap.GetScaleX()) * aGrfPrefMapModeSize.Width() ); + nGrfDPIy = aGrfPixelSize.Height() / ( static_cast<double>(aGrfMap.GetScaleY()) * aGrfPrefMapModeSize.Height() ); + } + else + { + const Size aGrf1000thInchSize = OutputDevice::LogicToLogic( + aGrfPrefMapModeSize, aGrfMap, MapMode(MapUnit::Map1000thInch)); + nGrfDPIx = aGrf1000thInchSize.Width() == 0 + ? 0.0 : 1000.0 * aGrfPixelSize.Width() / aGrf1000thInchSize.Width(); + nGrfDPIy = aGrf1000thInchSize.Height() == 0 + ? 0.0 : 1000.0 * aGrfPixelSize.Height() / aGrf1000thInchSize.Height(); + } + + return basegfx::B2DSize(nGrfDPIx, nGrfDPIy); +} + +Size Graphic::GetSizePixel( const OutputDevice* pRefDevice ) const +{ + Size aRet; + + if( GraphicType::Bitmap == mxImpGraphic->getType() ) + aRet = mxImpGraphic->getSizePixel(); + else + aRet = ( pRefDevice ? pRefDevice : Application::GetDefaultDevice() )->LogicToPixel( GetPrefSize(), GetPrefMapMode() ); + + return aRet; +} + +sal_uLong Graphic::GetSizeBytes() const +{ + return mxImpGraphic->getSizeBytes(); +} + +void Graphic::Draw(OutputDevice& rOutDev, const Point& rDestPt) const +{ + mxImpGraphic->draw(rOutDev, rDestPt); +} + +void Graphic::Draw(OutputDevice& rOutDev, const Point& rDestPt, + const Size& rDestSz) const +{ + if( GraphicType::Default == mxImpGraphic->getType() ) + ImplDrawDefault(rOutDev, nullptr, nullptr, nullptr, rDestPt, rDestSz); + else + mxImpGraphic->draw(rOutDev, rDestPt, rDestSz); +} + +void Graphic::DrawEx(OutputDevice& rOutDev, const OUString& rText, + vcl::Font& rFont, const BitmapEx& rBitmap, + const Point& rDestPt, const Size& rDestSz) +{ + ImplDrawDefault(rOutDev, &rText, &rFont, &rBitmap, rDestPt, rDestSz); +} + +void Graphic::StartAnimation(OutputDevice& rOutDev, const Point& rDestPt, + const Size& rDestSz, tools::Long nRendererId, + OutputDevice* pFirstFrameOutDev) +{ + ImplTestRefCount(); + mxImpGraphic->startAnimation(rOutDev, rDestPt, rDestSz, nRendererId, pFirstFrameOutDev); +} + +void Graphic::StopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId ) +{ + ImplTestRefCount(); + mxImpGraphic->stopAnimation( pOutDev, nRendererId ); +} + +void Graphic::SetAnimationNotifyHdl( const Link<Animation*,void>& rLink ) +{ + mxImpGraphic->setAnimationNotifyHdl( rLink ); +} + +Link<Animation*,void> Graphic::GetAnimationNotifyHdl() const +{ + return mxImpGraphic->getAnimationNotifyHdl(); +} + +sal_uInt32 Graphic::GetAnimationLoopCount() const +{ + return mxImpGraphic->getAnimationLoopCount(); +} + +std::shared_ptr<GraphicReader>& Graphic::GetReaderContext() +{ + return mxImpGraphic->getContext(); +} + +void Graphic::SetReaderContext( const std::shared_ptr<GraphicReader> &pReader ) +{ + mxImpGraphic->setContext( pReader ); +} + +void Graphic::SetDummyContext( bool value ) +{ + mxImpGraphic->setDummyContext( value ); +} + +bool Graphic::IsDummyContext() const +{ + return mxImpGraphic->isDummyContext(); +} + +void Graphic::SetGfxLink( const std::shared_ptr<GfxLink>& rGfxLink ) +{ + ImplTestRefCount(); + mxImpGraphic->setGfxLink(rGfxLink); +} + +const std::shared_ptr<GfxLink> & Graphic::GetSharedGfxLink() const +{ + return mxImpGraphic->getSharedGfxLink(); +} + +GfxLink Graphic::GetGfxLink() const +{ + return mxImpGraphic->getGfxLink(); +} + +bool Graphic::IsGfxLink() const +{ + return mxImpGraphic->isGfxLink(); +} + +BitmapChecksum Graphic::GetChecksum() const +{ + return mxImpGraphic->getChecksum(); +} + +const std::shared_ptr<VectorGraphicData>& Graphic::getVectorGraphicData() const +{ + return mxImpGraphic->getVectorGraphicData(); +} + +sal_Int32 Graphic::getPageNumber() const +{ + return mxImpGraphic->getPageNumber(); +} + +OUString Graphic::getOriginURL() const +{ + if (mxImpGraphic) + { + return mxImpGraphic->getOriginURL(); + } + return OUString(); +} + +void Graphic::setOriginURL(OUString const & rOriginURL) +{ + if (mxImpGraphic) + { + mxImpGraphic->setOriginURL(rOriginURL); + } +} + +OString Graphic::getUniqueID() const +{ + OString aUniqueString; + if (mxImpGraphic) + aUniqueString = mxImpGraphic->getUniqueID(); + return aUniqueString; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/graphictools.cxx b/vcl/source/gdi/graphictools.cxx new file mode 100644 index 0000000000..8308da0415 --- /dev/null +++ b/vcl/source/gdi/graphictools.cxx @@ -0,0 +1,289 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <utility> +#include <vcl/TypeSerializer.hxx> +#include <vcl/graphictools.hxx> + +SvtGraphicFill::Transform::Transform() +{ + matrix[0] = 1.0; matrix[1] = 0.0; matrix[2] = 0.0; + matrix[3] = 0.0; matrix[4] = 1.0; matrix[5] = 0.0; +} + +SvtGraphicStroke::SvtGraphicStroke() : + mfTransparency(), + mfStrokeWidth(), + maCapType(), + maJoinType(), + mfMiterLimit( 3.0 ) +{ +} + +SvtGraphicStroke::SvtGraphicStroke( tools::Polygon aPath, + tools::PolyPolygon aStartArrow, + tools::PolyPolygon aEndArrow, + double fTransparency, + double fStrokeWidth, + CapType aCap, + JoinType aJoin, + double fMiterLimit, + DashArray&& rDashArray ) : + maPath(std::move( aPath )), + maStartArrow(std::move( aStartArrow )), + maEndArrow(std::move( aEndArrow )), + mfTransparency( fTransparency ), + mfStrokeWidth( fStrokeWidth ), + maCapType( aCap ), + maJoinType( aJoin ), + mfMiterLimit( fMiterLimit ), + maDashArray( std::move(rDashArray) ) +{ +} + +void SvtGraphicStroke::getPath( tools::Polygon& rPath ) const +{ + rPath = maPath; +} + +void SvtGraphicStroke::getStartArrow( tools::PolyPolygon& rPath ) const +{ + rPath = maStartArrow; +} + +void SvtGraphicStroke::getEndArrow( tools::PolyPolygon& rPath ) const +{ + rPath = maEndArrow; +} + + +void SvtGraphicStroke::getDashArray( DashArray& rDashArray ) const +{ + rDashArray = maDashArray; +} + +void SvtGraphicStroke::setPath( const tools::Polygon& rPoly ) +{ + maPath = rPoly; +} + +void SvtGraphicStroke::setStartArrow( const tools::PolyPolygon& rPoly ) +{ + maStartArrow = rPoly; +} + +void SvtGraphicStroke::setEndArrow( const tools::PolyPolygon& rPoly ) +{ + maEndArrow = rPoly; +} + +void SvtGraphicStroke::scale( double fXScale, double fYScale ) +{ + // Clearly scaling stroke-width for fat lines is rather a problem + maPath.Scale( fXScale, fYScale ); + + double fScale = sqrt (fabs (fXScale * fYScale) ); // clearly not ideal. + mfStrokeWidth *= fScale; + mfMiterLimit *= fScale; + + maStartArrow.Scale( fXScale, fYScale ); + maEndArrow.Scale( fXScale, fYScale ); +} + +SvStream& WriteSvtGraphicStroke( SvStream& rOStm, const SvtGraphicStroke& rClass ) +{ + VersionCompatWrite aCompat( rOStm, 1 ); + + rClass.maPath.Write( rOStm ); + rClass.maStartArrow.Write( rOStm ); + rClass.maEndArrow.Write( rOStm ); + rOStm.WriteDouble( rClass.mfTransparency ); + rOStm.WriteDouble( rClass.mfStrokeWidth ); + sal_uInt16 nTmp = sal::static_int_cast<sal_uInt16>( rClass.maCapType ); + rOStm.WriteUInt16( nTmp ); + nTmp = sal::static_int_cast<sal_uInt16>( rClass.maJoinType ); + rOStm.WriteUInt16( nTmp ); + rOStm.WriteDouble( rClass.mfMiterLimit ); + + rOStm.WriteUInt32( rClass.maDashArray.size() ); + size_t i; + for(i=0; i<rClass.maDashArray.size(); ++i) + rOStm.WriteDouble( rClass.maDashArray[i] ); + + return rOStm; +} + +SvStream& ReadSvtGraphicStroke( SvStream& rIStm, SvtGraphicStroke& rClass ) +{ + VersionCompatRead aCompat( rIStm ); + + rClass.maPath.Read( rIStm ); + rClass.maStartArrow.Read( rIStm ); + rClass.maEndArrow.Read( rIStm ); + rIStm.ReadDouble( rClass.mfTransparency ); + rIStm.ReadDouble( rClass.mfStrokeWidth ); + sal_uInt16 nTmp; + rIStm.ReadUInt16( nTmp ); + rClass.maCapType = SvtGraphicStroke::CapType(nTmp); + rIStm.ReadUInt16( nTmp ); + rClass.maJoinType = SvtGraphicStroke::JoinType(nTmp); + rIStm.ReadDouble( rClass.mfMiterLimit ); + + sal_uInt32 nSize; + rIStm.ReadUInt32( nSize ); + rClass.maDashArray.resize(nSize); + size_t i; + for(i=0; i<rClass.maDashArray.size(); ++i) + rIStm.ReadDouble( rClass.maDashArray[i] ); + + return rIStm; +} + +SvtGraphicFill::SvtGraphicFill() : + maFillColor( COL_BLACK ), + mfTransparency(), + maFillRule(), + maFillType(), + mbTiling( false ), + maHatchType(), + maHatchColor( COL_BLACK ), + maGradientType(), + maGradient1stColor( COL_BLACK ), + maGradient2ndColor( COL_BLACK ), + maGradientStepCount( gradientStepsInfinite ) +{ +} + +SvtGraphicFill::SvtGraphicFill( tools::PolyPolygon aPath, + Color aFillColor, + double fTransparency, + FillRule aFillRule, + FillType aFillType, + const Transform& aFillTransform, + bool bTiling, + HatchType aHatchType, + Color aHatchColor, + GradientType aGradientType, + Color aGradient1stColor, + Color aGradient2ndColor, + sal_Int32 aGradientStepCount, + Graphic aFillGraphic ) : + maPath(std::move( aPath )), + maFillColor( aFillColor ), + mfTransparency( fTransparency ), + maFillRule( aFillRule ), + maFillType( aFillType ), + maFillTransform( aFillTransform ), + mbTiling( bTiling ), + maHatchType( aHatchType ), + maHatchColor( aHatchColor ), + maGradientType( aGradientType ), + maGradient1stColor( aGradient1stColor ), + maGradient2ndColor( aGradient2ndColor ), + maGradientStepCount( aGradientStepCount ), + maFillGraphic(std::move( aFillGraphic )) +{ +} + +void SvtGraphicFill::getPath( tools::PolyPolygon& rPath ) const +{ + rPath = maPath; +} + + +void SvtGraphicFill::getTransform( Transform& rTrans ) const +{ + rTrans = maFillTransform; +} + + +void SvtGraphicFill::getGraphic( Graphic& rGraphic ) const +{ + rGraphic = maFillGraphic; +} + +void SvtGraphicFill::setPath( const tools::PolyPolygon& rPath ) +{ + maPath = rPath; +} + +SvStream& WriteSvtGraphicFill( SvStream& rOStm, const SvtGraphicFill& rClass ) +{ + VersionCompatWrite aCompat( rOStm, 1 ); + + rClass.maPath.Write( rOStm ); + TypeSerializer aSerializer(rOStm); + aSerializer.writeColor(rClass.maFillColor); + rOStm.WriteDouble( rClass.mfTransparency ); + sal_uInt16 nTmp = sal::static_int_cast<sal_uInt16>( rClass.maFillRule ); + rOStm.WriteUInt16( nTmp ); + nTmp = sal::static_int_cast<sal_uInt16>( rClass.maFillType ); + rOStm.WriteUInt16( nTmp ); + int i; + for(i=0; i<SvtGraphicFill::Transform::MatrixSize; ++i) + rOStm.WriteDouble( rClass.maFillTransform.matrix[i] ); + nTmp = sal_uInt16(rClass.mbTiling); + rOStm.WriteUInt16( nTmp ); + nTmp = sal::static_int_cast<sal_uInt16>( rClass.maHatchType ); + rOStm.WriteUInt16( nTmp ); + aSerializer.writeColor(rClass.maHatchColor); + nTmp = sal::static_int_cast<sal_uInt16>( rClass.maGradientType ); + rOStm.WriteUInt16( nTmp ); + aSerializer.writeColor(rClass.maGradient1stColor); + aSerializer.writeColor(rClass.maGradient2ndColor); + rOStm.WriteInt32( rClass.maGradientStepCount ); + aSerializer.writeGraphic(rClass.maFillGraphic); + + return rOStm; +} + +SvStream& ReadSvtGraphicFill( SvStream& rIStm, SvtGraphicFill& rClass ) +{ + VersionCompatRead aCompat( rIStm ); + + rClass.maPath.Read( rIStm ); + + TypeSerializer aSerializer(rIStm); + aSerializer.readColor(rClass.maFillColor); + rIStm.ReadDouble( rClass.mfTransparency ); + sal_uInt16 nTmp; + rIStm.ReadUInt16( nTmp ); + rClass.maFillRule = SvtGraphicFill::FillRule( nTmp ); + rIStm.ReadUInt16( nTmp ); + rClass.maFillType = SvtGraphicFill::FillType( nTmp ); + for (int i = 0; i < SvtGraphicFill::Transform::MatrixSize; ++i) + rIStm.ReadDouble( rClass.maFillTransform.matrix[i] ); + rIStm.ReadUInt16( nTmp ); + rClass.mbTiling = nTmp; + rIStm.ReadUInt16( nTmp ); + rClass.maHatchType = SvtGraphicFill::HatchType( nTmp ); + aSerializer.readColor(rClass.maHatchColor); + rIStm.ReadUInt16( nTmp ); + rClass.maGradientType = SvtGraphicFill::GradientType( nTmp ); + aSerializer.readColor(rClass.maGradient1stColor); + aSerializer.readColor(rClass.maGradient2ndColor); + rIStm.ReadInt32( rClass.maGradientStepCount ); + aSerializer.readGraphic(rClass.maFillGraphic); + + return rIStm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/hatch.cxx b/vcl/source/gdi/hatch.cxx new file mode 100644 index 0000000000..e097f2f36d --- /dev/null +++ b/vcl/source/gdi/hatch.cxx @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <tools/GenericTypeSerializer.hxx> +#include <vcl/hatch.hxx> + +ImplHatch::ImplHatch() : + maColor ( COL_BLACK ), + meStyle ( HatchStyle::Single ), + mnDistance ( 1 ), + mnAngle ( 0 ) +{ +} + +bool ImplHatch::operator==( const ImplHatch& rImplHatch ) const +{ + return maColor == rImplHatch.maColor && + meStyle == rImplHatch.meStyle && + mnDistance == rImplHatch.mnDistance && + mnAngle == rImplHatch.mnAngle; +} + +Hatch::Hatch() = default; + +Hatch::Hatch( const Hatch& ) = default; + +Hatch::Hatch( HatchStyle eStyle, const Color& rColor, + tools::Long nDistance, Degree10 nAngle10 ) +{ + mpImplHatch->maColor = rColor; + mpImplHatch->meStyle = eStyle; + mpImplHatch->mnDistance = nDistance; + mpImplHatch->mnAngle = nAngle10; +} + +Hatch::~Hatch() = default; + +Hatch& Hatch::operator=( const Hatch& ) = default; + +bool Hatch::operator==( const Hatch& rHatch ) const +{ + return mpImplHatch == rHatch.mpImplHatch; +} + + +void Hatch::SetColor( const Color& rColor ) +{ + mpImplHatch->maColor = rColor; +} + +void Hatch::SetDistance( tools::Long nDistance ) +{ + mpImplHatch->mnDistance = nDistance; +} + +void Hatch::SetAngle( Degree10 nAngle10 ) +{ + mpImplHatch->mnAngle = nAngle10; +} + +SvStream& ReadHatch( SvStream& rIStm, Hatch& rHatch ) +{ + VersionCompatRead aCompat(rIStm); + + sal_uInt16 nTmpStyle(0); + rIStm.ReadUInt16(nTmpStyle); + rHatch.mpImplHatch->meStyle = static_cast<HatchStyle>(nTmpStyle); + + tools::GenericTypeSerializer aSerializer(rIStm); + aSerializer.readColor(rHatch.mpImplHatch->maColor); + + sal_Int32 nTmp32(0); + rIStm.ReadInt32(nTmp32); + rHatch.mpImplHatch->mnDistance = nTmp32; + + sal_Int16 nTmpAngle(0); + rIStm.ReadInt16(nTmpAngle); + rHatch.mpImplHatch->mnAngle = Degree10(nTmpAngle); + + return rIStm; +} + +SvStream& WriteHatch( SvStream& rOStm, const Hatch& rHatch ) +{ + VersionCompatWrite aCompat(rOStm, 1); + + rOStm.WriteUInt16( static_cast<sal_uInt16>(rHatch.mpImplHatch->meStyle) ); + + tools::GenericTypeSerializer aSerializer(rOStm); + aSerializer.writeColor(rHatch.mpImplHatch->maColor); + rOStm.WriteInt32( rHatch.mpImplHatch->mnDistance ).WriteInt16( rHatch.mpImplHatch->mnAngle.get() ); + + return rOStm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/impglyphitem.cxx b/vcl/source/gdi/impglyphitem.cxx new file mode 100644 index 0000000000..ca8016a192 --- /dev/null +++ b/vcl/source/gdi/impglyphitem.cxx @@ -0,0 +1,555 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <impglyphitem.hxx> +#include <utility> +#include <vcl/glyphitemcache.hxx> +#include <vcl/vcllayout.hxx> +#include <vcl/lazydelete.hxx> +#include <tools/stream.hxx> +#include <unotools/configmgr.hxx> +#include <TextLayoutCache.hxx> +#include <officecfg/Office/Common.hxx> +#include <o3tl/string_view.hxx> + +#include <unicode/ubidi.h> +#include <unicode/uchar.h> + +// These need being explicit because of SalLayoutGlyphsImpl being private in vcl. +SalLayoutGlyphs::SalLayoutGlyphs() {} + +SalLayoutGlyphs::~SalLayoutGlyphs() {} + +SalLayoutGlyphs::SalLayoutGlyphs(SalLayoutGlyphs&& rOther) noexcept +{ + std::swap(m_pImpl, rOther.m_pImpl); + std::swap(m_pExtraImpls, rOther.m_pExtraImpls); +} + +SalLayoutGlyphs& SalLayoutGlyphs::operator=(SalLayoutGlyphs&& rOther) noexcept +{ + if (this != &rOther) + { + std::swap(m_pImpl, rOther.m_pImpl); + std::swap(m_pExtraImpls, rOther.m_pExtraImpls); + } + return *this; +} + +bool SalLayoutGlyphs::IsValid() const +{ + if (m_pImpl == nullptr) + return false; + if (!m_pImpl->IsValid()) + return false; + if (m_pExtraImpls) + for (std::unique_ptr<SalLayoutGlyphsImpl> const& impl : *m_pExtraImpls) + if (!impl->IsValid()) + return false; + return true; +} + +void SalLayoutGlyphs::Invalidate() +{ + // Invalidating is in fact simply clearing. + m_pImpl.reset(); + m_pExtraImpls.reset(); +} + +SalLayoutGlyphsImpl* SalLayoutGlyphs::Impl(unsigned int nLevel) const +{ + if (nLevel == 0) + return m_pImpl.get(); + if (m_pExtraImpls != nullptr && nLevel - 1 < m_pExtraImpls->size()) + return (*m_pExtraImpls)[nLevel - 1].get(); + return nullptr; +} + +void SalLayoutGlyphs::AppendImpl(SalLayoutGlyphsImpl* pImpl) +{ + if (!m_pImpl) + m_pImpl.reset(pImpl); + else + { + if (!m_pExtraImpls) + m_pExtraImpls.reset(new std::vector<std::unique_ptr<SalLayoutGlyphsImpl>>); + m_pExtraImpls->emplace_back(pImpl); + } +} + +SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::clone() const { return new SalLayoutGlyphsImpl(*this); } + +// Clone, but only glyphs in the given range in the original text string. +// It is possible the given range may not be cloned, in which case this returns nullptr. +SalLayoutGlyphsImpl* SalLayoutGlyphsImpl::cloneCharRange(sal_Int32 index, sal_Int32 length) const +{ + std::unique_ptr<SalLayoutGlyphsImpl> copy(new SalLayoutGlyphsImpl(*GetFont())); + copy->SetFlags(GetFlags()); + if (empty()) + return copy.release(); + bool rtl = front().IsRTLGlyph(); + // Avoid mixing LTR/RTL or layouts that do not have it set explicitly (BiDiStrong). Otherwise + // the subset may not quite match what would a real layout call give (e.g. some characters with neutral + // direction such as space might have different LTR/RTL flag). It seems bailing out here mostly + // avoid relatively rare corner cases and doesn't matter for performance. + // This is also checked in SalLayoutGlyphsCache::GetLayoutGlyphs() below. + if (!(GetFlags() & SalLayoutFlags::BiDiStrong) + || rtl != bool(GetFlags() & SalLayoutFlags::BiDiRtl)) + return nullptr; + copy->reserve(std::min<size_t>(size(), length)); + sal_Int32 beginPos = index; + sal_Int32 endPos = index + length; + const_iterator pos; + if (rtl) + { + // Glyphs are in reverse order for RTL. + beginPos = index + length - 1; + endPos = index - 1; + // Skip glyphs that are in the string after the given index, i.e. are before the glyphs + // we want. + pos = std::partition_point( + begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() > beginPos; }); + } + else + { + // Skip glyphs that are in the string before the given index (glyphs are sorted by charPos()). + pos = std::partition_point( + begin(), end(), [beginPos](const GlyphItem& it) { return it.charPos() < beginPos; }); + } + if (pos == end()) + return nullptr; + // Require a start at the exact position given, otherwise bail out. + if (pos->charPos() != beginPos) + return nullptr; + // For RTL make sure we're not cutting in the middle of a multi-character glyph, + // or in the middle of a cluster + // (for non-RTL charPos is always the start of a multi-character glyph). + if (rtl && (pos->charPos() + pos->charCount() > beginPos + 1 || pos->IsInCluster())) + return nullptr; + if (!isSafeToBreak(pos, rtl)) + return nullptr; + // LinearPos needs adjusting to start at xOffset/yOffset for the first item, + // that's how it's computed in GenericSalLayout::LayoutText(). + basegfx::B2DPoint zeroPoint + = pos->linearPos() - basegfx::B2DPoint(pos->xOffset(), pos->yOffset()); + // Add and adjust all glyphs until the given length. + // The check is written as 'charPos + charCount <= endPos' rather than 'charPos < endPos' + // (or similarly for RTL) to make sure we include complete glyphs. If a glyph is composed + // from several characters, we should not cut in the middle of those characters, so this + // checks the glyph is entirely in the given character range. If it is not, this will end + // the loop and the later 'pos->charPos() != endPos' check will fail and bail out. + // CppunitTest_sw_layoutwriter's testCombiningCharacterCursorPosition would fail without this. + while (pos != end() + && (rtl ? pos->charPos() - pos->charCount() >= endPos + : pos->charPos() + pos->charCount() <= endPos)) + { + if (pos->IsRTLGlyph() != rtl) + return nullptr; // Don't mix RTL and non-RTL runs. + // HACK: When running CppunitTest_sw_uiwriter3's testTdf104649 on Mac there's glyph + // with id 1232 that has 0 charCount, 0 origWidth and inconsistent xOffset (sometimes 0, + // but sometimes not). Possibly font or Harfbuzz bug? It's extremely rare, so simply bail out. + if (pos->charCount() == 0 && pos->origWidth() == 0) + return nullptr; + copy->push_back(*pos); + copy->back().setLinearPos(copy->back().linearPos() - zeroPoint); + ++pos; + } + if (pos != end()) + { + // Fail if the next character is not at the expected past-end position. For RTL check + // that we're not cutting in the middle of a multi-character glyph. + if (rtl ? pos->charPos() + pos->charCount() != endPos + 1 : pos->charPos() != endPos) + return nullptr; + if (!isSafeToBreak(pos, rtl)) + return nullptr; + } + return copy.release(); +} + +bool SalLayoutGlyphsImpl::isSafeToBreak(const_iterator pos, bool rtl) const +{ + if (rtl) + { + // RTL is more complicated, because HB_GLYPH_FLAG_UNSAFE_TO_BREAK talks about beginning + // of a cluster, which refers to the text, not glyphs. This function is called + // for the first glyph of the subset and the first glyph after the subset, but since + // the glyphs are backwards, and we need the beginning of cluster at the start of the text + // and beginning of the cluster after the text, we need to check glyphs before this position. + if (pos == begin()) + return true; + --pos; + } + // Don't create a subset if it's not safe to break at the beginning or end of the sequence + // (https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-glyph-flags-t). + if (pos->IsUnsafeToBreak() || (pos->IsInCluster() && !pos->IsClusterStart())) + return false; + return true; +} + +#ifdef DBG_UTIL +bool SalLayoutGlyphsImpl::isLayoutEquivalent(const SalLayoutGlyphsImpl* other) const +{ + if (!GetFont()->mxFontMetric->CompareDeviceIndependentFontAttributes( + *other->GetFont()->mxFontMetric)) + return false; + if (GetFlags() != other->GetFlags()) + return false; + if (empty() || other->empty()) + return empty() == other->empty(); + if (size() != other->size()) + return false; + for (size_t pos = 0; pos < size(); ++pos) + { + if (!(*this)[pos].isLayoutEquivalent((*other)[pos])) + return false; + } + return true; +} +#endif + +bool SalLayoutGlyphsImpl::IsValid() const +{ + if (!m_rFontInstance.is()) + return false; + return true; +} + +void SalLayoutGlyphsCache::clear() { mCachedGlyphs.clear(); } + +SalLayoutGlyphsCache* SalLayoutGlyphsCache::self() +{ + static vcl::DeleteOnDeinit<SalLayoutGlyphsCache> cache( + !utl::ConfigManager::IsFuzzing() + ? officecfg::Office::Common::Cache::Font::GlyphsCacheSize::get() + : 20000); + return cache.get(); +} + +static UBiDiDirection getBiDiDirection(std::u16string_view text, sal_Int32 index, sal_Int32 len) +{ + // Return whether all character are LTR, RTL, neutral or whether it's mixed. + // This is sort of ubidi_getBaseDirection() and ubidi_getDirection(), + // but it's meant to be fast but also check all characters. + sal_Int32 end = index + len; + UBiDiDirection direction = UBIDI_NEUTRAL; + while (index < end) + { + switch (u_charDirection(o3tl::iterateCodePoints(text, &index))) + { + // Only characters with strong direction. + case U_LEFT_TO_RIGHT: + if (direction == UBIDI_RTL) + return UBIDI_MIXED; + direction = UBIDI_LTR; + break; + case U_RIGHT_TO_LEFT: + case U_RIGHT_TO_LEFT_ARABIC: + if (direction == UBIDI_LTR) + return UBIDI_MIXED; + direction = UBIDI_RTL; + break; + default: + break; + } + } + return direction; +} + +static SalLayoutGlyphs makeGlyphsSubset(const SalLayoutGlyphs& source, + const OutputDevice* outputDevice, std::u16string_view text, + sal_Int32 index, sal_Int32 len) +{ + // tdf#149264: We need to check if the text is LTR, RTL or mixed. Apparently + // harfbuzz doesn't give reproducible results (or possibly HB_GLYPH_FLAG_UNSAFE_TO_BREAK + // is not reliable?) when asked to lay out RTL text as LTR. So require that the whole + // subset ir either LTR or RTL. + UBiDiDirection direction = getBiDiDirection(text, index, len); + if (direction == UBIDI_MIXED) + return SalLayoutGlyphs(); + SalLayoutGlyphs ret; + for (int level = 0;; ++level) + { + const SalLayoutGlyphsImpl* sourceLevel = source.Impl(level); + if (sourceLevel == nullptr) + break; + bool sourceRtl = bool(sourceLevel->GetFlags() & SalLayoutFlags::BiDiRtl); + if ((direction == UBIDI_LTR && sourceRtl) || (direction == UBIDI_RTL && !sourceRtl)) + return SalLayoutGlyphs(); + SalLayoutGlyphsImpl* cloned = sourceLevel->cloneCharRange(index, len); + // If the glyphs range cannot be cloned, bail out. + if (cloned == nullptr) + return SalLayoutGlyphs(); + // If the entire string is mixed LTR/RTL but the subset is only LTR, + // then make sure the flags match that, otherwise checkGlyphsEqual() + // would assert on flags being different. + cloned->SetFlags(cloned->GetFlags() + | outputDevice->GetBiDiLayoutFlags(text, index, index + len)); + ret.AppendImpl(cloned); + } + return ret; +} + +#ifdef DBG_UTIL +static void checkGlyphsEqual(const SalLayoutGlyphs& g1, const SalLayoutGlyphs& g2) +{ + for (int level = 0;; ++level) + { + const SalLayoutGlyphsImpl* l1 = g1.Impl(level); + const SalLayoutGlyphsImpl* l2 = g2.Impl(level); + if (l1 == nullptr || l2 == nullptr) + { + assert(l1 == l2); + break; + } + assert(l1->isLayoutEquivalent(l2)); + } +} +#endif + +const SalLayoutGlyphs* +SalLayoutGlyphsCache::GetLayoutGlyphs(VclPtr<const OutputDevice> outputDevice, const OUString& text, + sal_Int32 nIndex, sal_Int32 nLen, tools::Long nLogicWidth, + const vcl::text::TextLayoutCache* layoutCache) +{ + if (nLen == 0) + return nullptr; + const CachedGlyphsKey key(outputDevice, text, nIndex, nLen, nLogicWidth); + GlyphsCache::const_iterator it = mCachedGlyphs.find(key); + if (it != mCachedGlyphs.end()) + { + if (it->second.IsValid()) + return &it->second; + // Do not try to create the layout here. If a cache item exists, it's already + // been attempted and the layout was invalid (this happens with MultiSalLayout). + // So in that case this is a cached failure. + return nullptr; + } + bool resetLastSubstringKey = true; + const sal_Unicode nbSpace = 0xa0; // non-breaking space + // SalLayoutGlyphsImpl::cloneCharRange() requires BiDiStrong, so if not set, do not even try. + bool skipGlyphSubsets + = !(outputDevice->GetLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong); + if ((nIndex != 0 || nLen != text.getLength()) && !skipGlyphSubsets) + { + // The glyphs functions are often called first for an entire string + // and then with an increasing starting index until the end of the string. + // Which means it's possible to get the glyphs faster by just copying + // a subset of the full glyphs and adjusting as necessary. + if (mLastTemporaryKey.has_value() && mLastTemporaryKey == key) + return &mLastTemporaryGlyphs; + const CachedGlyphsKey keyWhole(outputDevice, text, 0, text.getLength(), nLogicWidth); + GlyphsCache::const_iterator itWhole = mCachedGlyphs.find(keyWhole); + if (itWhole == mCachedGlyphs.end()) + { + // This function may often be called repeatedly for segments of the same string, + // in which case it is more efficient to cache glyphs for the entire string + // and then return subsets of them. So if a second call either starts at the same + // position or starts at the end of the previous call, cache the entire string. + // This used to do this only for the first two segments of the string, + // but that missed the case when the font slightly changed e.g. because of the first + // part being underlined. Doing this for any two segments allows this optimization + // even when the prefix of the string would use a different font. + // TODO: Can those font differences be ignored? + // Writer layouts tests enable SAL_NON_APPLICATION_FONT_USE=abort in order + // to make PrintFontManager::Substitute() abort if font fallback happens. When + // laying out the entire string the chance this happens increases (e.g. testAbi11870 + // normally calls this function only for a part of a string, but this optimization + // lays out the entire string and causes a fallback). Since this optimization + // does not change result of this function, simply disable it for those tests. + static const bool bAbortOnFontSubstitute = [] { + const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE"); + return pEnv && strcmp(pEnv, "abort") == 0; + }(); + if (mLastSubstringKey.has_value() && !bAbortOnFontSubstitute) + { + sal_Int32 pos = nIndex; + if (mLastSubstringKey->len < pos && text[pos - 1] == nbSpace) + --pos; // Writer skips a non-breaking space, so skip that character too. + if ((mLastSubstringKey->len == pos || mLastSubstringKey->index == nIndex) + && mLastSubstringKey + == CachedGlyphsKey(outputDevice, text, mLastSubstringKey->index, + mLastSubstringKey->len, nLogicWidth)) + { + GetLayoutGlyphs(outputDevice, text, 0, text.getLength(), nLogicWidth, + layoutCache); + itWhole = mCachedGlyphs.find(keyWhole); + } + else + mLastSubstringKey.reset(); + } + if (!mLastSubstringKey.has_value()) + { + mLastSubstringKey = key; + resetLastSubstringKey = false; + } + } + if (itWhole != mCachedGlyphs.end() && itWhole->second.IsValid()) + { + mLastSubstringKey.reset(); + mLastTemporaryGlyphs + = makeGlyphsSubset(itWhole->second, outputDevice, text, nIndex, nLen); + if (mLastTemporaryGlyphs.IsValid()) + { + mLastTemporaryKey = key; +#ifdef DBG_UTIL + std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache; + if (layoutCache == nullptr) + { + tmpLayoutCache = vcl::text::TextLayoutCache::Create(text); + layoutCache = tmpLayoutCache.get(); + } + // Check if the subset result really matches what we would get normally, + // to make sure corner cases are handled well (see SalLayoutGlyphsImpl::cloneCharRange()). + std::unique_ptr<SalLayout> layout + = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, {}, + SalLayoutFlags::GlyphItemsOnly, layoutCache); + assert(layout); + checkGlyphsEqual(mLastTemporaryGlyphs, layout->GetGlyphs()); +#endif + return &mLastTemporaryGlyphs; + } + } + } + if (resetLastSubstringKey) + { + // Writer does non-breaking space differently (not as part of the string), so in that + // case ignore that call and still allow finding two adjacent substrings that have + // the non-breaking space between them. + if (nLen != 1 || text[nIndex] != nbSpace) + mLastSubstringKey.reset(); + } + + std::shared_ptr<const vcl::text::TextLayoutCache> tmpLayoutCache; + if (layoutCache == nullptr) + { + tmpLayoutCache = vcl::text::TextLayoutCache::Create(text); + layoutCache = tmpLayoutCache.get(); + } + std::unique_ptr<SalLayout> layout + = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, {}, + SalLayoutFlags::GlyphItemsOnly, layoutCache); + if (layout) + { + SalLayoutGlyphs glyphs = layout->GetGlyphs(); + if (glyphs.IsValid()) + { + // TODO: Fallbacks do not work reliably (fallback font not included in the key), + // so do not cache (but still return once, using the temporary without a key set). + if (!mbCacheGlyphsWhenDoingFallbackFonts && glyphs.Impl(1) != nullptr) + { + mLastTemporaryGlyphs = std::move(glyphs); + mLastTemporaryKey.reset(); + return &mLastTemporaryGlyphs; + } + mCachedGlyphs.insert(std::make_pair(key, std::move(glyphs))); + assert(mCachedGlyphs.find(key) + == mCachedGlyphs.begin()); // newly inserted item is first + return &mCachedGlyphs.begin()->second; + } + } + // Failure, cache it too as invalid glyphs. + mCachedGlyphs.insert(std::make_pair(key, SalLayoutGlyphs())); + return nullptr; +} + +void SalLayoutGlyphsCache::SetCacheGlyphsWhenDoingFallbackFonts(bool bOK) +{ + mbCacheGlyphsWhenDoingFallbackFonts = bOK; + if (!bOK) + clear(); +} + +SalLayoutGlyphsCache::CachedGlyphsKey::CachedGlyphsKey( + const VclPtr<const OutputDevice>& outputDevice, OUString t, sal_Int32 i, sal_Int32 l, + tools::Long w) + : text(std::move(t)) + , index(i) + , len(l) + , logicWidth(w) + // we also need to save things used in OutputDevice::ImplPrepareLayoutArgs(), in case they + // change in the output device, plus mapMode affects the sizes. + , fontMetric(outputDevice->GetFontMetric()) + // TODO It would be possible to get a better hit ratio if mapMode wasn't part of the key + // and results that differ only in mapmode would have coordinates adjusted based on that. + // That would occasionally lead to rounding errors (at least differences that would + // make checkGlyphsEqual() fail). + , mapMode(outputDevice->GetMapMode()) + , digitLanguage(outputDevice->GetDigitLanguage()) + , layoutMode(outputDevice->GetLayoutMode()) + , rtl(outputDevice->IsRTLEnabled()) +{ + const LogicalFontInstance* fi = outputDevice->GetFontInstance(); + fi->GetScale(&fontScaleX, &fontScaleY); + + const vcl::font::FontSelectPattern& rFSD = fi->GetFontSelectPattern(); + disabledLigatures = rFSD.GetPitch() == PITCH_FIXED; + artificialItalic = fi->NeedsArtificialItalic(); + artificialBold = fi->NeedsArtificialBold(); + + hashValue = 0; + o3tl::hash_combine(hashValue, vcl::text::FirstCharsStringHash()(text)); + o3tl::hash_combine(hashValue, index); + o3tl::hash_combine(hashValue, len); + o3tl::hash_combine(hashValue, logicWidth); + o3tl::hash_combine(hashValue, outputDevice.get()); + // Need to use IgnoreColor, because sometimes the color changes, but it's irrelevant + // for text layout (and also obsolete in vcl::Font). + o3tl::hash_combine(hashValue, fontMetric.GetHashValueIgnoreColor()); + // For some reason font scale may differ even if vcl::Font is the same, + // so explicitly check it too. + o3tl::hash_combine(hashValue, fontScaleX); + o3tl::hash_combine(hashValue, fontScaleY); + o3tl::hash_combine(hashValue, mapMode.GetHashValue()); + o3tl::hash_combine(hashValue, rtl); + o3tl::hash_combine(hashValue, disabledLigatures); + o3tl::hash_combine(hashValue, artificialItalic); + o3tl::hash_combine(hashValue, artificialBold); + o3tl::hash_combine(hashValue, layoutMode); + o3tl::hash_combine(hashValue, digitLanguage.get()); +} + +inline bool SalLayoutGlyphsCache::CachedGlyphsKey::operator==(const CachedGlyphsKey& other) const +{ + return hashValue == other.hashValue && index == other.index && len == other.len + && logicWidth == other.logicWidth && mapMode == other.mapMode && rtl == other.rtl + && disabledLigatures == other.disabledLigatures + && artificialItalic == other.artificialItalic && artificialBold == other.artificialBold + && layoutMode == other.layoutMode && digitLanguage == other.digitLanguage + && fontScaleX == other.fontScaleX && fontScaleY == other.fontScaleY + && fontMetric.EqualIgnoreColor(other.fontMetric) + && vcl::text::FastStringCompareEqual()(text, other.text); + // Slower things last in the comparison. +} + +size_t SalLayoutGlyphsCache::GlyphsCost::operator()(const SalLayoutGlyphs& glyphs) const +{ + size_t cost = 0; + for (int level = 0;; ++level) + { + const SalLayoutGlyphsImpl* impl = glyphs.Impl(level); + if (impl == nullptr) + break; + // Count size in bytes, both the SalLayoutGlyphsImpl instance and contained GlyphItem's. + cost += sizeof(*impl); + cost += impl->size() * sizeof(impl->front()); + } + return cost; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/impgraph.cxx b/vcl/source/gdi/impgraph.cxx new file mode 100644 index 0000000000..ac36d2c72b --- /dev/null +++ b/vcl/source/gdi/impgraph.cxx @@ -0,0 +1,1775 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <comphelper/fileformat.h> +#include <o3tl/make_shared.hxx> +#include <tools/fract.hxx> +#include <tools/vcompat.hxx> +#include <tools/urlobj.hxx> +#include <tools/stream.hxx> +#include <unotools/ucbhelper.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/tempfile.hxx> +#include <utility> +#include <vcl/filter/SvmReader.hxx> +#include <vcl/filter/SvmWriter.hxx> +#include <vcl/outdev.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gfxlink.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/graph.hxx> +#include <vcl/metaact.hxx> +#include <impgraph.hxx> +#include <com/sun/star/graphic/XPrimitive2D.hpp> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <vcl/dibtools.hxx> +#include <map> +#include <memory> +#include <vcl/gdimetafiletools.hxx> +#include <vcl/TypeSerializer.hxx> +#include <vcl/pdfread.hxx> +#include <graphic/VectorGraphicLoader.hxx> + +#define GRAPHIC_MTFTOBMP_MAXEXT 2048 +#define GRAPHIC_STREAMBUFSIZE 8192UL + +#define SWAP_FORMAT_ID COMPAT_FORMAT( 'S', 'W', 'A', 'P' ) + +using namespace com::sun::star; + + +class ImpSwapFile +{ +private: + utl::TempFileFast maTempFile; + OUString maOriginURL; + +public: + ImpSwapFile(OUString aOriginURL) + : maOriginURL(std::move(aOriginURL)) + { + } + + SvStream* getStream() { return maTempFile.GetStream(StreamMode::READWRITE); } + OUString const & getOriginURL() const { return maOriginURL; } +}; + +SvStream* ImpGraphic::getSwapFileStream() const +{ + if (mpSwapFile) + return mpSwapFile->getStream(); + return nullptr; +} + +ImpGraphic::ImpGraphic() : + meType ( GraphicType::NONE ), + mnSizeBytes ( 0 ), + mbSwapOut ( false ), + mbDummyContext ( false ), + maLastUsed (std::chrono::high_resolution_clock::now()), + mbPrepared ( false ) +{ +} + +ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic) + : maMetaFile(rImpGraphic.maMetaFile) + , maBitmapEx(rImpGraphic.maBitmapEx) + , maSwapInfo(rImpGraphic.maSwapInfo) + , mpContext(rImpGraphic.mpContext) + , mpSwapFile(rImpGraphic.mpSwapFile) + , mpGfxLink(rImpGraphic.mpGfxLink) + , meType(rImpGraphic.meType) + , mnSizeBytes(rImpGraphic.mnSizeBytes) + , mbSwapOut(rImpGraphic.mbSwapOut) + , mbDummyContext(rImpGraphic.mbDummyContext) + , maVectorGraphicData(rImpGraphic.maVectorGraphicData) + , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink) + , maLastUsed (std::chrono::high_resolution_clock::now()) + , mbPrepared (rImpGraphic.mbPrepared) +{ + if( rImpGraphic.mpAnimation ) + { + mpAnimation = std::make_unique<Animation>( *rImpGraphic.mpAnimation ); + maBitmapEx = mpAnimation->GetBitmapEx(); + } +} + +ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept + : maMetaFile(std::move(rImpGraphic.maMetaFile)) + , maBitmapEx(std::move(rImpGraphic.maBitmapEx)) + , maSwapInfo(std::move(rImpGraphic.maSwapInfo)) + , mpAnimation(std::move(rImpGraphic.mpAnimation)) + , mpContext(std::move(rImpGraphic.mpContext)) + , mpSwapFile(std::move(rImpGraphic.mpSwapFile)) + , mpGfxLink(std::move(rImpGraphic.mpGfxLink)) + , meType(rImpGraphic.meType) + , mnSizeBytes(rImpGraphic.mnSizeBytes) + , mbSwapOut(rImpGraphic.mbSwapOut) + , mbDummyContext(rImpGraphic.mbDummyContext) + , maVectorGraphicData(std::move(rImpGraphic.maVectorGraphicData)) + , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink) + , maLastUsed (std::chrono::high_resolution_clock::now()) + , mbPrepared (rImpGraphic.mbPrepared) +{ + rImpGraphic.clear(); + rImpGraphic.mbDummyContext = false; +} + +ImpGraphic::ImpGraphic(std::shared_ptr<GfxLink> xGfxLink, sal_Int32 nPageIndex) + : mpGfxLink(std::move(xGfxLink)) + , meType(GraphicType::Bitmap) + , mnSizeBytes(0) + , mbSwapOut(true) + , mbDummyContext(false) + , maLastUsed (std::chrono::high_resolution_clock::now()) + , mbPrepared (false) +{ + maSwapInfo.mbIsTransparent = true; + maSwapInfo.mbIsAlpha = true; + maSwapInfo.mbIsEPS = false; + maSwapInfo.mbIsAnimated = false; + maSwapInfo.mnAnimationLoopCount = 0; + maSwapInfo.mnPageIndex = nPageIndex; +} + +ImpGraphic::ImpGraphic(GraphicExternalLink aGraphicExternalLink) : + meType ( GraphicType::Default ), + mnSizeBytes ( 0 ), + mbSwapOut ( false ), + mbDummyContext ( false ), + maGraphicExternalLink(std::move(aGraphicExternalLink)), + maLastUsed (std::chrono::high_resolution_clock::now()), + mbPrepared (false) +{ +} + +ImpGraphic::ImpGraphic( const BitmapEx& rBitmapEx ) : + maBitmapEx ( rBitmapEx ), + meType ( !rBitmapEx.IsEmpty() ? GraphicType::Bitmap : GraphicType::NONE ), + mnSizeBytes ( 0 ), + mbSwapOut ( false ), + mbDummyContext ( false ), + maLastUsed (std::chrono::high_resolution_clock::now()), + mbPrepared (false) +{ +} + +ImpGraphic::ImpGraphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr) +: meType( rVectorGraphicDataPtr ? GraphicType::Bitmap : GraphicType::NONE ), + mnSizeBytes( 0 ), + mbSwapOut( false ), + mbDummyContext ( false ), + maVectorGraphicData(rVectorGraphicDataPtr), + maLastUsed (std::chrono::high_resolution_clock::now()), + mbPrepared (false) +{ +} + +ImpGraphic::ImpGraphic( const Animation& rAnimation ) : + maBitmapEx ( rAnimation.GetBitmapEx() ), + mpAnimation ( std::make_unique<Animation>( rAnimation ) ), + meType ( GraphicType::Bitmap ), + mnSizeBytes ( 0 ), + mbSwapOut ( false ), + mbDummyContext ( false ), + maLastUsed (std::chrono::high_resolution_clock::now()), + mbPrepared (false) +{ +} + +ImpGraphic::ImpGraphic( const GDIMetaFile& rMtf ) : + maMetaFile ( rMtf ), + meType ( GraphicType::GdiMetafile ), + mnSizeBytes ( 0 ), + mbSwapOut ( false ), + mbDummyContext ( false ), + maLastUsed (std::chrono::high_resolution_clock::now()), + mbPrepared (false) +{ +} + +ImpGraphic::~ImpGraphic() +{ + vcl::graphic::Manager::get().unregisterGraphic(this); +} + +ImpGraphic& ImpGraphic::operator=( const ImpGraphic& rImpGraphic ) +{ + if( &rImpGraphic != this ) + { + sal_Int64 aOldSizeBytes = mnSizeBytes; + + maMetaFile = rImpGraphic.maMetaFile; + meType = rImpGraphic.meType; + mnSizeBytes = rImpGraphic.mnSizeBytes; + + maSwapInfo = rImpGraphic.maSwapInfo; + mpContext = rImpGraphic.mpContext; + mbDummyContext = rImpGraphic.mbDummyContext; + maGraphicExternalLink = rImpGraphic.maGraphicExternalLink; + + mpAnimation.reset(); + + if ( rImpGraphic.mpAnimation ) + { + mpAnimation = std::make_unique<Animation>( *rImpGraphic.mpAnimation ); + maBitmapEx = mpAnimation->GetBitmapEx(); + } + else + { + maBitmapEx = rImpGraphic.maBitmapEx; + } + + mbSwapOut = rImpGraphic.mbSwapOut; + mpSwapFile = rImpGraphic.mpSwapFile; + mbPrepared = rImpGraphic.mbPrepared; + + mpGfxLink = rImpGraphic.mpGfxLink; + + maVectorGraphicData = rImpGraphic.maVectorGraphicData; + maLastUsed = std::chrono::high_resolution_clock::now(); + + vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes); + } + + return *this; +} + +ImpGraphic& ImpGraphic::operator=(ImpGraphic&& rImpGraphic) +{ + sal_Int64 aOldSizeBytes = mnSizeBytes; + + maMetaFile = std::move(rImpGraphic.maMetaFile); + meType = rImpGraphic.meType; + mnSizeBytes = rImpGraphic.mnSizeBytes; + maSwapInfo = std::move(rImpGraphic.maSwapInfo); + mpContext = std::move(rImpGraphic.mpContext); + mbDummyContext = rImpGraphic.mbDummyContext; + mpAnimation = std::move(rImpGraphic.mpAnimation); + maBitmapEx = std::move(rImpGraphic.maBitmapEx); + mbSwapOut = rImpGraphic.mbSwapOut; + mpSwapFile = std::move(rImpGraphic.mpSwapFile); + mpGfxLink = std::move(rImpGraphic.mpGfxLink); + maVectorGraphicData = std::move(rImpGraphic.maVectorGraphicData); + maGraphicExternalLink = rImpGraphic.maGraphicExternalLink; + mbPrepared = rImpGraphic.mbPrepared; + + rImpGraphic.clear(); + rImpGraphic.mbDummyContext = false; + maLastUsed = std::chrono::high_resolution_clock::now(); + + vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes); + + return *this; +} + +bool ImpGraphic::operator==( const ImpGraphic& rOther ) const +{ + if( this == &rOther ) + return true; + + if (mbPrepared && rOther.mbPrepared) + return (*mpGfxLink == *rOther.mpGfxLink); + + if (!isAvailable() || !rOther.isAvailable()) + return false; + + if ( meType != rOther.meType ) + return false; + + bool bRet = false; + switch( meType ) + { + case GraphicType::NONE: + case GraphicType::Default: + return true; + + case GraphicType::GdiMetafile: + return ( rOther.maMetaFile == maMetaFile ); + + case GraphicType::Bitmap: + { + if(maVectorGraphicData) + { + if(maVectorGraphicData == rOther.maVectorGraphicData) + { + // equal instances + bRet = true; + } + else if(rOther.maVectorGraphicData) + { + // equal content + bRet = (*maVectorGraphicData) == (*rOther.maVectorGraphicData); + } + } + else if( mpAnimation ) + { + if( rOther.mpAnimation && ( *rOther.mpAnimation == *mpAnimation ) ) + bRet = true; + } + else if( !rOther.mpAnimation && ( rOther.maBitmapEx == maBitmapEx ) ) + { + bRet = true; + } + } + break; + } + + return bRet; +} + +const std::shared_ptr<VectorGraphicData>& ImpGraphic::getVectorGraphicData() const +{ + ensureAvailable(); + + return maVectorGraphicData; +} + +void ImpGraphic::createSwapInfo() +{ + if (isSwappedOut()) + return; + + if (!maBitmapEx.IsEmpty()) + maSwapInfo.maSizePixel = maBitmapEx.GetSizePixel(); + else + maSwapInfo.maSizePixel = Size(); + + maSwapInfo.maPrefMapMode = getPrefMapMode(); + maSwapInfo.maPrefSize = getPrefSize(); + maSwapInfo.mbIsAnimated = isAnimated(); + maSwapInfo.mbIsEPS = isEPS(); + maSwapInfo.mbIsTransparent = isTransparent(); + maSwapInfo.mbIsAlpha = isAlpha(); + maSwapInfo.mnAnimationLoopCount = getAnimationLoopCount(); + maSwapInfo.mnPageIndex = getPageNumber(); +} + +void ImpGraphic::clearGraphics() +{ + maBitmapEx.Clear(); + maMetaFile.Clear(); + mpAnimation.reset(); + maVectorGraphicData.reset(); +} + +void ImpGraphic::setPrepared(bool bAnimated, const Size* pSizeHint) +{ + mbPrepared = true; + mbSwapOut = true; + meType = GraphicType::Bitmap; + + SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(mpGfxLink->GetData()), mpGfxLink->GetDataSize(), StreamMode::READ | StreamMode::WRITE); + + if (pSizeHint) + { + maSwapInfo.maPrefSize = *pSizeHint; + maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM); + } + + GraphicDescriptor aDescriptor(aMemoryStream, nullptr); + if (aDescriptor.Detect(true)) + { + if (!pSizeHint) + { + // If we have logic size, work with that, as later pixel -> logic + // conversion will work with the output device DPI, not the graphic + // DPI. + Size aLogSize = aDescriptor.GetSize_100TH_MM(); + if (aDescriptor.GetPreferredLogSize() && aDescriptor.GetPreferredMapMode()) + { + maSwapInfo.maPrefSize = *aDescriptor.GetPreferredLogSize(); + maSwapInfo.maPrefMapMode = *aDescriptor.GetPreferredMapMode(); + } + else if (aLogSize.getWidth() && aLogSize.getHeight()) + { + maSwapInfo.maPrefSize = aLogSize; + maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM); + } + else + { + maSwapInfo.maPrefSize = aDescriptor.GetSizePixel(); + maSwapInfo.maPrefMapMode = MapMode(MapUnit::MapPixel); + } + } + + maSwapInfo.maSizePixel = aDescriptor.GetSizePixel(); + maSwapInfo.mbIsTransparent = aDescriptor.IsTransparent(); + maSwapInfo.mbIsAlpha = aDescriptor.IsAlpha(); + } else { + maSwapInfo.mbIsTransparent = false; + maSwapInfo.mbIsAlpha = false; + } + + maSwapInfo.mnAnimationLoopCount = 0; + maSwapInfo.mbIsEPS = false; + maSwapInfo.mbIsAnimated = bAnimated; + + if (maVectorGraphicData) + maSwapInfo.mnPageIndex = maVectorGraphicData->getPageIndex(); +} + +void ImpGraphic::clear() +{ + mpSwapFile.reset(); + mbSwapOut = false; + mbPrepared = false; + + // cleanup + clearGraphics(); + meType = GraphicType::NONE; + sal_Int64 nOldSize = mnSizeBytes; + mnSizeBytes = 0; + vcl::graphic::Manager::get().changeExisting(this, nOldSize); + maGraphicExternalLink.msURL.clear(); +} + +void ImpGraphic::setDefaultType() +{ + clear(); + meType = GraphicType::Default; +} + +bool ImpGraphic::isSupportedGraphic() const +{ + return( meType != GraphicType::NONE ); +} + +bool ImpGraphic::isTransparent() const +{ + bool bRet(true); + + if (mbSwapOut) + { + bRet = maSwapInfo.mbIsTransparent; + } + else if (meType == GraphicType::Bitmap && !maVectorGraphicData) + { + bRet = mpAnimation ? mpAnimation->IsTransparent() : maBitmapEx.IsAlpha(); + } + + return bRet; +} + +bool ImpGraphic::isAlpha() const +{ + bool bRet(false); + + if (mbSwapOut) + { + bRet = maSwapInfo.mbIsAlpha; + } + else if (maVectorGraphicData) + { + bRet = true; + } + else if (meType == GraphicType::Bitmap) + { + bRet = (nullptr == mpAnimation && maBitmapEx.IsAlpha()); + } + + return bRet; +} + +bool ImpGraphic::isAnimated() const +{ + return mbSwapOut ? maSwapInfo.mbIsAnimated : mpAnimation != nullptr; +} + +bool ImpGraphic::isEPS() const +{ + if (mbSwapOut) + return maSwapInfo.mbIsEPS; + + return( ( meType == GraphicType::GdiMetafile ) && + ( maMetaFile.GetActionSize() > 0 ) && + ( maMetaFile.GetAction( 0 )->GetType() == MetaActionType::EPS ) ); +} + +bool ImpGraphic::isAvailable() const +{ + return !mbPrepared && !mbSwapOut; +} + +bool ImpGraphic::makeAvailable() +{ + return ensureAvailable(); +} + +BitmapEx ImpGraphic::getVectorGraphicReplacement() const +{ + BitmapEx aRet = maVectorGraphicData->getReplacement(); + + if (maExPrefSize.getWidth() && maExPrefSize.getHeight()) + { + aRet.SetPrefSize(maExPrefSize); + } + + return aRet; +} + +Bitmap ImpGraphic::getBitmap(const GraphicConversionParameters& rParameters) const +{ + Bitmap aRetBmp; + + ensureAvailable(); + + if( meType == GraphicType::Bitmap ) + { + if(maVectorGraphicData && maBitmapEx.IsEmpty()) + { + // use maBitmapEx as local buffer for rendered svg + const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement(); + } + + const BitmapEx& rRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx ); + + aRetBmp = rRetBmpEx.GetBitmap( COL_WHITE ); + + if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height()) + aRetBmp.Scale(rParameters.getSizePixel()); + } + else if( ( meType != GraphicType::Default ) && isSupportedGraphic() ) + { + if(maBitmapEx.IsEmpty()) + { + // calculate size + ScopedVclPtrInstance< VirtualDevice > aVDev; + Size aDrawSize(aVDev->LogicToPixel(maMetaFile.GetPrefSize(), maMetaFile.GetPrefMapMode())); + + if(rParameters.getSizePixel().Width() && rParameters.getSizePixel().Height()) + { + // apply given size if exists + aDrawSize = rParameters.getSizePixel(); + } + + if(aDrawSize.Width() && aDrawSize.Height() && !rParameters.getUnlimitedSize() + && (aDrawSize.Width() > GRAPHIC_MTFTOBMP_MAXEXT || aDrawSize.Height() > GRAPHIC_MTFTOBMP_MAXEXT)) + { + // limit bitmap size to a maximum of GRAPHIC_MTFTOBMP_MAXEXT x GRAPHIC_MTFTOBMP_MAXEXT + double fWH(static_cast<double>(aDrawSize.Width()) / static_cast<double>(aDrawSize.Height())); + + if(fWH <= 1.0) + { + aDrawSize.setWidth(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT * fWH)); + aDrawSize.setHeight(GRAPHIC_MTFTOBMP_MAXEXT); + } + else + { + aDrawSize.setWidth(GRAPHIC_MTFTOBMP_MAXEXT); + aDrawSize.setHeight(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT / fWH)); + } + } + + // calculate pixel size. Normally, it's the same as aDrawSize, but may + // need to be extended when hairlines are on the right or bottom edge + Size aPixelSize(aDrawSize); + + if(GraphicType::GdiMetafile == getType()) + { + // tdf#126319 Removed correction based on hairline-at-the-extremes of + // the metafile. The task shows that this is no longer sufficient since + // less hairlines get used in general - what is good, but breaks that + // old fix. Anyways, hairlines are a left-over from non-AA times + // when it was not possible to paint lines taller than one pixel. + // This might need to be corrected further using primitives and + // the possibility to get better-quality ranges for correction. For + // now, always add that one pixel. + aPixelSize.setWidth(aPixelSize.getWidth() + 1); + aPixelSize.setHeight(aPixelSize.getHeight() + 1); + } + + if(aVDev->SetOutputSizePixel(aPixelSize)) + { + if(rParameters.getAntiAliase()) + { + aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::Enable); + } + + if(rParameters.getSnapHorVerLines()) + { + aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline); + } + + draw(*aVDev, Point(), aDrawSize); + + // use maBitmapEx as local buffer for rendered metafile + const_cast< ImpGraphic* >(this)->maBitmapEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() ); + } + } + + aRetBmp = maBitmapEx.GetBitmap(); + } + + if( !aRetBmp.IsEmpty() ) + { + aRetBmp.SetPrefMapMode(getPrefMapMode()); + aRetBmp.SetPrefSize(getPrefSize()); + } + + return aRetBmp; +} + +BitmapEx ImpGraphic::getBitmapEx(const GraphicConversionParameters& rParameters) const +{ + BitmapEx aRetBmpEx; + + ensureAvailable(); + + if( meType == GraphicType::Bitmap ) + { + if(maVectorGraphicData && maBitmapEx.IsEmpty()) + { + // use maBitmapEx as local buffer for rendered svg + const_cast< ImpGraphic* >(this)->maBitmapEx = getVectorGraphicReplacement(); + } + + aRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx ); + + if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height()) + { + aRetBmpEx.Scale( + rParameters.getSizePixel(), + BmpScaleFlag::Fast); + } + } + else if( ( meType != GraphicType::Default ) && isSupportedGraphic() ) + { + if(maBitmapEx.IsEmpty()) + { + const ImpGraphic aMonoMask( maMetaFile.GetMonochromeMtf( COL_BLACK ) ); + + // use maBitmapEx as local buffer for rendered metafile + const_cast< ImpGraphic* >(this)->maBitmapEx = BitmapEx(getBitmap(rParameters), aMonoMask.getBitmap(rParameters)); + } + + aRetBmpEx = maBitmapEx; + } + + return aRetBmpEx; +} + +Animation ImpGraphic::getAnimation() const +{ + Animation aAnimation; + + ensureAvailable(); + if( mpAnimation ) + aAnimation = *mpAnimation; + + return aAnimation; +} + +const BitmapEx& ImpGraphic::getBitmapExRef() const +{ + ensureAvailable(); + return maBitmapEx; +} + +const GDIMetaFile& ImpGraphic::getGDIMetaFile() const +{ + ensureAvailable(); + if (!maMetaFile.GetActionSize() + && maVectorGraphicData + && (VectorGraphicDataType::Emf == maVectorGraphicData->getType() + || VectorGraphicDataType::Wmf == maVectorGraphicData->getType())) + { + // If we have a Emf/Wmf VectorGraphic object, we + // need a way to get the Metafile data out of the primitive + // representation. Use a strict virtual hook (MetafileAccessor) + // to access the MetafilePrimitive2D directly. Also see comments in + // XEmfParser about this. + const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > > aSequence(maVectorGraphicData->getPrimitive2DSequence()); + + if (1 == aSequence.size()) + { + // try to cast to MetafileAccessor implementation + const css::uno::Reference< css::graphic::XPrimitive2D > xReference(aSequence[0]); + auto pUnoPrimitive = static_cast< const drawinglayer::primitive2d::UnoPrimitive2D* >(xReference.get()); + if (pUnoPrimitive) + { + const MetafileAccessor* pMetafileAccessor = dynamic_cast< const MetafileAccessor* >(pUnoPrimitive->getBasePrimitive2D().get()); + + if (pMetafileAccessor) + { + // it is a MetafileAccessor implementation, get Metafile + pMetafileAccessor->accessMetafile(const_cast< ImpGraphic* >(this)->maMetaFile); + } + } + } + } + + if (GraphicType::Bitmap == meType && !maMetaFile.GetActionSize()) + { + // #i119735# + // Use the local maMetaFile as container for a metafile-representation + // of the bitmap graphic. This will be done only once, thus be buffered. + // I checked all usages of maMetaFile, it is only used when type is not + // GraphicType::Bitmap. In operator= it will get copied, thus buffering will + // survive copying (change this if not wanted) + ImpGraphic* pThat = const_cast< ImpGraphic* >(this); + + if(maVectorGraphicData && maBitmapEx.IsEmpty()) + { + // use maBitmapEx as local buffer for rendered svg + pThat->maBitmapEx = getVectorGraphicReplacement(); + } + + // #123983# directly create a metafile with the same PrefSize and PrefMapMode + // the bitmap has, this will be an always correct metafile + if(maBitmapEx.IsAlpha()) + { + pThat->maMetaFile.AddAction(new MetaBmpExScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx)); + } + else + { + pThat->maMetaFile.AddAction(new MetaBmpScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx.GetBitmap())); + } + + pThat->maMetaFile.Stop(); + pThat->maMetaFile.WindStart(); + pThat->maMetaFile.SetPrefSize(maBitmapEx.GetPrefSize()); + pThat->maMetaFile.SetPrefMapMode(maBitmapEx.GetPrefMapMode()); + } + + return maMetaFile; +} + +Size ImpGraphic::getSizePixel() const +{ + Size aSize; + + if (isSwappedOut()) + aSize = maSwapInfo.maSizePixel; + else + aSize = getBitmapEx(GraphicConversionParameters()).GetSizePixel(); + + return aSize; +} + +Size ImpGraphic::getPrefSize() const +{ + Size aSize; + + if (isSwappedOut()) + { + aSize = maSwapInfo.maPrefSize; + } + else + { + switch (meType) + { + case GraphicType::Bitmap: + { + if (maVectorGraphicData && maBitmapEx.IsEmpty()) + { + if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight()) + { + // svg not yet buffered in maBitmapEx, return size derived from range + const basegfx::B2DRange& rRange = maVectorGraphicData->getRange(); + +#ifdef MACOSX + // tdf#157680 scale down estimated size of embedded PDF + // For some unknown reason, the embedded PDF sizes + // are 20x larger than expected. This only occurs on + // macOS so possibly there is some special conversion + // from MapUnit::MapPoint to MapUnit::MapTwip elsewhere + // in the code. + if (maVectorGraphicData->getType() == VectorGraphicDataType::Pdf) + aSize = Size(basegfx::fround(rRange.getWidth() / 20.0f), basegfx::fround(rRange.getHeight() / 20.0f)); + else +#endif + aSize = Size(basegfx::fround(rRange.getWidth()), basegfx::fround(rRange.getHeight())); + } + else + { + aSize = maExPrefSize; + } + } + else + { + aSize = maBitmapEx.GetPrefSize(); + + if( !aSize.Width() || !aSize.Height() ) + { + aSize = maBitmapEx.GetSizePixel(); + } + } + } + break; + + case GraphicType::GdiMetafile: + { + aSize = maMetaFile.GetPrefSize(); + } + break; + + case GraphicType::NONE: + case GraphicType::Default: + break; + } + } + + return aSize; +} + +void ImpGraphic::setValuesForPrefSize(const Size& rPrefSize) +{ + switch (meType) + { + case GraphicType::Bitmap: + { + // used when importing a writer FlyFrame with SVG as graphic, added conversion + // to allow setting the PrefSize at the BitmapEx to hold it + if (maVectorGraphicData && maBitmapEx.IsEmpty()) + { + maExPrefSize = rPrefSize; + } + + // #108077# Push through pref size to animation object, + // will be lost on copy otherwise + if (mpAnimation) + { + const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefSize(rPrefSize); + } + + if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight()) + { + maBitmapEx.SetPrefSize(rPrefSize); + } + } + break; + + case GraphicType::GdiMetafile: + { + if (isSupportedGraphic()) + maMetaFile.SetPrefSize(rPrefSize); + } + break; + + case GraphicType::NONE: + case GraphicType::Default: + break; + } +} + +void ImpGraphic::setPrefSize(const Size& rPrefSize) +{ + ensureAvailable(); + setValuesForPrefSize(rPrefSize); +} + +MapMode ImpGraphic::getPrefMapMode() const +{ + MapMode aMapMode; + + if (isSwappedOut()) + { + aMapMode = maSwapInfo.maPrefMapMode; + } + else + { + switch (meType) + { + case GraphicType::Bitmap: + { + if (maVectorGraphicData && maBitmapEx.IsEmpty()) + { + // svg not yet buffered in maBitmapEx, return default PrefMapMode + aMapMode = MapMode(MapUnit::Map100thMM); + } + else + { + const Size aSize(maBitmapEx.GetPrefSize()); + + if (aSize.Width() && aSize.Height()) + aMapMode = maBitmapEx.GetPrefMapMode(); + } + } + break; + + case GraphicType::GdiMetafile: + { + return maMetaFile.GetPrefMapMode(); + } + break; + + case GraphicType::NONE: + case GraphicType::Default: + break; + } + } + + return aMapMode; +} + +void ImpGraphic::setValuesForPrefMapMod(const MapMode& rPrefMapMode) +{ + switch (meType) + { + case GraphicType::Bitmap: + { + if (maVectorGraphicData) + { + // ignore for Vector Graphic Data. If this is really used (except the grfcache) + // it can be extended by using maBitmapEx as buffer for getVectorGraphicReplacement() + } + else + { + // #108077# Push through pref mapmode to animation object, + // will be lost on copy otherwise + if (mpAnimation) + { + const_cast<BitmapEx&>(mpAnimation->GetBitmapEx()).SetPrefMapMode(rPrefMapMode); + } + + maBitmapEx.SetPrefMapMode(rPrefMapMode); + } + } + break; + + case GraphicType::GdiMetafile: + { + maMetaFile.SetPrefMapMode(rPrefMapMode); + } + break; + + case GraphicType::NONE: + case GraphicType::Default: + break; + } +} + +void ImpGraphic::setPrefMapMode(const MapMode& rPrefMapMode) +{ + ensureAvailable(); + setValuesForPrefMapMod(rPrefMapMode); +} + +sal_uLong ImpGraphic::getSizeBytes() const +{ + if (mnSizeBytes > 0) + return mnSizeBytes; + + if (mbPrepared) + ensureAvailable(); + + switch (meType) + { + case GraphicType::Bitmap: + { + if (maVectorGraphicData) + { + std::pair<VectorGraphicData::State, size_t> aPair(maVectorGraphicData->getSizeBytes()); + if (VectorGraphicData::State::UNPARSED == aPair.first) + { + return aPair.second; // don't cache it until Vector Graphic Data is parsed + } + mnSizeBytes = aPair.second; + } + else + { + mnSizeBytes = mpAnimation ? mpAnimation->GetSizeBytes() : maBitmapEx.GetSizeBytes(); + } + } + break; + + case GraphicType::GdiMetafile: + { + mnSizeBytes = maMetaFile.GetSizeBytes(); + } + break; + + case GraphicType::NONE: + case GraphicType::Default: + break; + } + + return mnSizeBytes; +} + +void ImpGraphic::draw(OutputDevice& rOutDev, const Point& rDestPt) const +{ + ensureAvailable(); + + if (isSwappedOut()) + return; + + switch (meType) + { + case GraphicType::Bitmap: + { + if (maVectorGraphicData && maBitmapEx.IsEmpty()) + { + // use maBitmapEx as local buffer for rendered svg + const_cast<ImpGraphic*>(this)->maBitmapEx = getVectorGraphicReplacement(); + } + + if (mpAnimation) + { + mpAnimation->Draw(rOutDev, rDestPt); + } + else + { + maBitmapEx.Draw(&rOutDev, rDestPt); + } + } + break; + + case GraphicType::GdiMetafile: + { + draw(rOutDev, rDestPt, maMetaFile.GetPrefSize()); + } + break; + + case GraphicType::Default: + case GraphicType::NONE: + break; + } +} + +void ImpGraphic::draw(OutputDevice& rOutDev, + const Point& rDestPt, const Size& rDestSize) const +{ + ensureAvailable(); + + if (isSwappedOut()) + return; + + switch (meType) + { + case GraphicType::Bitmap: + { + if (maVectorGraphicData && maBitmapEx.IsEmpty()) + { + // use maBitmapEx as local buffer for rendered svg + const_cast<ImpGraphic*>(this)->maBitmapEx = getVectorGraphicReplacement(); + } + + if (mpAnimation) + { + mpAnimation->Draw(rOutDev, rDestPt, rDestSize); + } + else + { + maBitmapEx.Draw(&rOutDev, rDestPt, rDestSize); + } + } + break; + + case GraphicType::GdiMetafile: + { + const_cast<ImpGraphic*>(this)->maMetaFile.WindStart(); + const_cast<ImpGraphic*>(this)->maMetaFile.Play(rOutDev, rDestPt, rDestSize); + const_cast<ImpGraphic*>(this)->maMetaFile.WindStart(); + } + break; + + case GraphicType::Default: + case GraphicType::NONE: + break; + } +} + +void ImpGraphic::startAnimation(OutputDevice& rOutDev, const Point& rDestPt, + const Size& rDestSize, tools::Long nRendererId, + OutputDevice* pFirstFrameOutDev ) +{ + ensureAvailable(); + + if( isSupportedGraphic() && !isSwappedOut() && mpAnimation ) + mpAnimation->Start(rOutDev, rDestPt, rDestSize, nRendererId, pFirstFrameOutDev); +} + +void ImpGraphic::stopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId ) +{ + ensureAvailable(); + + if( isSupportedGraphic() && !isSwappedOut() && mpAnimation ) + mpAnimation->Stop( pOutDev, nRendererId ); +} + +void ImpGraphic::setAnimationNotifyHdl( const Link<Animation*,void>& rLink ) +{ + ensureAvailable(); + + if( mpAnimation ) + mpAnimation->SetNotifyHdl( rLink ); +} + +Link<Animation*,void> ImpGraphic::getAnimationNotifyHdl() const +{ + Link<Animation*,void> aLink; + + ensureAvailable(); + + if( mpAnimation ) + aLink = mpAnimation->GetNotifyHdl(); + + return aLink; +} + +sal_uInt32 ImpGraphic::getAnimationLoopCount() const +{ + if (mbSwapOut) + return maSwapInfo.mnAnimationLoopCount; + + return mpAnimation ? mpAnimation->GetLoopCount() : 0; +} + +void ImpGraphic::setContext( const std::shared_ptr<GraphicReader>& pReader ) +{ + mpContext = pReader; + mbDummyContext = false; +} + +bool ImpGraphic::swapInContent(SvStream& rStream) +{ + bool bRet = false; + + sal_uInt32 nId; + sal_Int32 nType; + sal_Int32 nLength; + + rStream.ReadUInt32(nId); + + // check version + if (SWAP_FORMAT_ID != nId) + { + SAL_WARN("vcl", "Incompatible swap file!"); + return false; + } + + rStream.ReadInt32(nType); + rStream.ReadInt32(nLength); + + meType = static_cast<GraphicType>(nType); + + if (meType == GraphicType::NONE || meType == GraphicType::Default) + { + return true; + } + else + { + bRet = swapInGraphic(rStream); + } + + return bRet; +} + +bool ImpGraphic::swapOutGraphic(SvStream& rStream) +{ + if (rStream.GetError()) + return false; + + ensureAvailable(); + + if (isSwappedOut()) + { + rStream.SetError(SVSTREAM_GENERALERROR); + return false; + } + + switch (meType) + { + case GraphicType::GdiMetafile: + { + if(!rStream.GetError()) + { + SvmWriter aWriter(rStream); + aWriter.Write(maMetaFile); + } + } + break; + + case GraphicType::Bitmap: + { + if (maVectorGraphicData) + { + rStream.WriteInt32(sal_Int32(GraphicContentType::Vector)); + // stream out Vector Graphic defining data (length, byte array and evtl. path) + // this is used e.g. in swapping out graphic data and in transporting it over UNO API + // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be + // no problem to extend it; only used at runtime + switch (maVectorGraphicData->getType()) + { + case VectorGraphicDataType::Wmf: + { + rStream.WriteUInt32(constWmfMagic); + break; + } + case VectorGraphicDataType::Emf: + { + rStream.WriteUInt32(constEmfMagic); + break; + } + case VectorGraphicDataType::Svg: + { + rStream.WriteUInt32(constSvgMagic); + break; + } + case VectorGraphicDataType::Pdf: + { + rStream.WriteUInt32(constPdfMagic); + break; + } + } + + rStream.WriteUInt32(maVectorGraphicData->getBinaryDataContainer().getSize()); + maVectorGraphicData->getBinaryDataContainer().writeToStream(rStream); + } + else if (mpAnimation) + { + rStream.WriteInt32(sal_Int32(GraphicContentType::Animation)); + WriteAnimation(rStream, *mpAnimation); + } + else + { + rStream.WriteInt32(sal_Int32(GraphicContentType::Bitmap)); + WriteDIBBitmapEx(maBitmapEx, rStream); + } + } + break; + + case GraphicType::NONE: + case GraphicType::Default: + break; + } + + if (mpGfxLink) + mpGfxLink->getDataContainer().swapOut(); + + return true; +} + +bool ImpGraphic::swapOutContent(SvStream& rStream) +{ + ensureAvailable(); + + bool bRet = false; + + if (meType == GraphicType::NONE || meType == GraphicType::Default || isSwappedOut()) + return false; + + sal_uLong nDataFieldPos; + + // Write the SWAP ID + rStream.WriteUInt32(SWAP_FORMAT_ID); + + rStream.WriteInt32(static_cast<sal_Int32>(meType)); + + // data size is updated later + nDataFieldPos = rStream.Tell(); + rStream.WriteInt32(0); + + // write data block + const sal_uInt64 nDataStart = rStream.Tell(); + + swapOutGraphic(rStream); + + if (!rStream.GetError()) + { + // Write the written length th the header + const sal_uInt64 nCurrentPosition = rStream.Tell(); + rStream.Seek(nDataFieldPos); + rStream.WriteInt32(nCurrentPosition - nDataStart); + rStream.Seek(nCurrentPosition); + bRet = true; + } + + return bRet; +} + +bool ImpGraphic::swapOut() +{ + if (isSwappedOut()) + return false; + + bool bResult = false; + + sal_Int64 nByteSize = getSizeBytes(); + + // We have GfxLink so we have the source available + if (mpGfxLink && mpGfxLink->IsNative()) + { + createSwapInfo(); + + clearGraphics(); + + // reset the swap file + mpSwapFile.reset(); + + mpGfxLink->getDataContainer().swapOut(); + + // mark as swapped out + mbSwapOut = true; + + bResult = true; + } + else + { + // Create a swap file + auto pSwapFile = o3tl::make_shared<ImpSwapFile>(getOriginURL()); + + // Open a stream to write the swap file to + { + SvStream* pOutputStream = pSwapFile->getStream(); + + if (!pOutputStream) + return false; + + // Write to stream + pOutputStream->SetVersion(SOFFICE_FILEFORMAT_50); + pOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE); + pOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE); + + if (!pOutputStream->GetError() && swapOutContent(*pOutputStream)) + { + pOutputStream->FlushBuffer(); + bResult = !pOutputStream->GetError(); + } + } + + // Check if writing was successful + if (bResult) + { + // We have swapped out, so can clean memory and prepare swap info + createSwapInfo(); + clearGraphics(); + + mpSwapFile = std::move(pSwapFile); + mbSwapOut = true; + } + } + + if (bResult) + { + // Signal to manager that we have swapped out + vcl::graphic::Manager::get().swappedOut(this, nByteSize); + } + + return bResult; +} + +bool ImpGraphic::ensureAvailable() const +{ + auto pThis = const_cast<ImpGraphic*>(this); + + bool bResult = true; + + if (isSwappedOut()) + bResult = pThis->swapIn(); + + pThis->maLastUsed = std::chrono::high_resolution_clock::now(); + return bResult; +} + +void ImpGraphic::updateFromLoadedGraphic(const ImpGraphic* pGraphic) +{ + if (mbPrepared) + { + GraphicExternalLink aLink = maGraphicExternalLink; + Size aPrefSize = maSwapInfo.maPrefSize; + MapMode aPrefMapMode = maSwapInfo.maPrefMapMode; + *this = *pGraphic; + if (aPrefSize.getWidth() && aPrefSize.getHeight() && aPrefMapMode == getPrefMapMode()) + { + // Use custom preferred size if it was set when the graphic was still unloaded. + // Only set the size in case the unloaded and loaded unit matches. + setPrefSize(aPrefSize); + } + maGraphicExternalLink = aLink; + } + else + { + // Move over only graphic content + mpAnimation.reset(); + if (pGraphic->mpAnimation) + { + mpAnimation = std::make_unique<Animation>(*pGraphic->mpAnimation); + maBitmapEx = mpAnimation->GetBitmapEx(); + } + else + { + maBitmapEx = pGraphic->maBitmapEx; + } + + maMetaFile = pGraphic->maMetaFile; + maVectorGraphicData = pGraphic->maVectorGraphicData; + + // Set to 0, to force recalculation + mnSizeBytes = 0; + mnChecksum = 0; + + restoreFromSwapInfo(); + + mbSwapOut = false; + } +} + +void ImpGraphic::dumpState(rtl::OStringBuffer &rState) +{ + if (meType == GraphicType::NONE && mnSizeBytes == 0) + return; // uninteresting. + + rState.append("\n\t"); + + if (mbSwapOut) + rState.append("swapped\t"); + else + rState.append("loaded\t"); + + rState.append(static_cast<sal_Int32>(meType)); + rState.append("\tsize:\t"); + rState.append(static_cast<sal_Int64>(mnSizeBytes)); + rState.append("\tgfxl:\t"); + rState.append(static_cast<sal_Int64>(mpGfxLink ? mpGfxLink->getSizeBytes() : -1)); + rState.append("\t"); + rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Width())); + rState.append("x"); + rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Height())); + rState.append("\t"); + rState.append(static_cast<sal_Int32>(maExPrefSize.Width())); + rState.append("x"); + rState.append(static_cast<sal_Int32>(maExPrefSize.Height())); +} + +void ImpGraphic::restoreFromSwapInfo() +{ + setValuesForPrefMapMod(maSwapInfo.maPrefMapMode); + setValuesForPrefSize(maSwapInfo.maPrefSize); + + if (maVectorGraphicData) + { + maVectorGraphicData->setPageIndex(maSwapInfo.mnPageIndex); + } +} + +namespace +{ + +std::optional<VectorGraphicDataType> lclConvertToVectorGraphicType(GfxLink const & rLink) +{ + switch(rLink.GetType()) + { + case GfxLinkType::NativePdf: + return VectorGraphicDataType::Pdf; + + case GfxLinkType::NativeWmf: + if (rLink.IsEMF()) + return VectorGraphicDataType::Emf; + else + return VectorGraphicDataType::Wmf; + + case GfxLinkType::NativeSvg: + return VectorGraphicDataType::Svg; + + default: + break; + } + return std::optional<VectorGraphicDataType>(); +} + +} // end namespace + +bool ImpGraphic::swapIn() +{ + if (!isSwappedOut()) + return false; + + bool bReturn = false; + + if (mbPrepared) + { + Graphic aGraphic; + if (!mpGfxLink->LoadNative(aGraphic)) + return false; + + updateFromLoadedGraphic(aGraphic.ImplGetImpGraphic()); + + maLastUsed = std::chrono::high_resolution_clock::now(); + bReturn = true; + } + else if (mpGfxLink && mpGfxLink->IsNative()) + { + std::optional<VectorGraphicDataType> oType = lclConvertToVectorGraphicType(*mpGfxLink); + if (oType) + { + maVectorGraphicData = vcl::loadVectorGraphic(mpGfxLink->getDataContainer(), *oType); + + // Set to 0, to force recalculation + mnSizeBytes = 0; + mnChecksum = 0; + + restoreFromSwapInfo(); + + mbSwapOut = false; + } + else + { + Graphic aGraphic; + if (!mpGfxLink->LoadNative(aGraphic)) + return false; + + ImpGraphic* pImpGraphic = aGraphic.ImplGetImpGraphic(); + if (meType != pImpGraphic->meType) + return false; + + updateFromLoadedGraphic(pImpGraphic); + } + + maLastUsed = std::chrono::high_resolution_clock::now(); + bReturn = true; + } + else + { + SvStream* pStream = nullptr; + + if (mpSwapFile) + pStream = mpSwapFile->getStream(); + + if (pStream) + { + pStream->SetVersion(SOFFICE_FILEFORMAT_50); + pStream->SetCompressMode(SvStreamCompressFlags::NATIVE); + pStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE); + pStream->Seek(STREAM_SEEK_TO_BEGIN); + + bReturn = swapInFromStream(*pStream); + + restoreFromSwapInfo(); + + setOriginURL(mpSwapFile->getOriginURL()); + + mpSwapFile.reset(); + } + } + + if (bReturn) + { + vcl::graphic::Manager::get().swappedIn(this, getSizeBytes()); + } + + return bReturn; +} + +bool ImpGraphic::swapInFromStream(SvStream& rStream) +{ + bool bRet = false; + + if (rStream.GetError()) + return false; + + clearGraphics(); + mnSizeBytes = 0; + mnChecksum = 0; + + bRet = swapInContent(rStream); + + if (!bRet) + { + //throw away swapfile, etc. + clear(); + } + + mbSwapOut = false; + + return bRet; +} + +bool ImpGraphic::swapInGraphic(SvStream& rStream) +{ + bool bReturn = false; + + if (rStream.GetError()) + return bReturn; + + if (meType == GraphicType::Bitmap) + { + sal_Int32 nContentType = -1; + rStream.ReadInt32(nContentType); + if (nContentType < 0) + return false; + + auto eContentType = static_cast<GraphicContentType>(nContentType); + + switch (eContentType) + { + case GraphicContentType::Bitmap: + { + BitmapEx aBitmapEx; + ReadDIBBitmapEx(aBitmapEx, rStream); + if (!rStream.GetError()) + { + maBitmapEx = aBitmapEx; + bReturn = true; + } + } + break; + + case GraphicContentType::Animation: + { + auto pAnimation = std::make_unique<Animation>(); + ReadAnimation(rStream, *pAnimation); + if (!rStream.GetError()) + { + mpAnimation = std::move(pAnimation); + maBitmapEx = mpAnimation->GetBitmapEx(); + bReturn = true; + } + } + break; + + case GraphicContentType::Vector: + { + // try to stream in Svg defining data (length, byte array and evtl. path) + // See below (operator<<) for more information + sal_uInt32 nMagic; + rStream.ReadUInt32(nMagic); + + if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic || constPdfMagic == nMagic) + { + sal_uInt32 nVectorGraphicDataSize(0); + rStream.ReadUInt32(nVectorGraphicDataSize); + + if (nVectorGraphicDataSize) + { + BinaryDataContainer aDataContainer(rStream, nVectorGraphicDataSize); + + if (rStream.GetError()) + return false; + + VectorGraphicDataType aDataType; + + switch (nMagic) + { + case constSvgMagic: + aDataType = VectorGraphicDataType::Svg; + break; + case constWmfMagic: + aDataType = VectorGraphicDataType::Wmf; + break; + case constEmfMagic: + aDataType = VectorGraphicDataType::Emf; + break; + case constPdfMagic: + aDataType = VectorGraphicDataType::Pdf; + break; + default: + return false; + } + + auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, aDataType); + + if (!rStream.GetError()) + { + maVectorGraphicData = aVectorGraphicDataPtr; + bReturn = true; + } + } + } + } + break; + } + } + else if (meType == GraphicType::GdiMetafile) + { + GDIMetaFile aMetaFile; + SvmReader aReader(rStream); + aReader.Read(aMetaFile); + if (!rStream.GetError()) + { + maMetaFile = aMetaFile; + bReturn = true; + } + } + return bReturn; +} + +void ImpGraphic::setGfxLink(const std::shared_ptr<GfxLink>& rGfxLink) +{ + ensureAvailable(); + + mpGfxLink = rGfxLink; +} + +const std::shared_ptr<GfxLink> & ImpGraphic::getSharedGfxLink() const +{ + return mpGfxLink; +} + +GfxLink ImpGraphic::getGfxLink() const +{ + ensureAvailable(); + + return( mpGfxLink ? *mpGfxLink : GfxLink() ); +} + +bool ImpGraphic::isGfxLink() const +{ + return ( bool(mpGfxLink) ); +} + +BitmapChecksum ImpGraphic::getChecksum() const +{ + if (mnChecksum != 0) + return mnChecksum; + + ensureAvailable(); + + switch (meType) + { + case GraphicType::NONE: + case GraphicType::Default: + break; + + case GraphicType::Bitmap: + { + if (maVectorGraphicData) + mnChecksum = maVectorGraphicData->GetChecksum(); + else if (mpAnimation) + mnChecksum = mpAnimation->GetChecksum(); + else + mnChecksum = maBitmapEx.GetChecksum(); + } + break; + + case GraphicType::GdiMetafile: + { + mnChecksum = SvmWriter::GetChecksum(maMetaFile); + } + break; + } + return mnChecksum; +} + +sal_Int32 ImpGraphic::getPageNumber() const +{ + if (isSwappedOut()) + return maSwapInfo.mnPageIndex; + + if (maVectorGraphicData) + return maVectorGraphicData->getPageIndex(); + return -1; +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/jobset.cxx b/vcl/source/gdi/jobset.cxx new file mode 100644 index 0000000000..19fa712ea6 --- /dev/null +++ b/vcl/source/gdi/jobset.cxx @@ -0,0 +1,421 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <tools/solar.h> +#include <tools/stream.hxx> +#include <vcl/jobset.hxx> +#include <jobset.h> +#include <memory> + +#define JOBSET_FILE364_SYSTEM (sal_uInt16(0xFFFF)) +#define JOBSET_FILE605_SYSTEM (sal_uInt16(0xFFFE)) + +namespace { + +// used only for compatibility with older versions, +// printer/driver name are truncated if too long, +// device name and port name are completely unused +struct ImplOldJobSetupData +{ + char cPrinterName[64]; + char cDeviceName[32]; + char cPortName[32]; + char cDriverName[32]; +}; + +struct Impl364JobSetupData +{ + SVBT16 nSize; + SVBT16 nSystem; + SVBT32 nDriverDataLen; + SVBT16 nOrientation; + SVBT16 nPaperBin; + SVBT16 nPaperFormat; + SVBT32 nPaperWidth; + SVBT32 nPaperHeight; +}; + +} + +ImplJobSetup::ImplJobSetup() +{ + mnSystem = 0; + meOrientation = Orientation::Portrait; + meDuplexMode = DuplexMode::Unknown; + mnPaperBin = 0; + mePaperFormat = PAPER_USER; + mnPaperWidth = 0; + mnPaperHeight = 0; + mnDriverDataLen = 0; + mbPapersizeFromSetup = false; + meSetupMode = PrinterSetupMode::DocumentGlobal; +} + +ImplJobSetup::ImplJobSetup( const ImplJobSetup& rJobSetup ) : + mnSystem( rJobSetup.GetSystem() ), + maPrinterName( rJobSetup.GetPrinterName() ), + maDriver( rJobSetup.GetDriver() ), + meOrientation( rJobSetup.GetOrientation() ), + meDuplexMode( rJobSetup.GetDuplexMode() ), + mnPaperBin( rJobSetup.GetPaperBin() ), + mePaperFormat( rJobSetup.GetPaperFormat() ), + mnPaperWidth( rJobSetup.GetPaperWidth() ), + mnPaperHeight( rJobSetup.GetPaperHeight() ), + mnDriverDataLen( rJobSetup.GetDriverDataLen() ), + mpDriverData(), + mbPapersizeFromSetup( rJobSetup.GetPapersizeFromSetup() ), + meSetupMode( rJobSetup.GetPrinterSetupMode() ), + maValueMap( rJobSetup.GetValueMap() ) + { + if ( rJobSetup.GetDriverData() ) + { + mpDriverData.reset( new sal_uInt8[mnDriverDataLen] ); + memcpy( mpDriverData.get(), rJobSetup.GetDriverData(), mnDriverDataLen ); + } + else + mpDriverData = nullptr; +} + +ImplJobSetup::~ImplJobSetup() +{ +} + +void ImplJobSetup::SetSystem(sal_uInt16 nSystem) +{ + mnSystem = nSystem; +} + +void ImplJobSetup::SetPrinterName(const OUString& rPrinterName) +{ + maPrinterName = rPrinterName; +} + +void ImplJobSetup::SetDriver(const OUString& rDriver) +{ + maDriver = rDriver; +} + +void ImplJobSetup::SetOrientation(Orientation eOrientation) +{ + meOrientation = eOrientation; +} + +void ImplJobSetup::SetDuplexMode(DuplexMode eDuplexMode) +{ + meDuplexMode = eDuplexMode; +} + +void ImplJobSetup::SetPaperBin(sal_uInt16 nPaperBin) +{ + mnPaperBin = nPaperBin; +} + +void ImplJobSetup::SetPaperFormat(Paper ePaperFormat) +{ + mePaperFormat = ePaperFormat; +} + +void ImplJobSetup::SetPaperWidth(tools::Long nPaperWidth) +{ + mnPaperWidth = nPaperWidth; +} + +void ImplJobSetup::SetPaperHeight(tools::Long nPaperHeight) +{ + mnPaperHeight = nPaperHeight; +} + +void ImplJobSetup::SetDriverData(std::unique_ptr<sal_uInt8[]> pDriverData, sal_uInt32 nDriverDataLen) +{ + mpDriverData = std::move(pDriverData); + mnDriverDataLen = nDriverDataLen; +} + +void ImplJobSetup::SetPapersizeFromSetup(bool bPapersizeFromSetup) +{ + mbPapersizeFromSetup = bPapersizeFromSetup; +} + +void ImplJobSetup::SetPrinterSetupMode(PrinterSetupMode eMode) +{ + meSetupMode = eMode; +} + +void ImplJobSetup::SetValueMap( const OUString& rKey, const OUString& rValue ) +{ + maValueMap [ rKey ] = rValue; +} + +JobSetup& JobSetup::operator=( const JobSetup& ) = default; + +JobSetup& JobSetup::operator=( JobSetup&& ) = default; + +bool ImplJobSetup::operator==( const ImplJobSetup& rImplJobSetup ) const +{ + return mnSystem == rImplJobSetup.mnSystem && + maPrinterName == rImplJobSetup.maPrinterName && + maDriver == rImplJobSetup.maDriver && + meOrientation == rImplJobSetup.meOrientation && + meDuplexMode == rImplJobSetup.meDuplexMode && + mnPaperBin == rImplJobSetup.mnPaperBin && + mePaperFormat == rImplJobSetup.mePaperFormat && + mnPaperWidth == rImplJobSetup.mnPaperWidth && + mnPaperHeight == rImplJobSetup.mnPaperHeight && + mbPapersizeFromSetup == rImplJobSetup.mbPapersizeFromSetup && + mnDriverDataLen == rImplJobSetup.mnDriverDataLen && + maValueMap == rImplJobSetup.maValueMap && + memcmp( mpDriverData.get(), + rImplJobSetup.mpDriverData.get(), + std::min(mnDriverDataLen, rImplJobSetup.mnDriverDataLen)) == 0; +} + +namespace +{ + JobSetup::ImplType& GetGlobalDefault() + { + static JobSetup::ImplType gDefault; + return gDefault; + } +} + +JobSetup::JobSetup() : mpData(GetGlobalDefault()) +{ +} + +JobSetup::JobSetup( const JobSetup& ) = default; + +JobSetup::~JobSetup() = default; + +bool JobSetup::operator==( const JobSetup& rJobSetup ) const +{ + return mpData == rJobSetup.mpData; +} + +const ImplJobSetup& JobSetup::ImplGetConstData() const +{ + return *mpData; +} + +ImplJobSetup& JobSetup::ImplGetData() +{ + return *mpData; +} + +OUString const & JobSetup::GetPrinterName() const +{ + return mpData->GetPrinterName(); +} + +bool JobSetup::IsDefault() const +{ + return mpData.same_object(GetGlobalDefault()); +} + +SvStream& ReadJobSetup( SvStream& rIStream, JobSetup& rJobSetup ) +{ + { + sal_uInt16 nLen = 0; + rIStream.ReadUInt16( nLen ); + if (nLen <= 4) + return rIStream; + + sal_uInt16 nSystem = 0; + rIStream.ReadUInt16( nSystem ); + size_t nRead = nLen - sizeof(nLen) - sizeof(nSystem); + if (nRead > rIStream.remainingSize()) + { + SAL_WARN("vcl", "Parsing error: " << rIStream.remainingSize() << + " max possible entries, but " << nRead << " claimed, truncating"); + return rIStream; + } + sal_uInt64 const nFirstPos = rIStream.Tell(); + std::unique_ptr<char[]> pTempBuf(new char[nRead]); + nRead = rIStream.ReadBytes(pTempBuf.get(), nRead); + if (nRead >= sizeof(ImplOldJobSetupData)) + { + ImplOldJobSetupData* pData = reinterpret_cast<ImplOldJobSetupData*>(pTempBuf.get()); + + rtl_TextEncoding aStreamEncoding = RTL_TEXTENCODING_UTF8; + if( nSystem == JOBSET_FILE364_SYSTEM ) + aStreamEncoding = rIStream.GetStreamCharSet(); + + ImplJobSetup& rJobData = rJobSetup.ImplGetData(); + + // use (potentially truncated) printer/driver name from ImplOldJobSetupData as fallback, + // gets overwritten below if PRINTER_NAME/DRIVER_NAME keys are set + pData->cPrinterName[std::size(pData->cPrinterName) - 1] = 0; + rJobData.SetPrinterName( OStringToOUString(pData->cPrinterName, aStreamEncoding) ); + pData->cDriverName[std::size(pData->cDriverName) - 1] = 0; + rJobData.SetDriver( OStringToOUString(pData->cDriverName, aStreamEncoding) ); + + // Are these our new JobSetup files? + if ( nSystem == JOBSET_FILE364_SYSTEM || + nSystem == JOBSET_FILE605_SYSTEM ) + { + if (nRead < sizeof(ImplOldJobSetupData) + sizeof(Impl364JobSetupData)) + { + SAL_WARN("vcl", "Parsing error: " << sizeof(ImplOldJobSetupData) + sizeof(Impl364JobSetupData) << + " required, but " << nRead << " available"); + return rIStream; + } + + Impl364JobSetupData* pOldJobData = reinterpret_cast<Impl364JobSetupData*>(pTempBuf.get() + sizeof( ImplOldJobSetupData )); + sal_uInt16 nOldJobDataSize = SVBT16ToUInt16( pOldJobData->nSize ); + rJobData.SetSystem( SVBT16ToUInt16( pOldJobData->nSystem ) ); + const sal_uInt32 nDriverDataLen = SVBT32ToUInt32( pOldJobData->nDriverDataLen ); + rJobData.SetOrientation( static_cast<Orientation>(SVBT16ToUInt16( pOldJobData->nOrientation )) ); + rJobData.SetDuplexMode( DuplexMode::Unknown ); + rJobData.SetPaperBin( SVBT16ToUInt16( pOldJobData->nPaperBin ) ); + sal_uInt16 nPaperFormat = SVBT16ToUInt16(pOldJobData->nPaperFormat); + if (nPaperFormat < NUM_PAPER_ENTRIES) + rJobData.SetPaperFormat(static_cast<Paper>(nPaperFormat)); + else + { + SAL_WARN("vcl", "Parsing error: " << nPaperFormat << + " paper format, but legal max is " << NUM_PAPER_ENTRIES); + } + rJobData.SetPaperWidth( static_cast<tools::Long>(SVBT32ToUInt32( pOldJobData->nPaperWidth )) ); + rJobData.SetPaperHeight( static_cast<tools::Long>(SVBT32ToUInt32( pOldJobData->nPaperHeight )) ); + if ( nDriverDataLen ) + { + const char* pDriverData = reinterpret_cast<const char*>(pOldJobData) + nOldJobDataSize; + const char* pDriverDataEnd = pDriverData + nDriverDataLen; + if (pDriverDataEnd > pTempBuf.get() + nRead) + { + SAL_WARN("vcl", "corrupted job setup"); + } + else + { + auto pNewDriverData = std::make_unique<sal_uInt8[]>( nDriverDataLen ); + memcpy( pNewDriverData.get(), pDriverData, nDriverDataLen ); + rJobData.SetDriverData( std::move(pNewDriverData), nDriverDataLen ); + } + } + if( nSystem == JOBSET_FILE605_SYSTEM ) + { + rIStream.Seek( nFirstPos + sizeof( ImplOldJobSetupData ) + + sizeof( Impl364JobSetupData ) + rJobData.GetDriverDataLen() ); + while( rIStream.Tell() < nFirstPos + nRead ) + { + OUString aKey = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStream, RTL_TEXTENCODING_UTF8); + OUString aValue = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStream, RTL_TEXTENCODING_UTF8); + if( aKey == "COMPAT_DUPLEX_MODE" ) + { + if( aValue == "DuplexMode::Unknown" ) + rJobData.SetDuplexMode( DuplexMode::Unknown ); + else if( aValue == "DuplexMode::Off" ) + rJobData.SetDuplexMode( DuplexMode::Off ); + else if( aValue == "DuplexMode::ShortEdge" ) + rJobData.SetDuplexMode( DuplexMode::ShortEdge ); + else if( aValue == "DuplexMode::LongEdge" ) + rJobData.SetDuplexMode( DuplexMode::LongEdge ); + } + else if (aKey == u"PRINTER_NAME") + rJobData.SetPrinterName(aValue); + else if (aKey == u"DRIVER_NAME") + rJobData.SetDriver(aValue); + else + rJobData.SetValueMap(aKey, aValue); + } + SAL_WARN_IF( rIStream.Tell() != nFirstPos+nRead, "vcl", "corrupted job setup" ); + // ensure correct stream position + rIStream.Seek(nFirstPos + nRead); + } + } + } + } + + return rIStream; +} + +SvStream& WriteJobSetup( SvStream& rOStream, const JobSetup& rJobSetup ) +{ + { + sal_uInt16 nLen = 0; + if ( rJobSetup.IsDefault() ) + rOStream.WriteUInt16( nLen ); + else + { + const ImplJobSetup& rJobData = rJobSetup.ImplGetConstData(); + Impl364JobSetupData aOldJobData; + sal_uInt16 nOldJobDataSize = sizeof( aOldJobData ); + ShortToSVBT16( nOldJobDataSize, aOldJobData.nSize ); + ShortToSVBT16( rJobData.GetSystem(), aOldJobData.nSystem ); + UInt32ToSVBT32( rJobData.GetDriverDataLen(), aOldJobData.nDriverDataLen ); + ShortToSVBT16( static_cast<sal_uInt16>(rJobData.GetOrientation()), aOldJobData.nOrientation ); + ShortToSVBT16( rJobData.GetPaperBin(), aOldJobData.nPaperBin ); + ShortToSVBT16( static_cast<sal_uInt16>(rJobData.GetPaperFormat()), aOldJobData.nPaperFormat ); + UInt32ToSVBT32( static_cast<sal_uLong>(rJobData.GetPaperWidth()), aOldJobData.nPaperWidth ); + UInt32ToSVBT32( static_cast<sal_uLong>(rJobData.GetPaperHeight()), aOldJobData.nPaperHeight ); + + ImplOldJobSetupData aOldData = {}; + OString aPrnByteName(OUStringToOString(rJobData.GetPrinterName(), RTL_TEXTENCODING_UTF8)); + strncpy(aOldData.cPrinterName, aPrnByteName.getStr(), std::size(aOldData.cPrinterName) - 1); + OString aDriverByteName(OUStringToOString(rJobData.GetDriver(), RTL_TEXTENCODING_UTF8)); + strncpy(aOldData.cDriverName, aDriverByteName.getStr(), std::size(aOldData.cDriverName) - 1); + int nPos = rOStream.Tell(); + rOStream.WriteUInt16( 0 ); + rOStream.WriteUInt16( JOBSET_FILE605_SYSTEM ); + rOStream.WriteBytes( &aOldData, sizeof( aOldData ) ); + rOStream.WriteBytes( &aOldJobData, nOldJobDataSize ); + rOStream.WriteBytes( rJobData.GetDriverData(), rJobData.GetDriverDataLen() ); + + const std::unordered_map< OUString, OUString >& rValueMap( + rJobData.GetValueMap()); + + for (auto const& value : rValueMap) + { + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, value.first, RTL_TEXTENCODING_UTF8); + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, value.second, RTL_TEXTENCODING_UTF8); + } + write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "COMPAT_DUPLEX_MODE"); + switch( rJobData.GetDuplexMode() ) + { + case DuplexMode::Unknown: + write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::Unknown"); + break; + case DuplexMode::Off: + write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::Off"); + break; + case DuplexMode::ShortEdge: + write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::ShortEdge"); + break; + case DuplexMode::LongEdge: + write_uInt16_lenPrefixed_uInt8s_FromOString(rOStream, "DuplexMode::LongEdge"); + break; + } + // write printer, driver name in full, the ones in aOldData may be truncated + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, u"PRINTER_NAME", RTL_TEXTENCODING_UTF8); + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, rJobData.GetPrinterName(), RTL_TEXTENCODING_UTF8); + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, u"DRIVER_NAME", RTL_TEXTENCODING_UTF8); + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStream, rJobData.GetDriver(), RTL_TEXTENCODING_UTF8); + + nLen = sal::static_int_cast<sal_uInt16>(rOStream.Tell() - nPos); + rOStream.Seek( nPos ); + rOStream.WriteUInt16( nLen ); + rOStream.Seek( nPos + nLen ); + } + } + + return rOStream; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/lineinfo.cxx b/vcl/source/gdi/lineinfo.cxx new file mode 100644 index 0000000000..490b1d920d --- /dev/null +++ b/vcl/source/gdi/lineinfo.cxx @@ -0,0 +1,293 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <vcl/lineinfo.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <numeric> + + +ImplLineInfo::ImplLineInfo() + : mnWidth(0) + , mnDashLen(0) + , mnDotLen(0) + , mnDistance(0) + , meLineJoin(basegfx::B2DLineJoin::Round) + , meLineCap(css::drawing::LineCap_BUTT) + , meStyle(LineStyle::Solid) + , mnDashCount(0) + , mnDotCount(0) +{ +} + +inline bool ImplLineInfo::operator==( const ImplLineInfo& rB ) const +{ + return(meStyle == rB.meStyle + && mnWidth == rB.mnWidth + && mnDashCount == rB.mnDashCount + && mnDashLen == rB.mnDashLen + && mnDotCount == rB.mnDotCount + && mnDotLen == rB.mnDotLen + && mnDistance == rB.mnDistance + && meLineJoin == rB.meLineJoin + && meLineCap == rB.meLineCap); +} + + +LineInfo::LineInfo( LineStyle eStyle, double nWidth ) +{ + mpImplLineInfo->meStyle = eStyle; + mpImplLineInfo->mnWidth = nWidth; +} + +LineInfo::LineInfo( const LineInfo& ) = default; + +LineInfo::LineInfo( LineInfo&& ) = default; + +LineInfo::~LineInfo() = default; + +LineInfo& LineInfo::operator=( const LineInfo& ) = default; + +LineInfo& LineInfo::operator=( LineInfo&& ) = default; + +bool LineInfo::operator==( const LineInfo& rLineInfo ) const +{ + return mpImplLineInfo == rLineInfo.mpImplLineInfo; +} + +void LineInfo::SetStyle( LineStyle eStyle ) +{ + mpImplLineInfo->meStyle = eStyle; +} + +void LineInfo::SetWidth( double nWidth ) +{ + mpImplLineInfo->mnWidth = nWidth; +} + +void LineInfo::SetDashCount( sal_uInt16 nDashCount ) +{ + mpImplLineInfo->mnDashCount = nDashCount; +} + +void LineInfo::SetDashLen( double nDashLen ) +{ + mpImplLineInfo->mnDashLen = nDashLen; +} + +void LineInfo::SetDotCount( sal_uInt16 nDotCount ) +{ + mpImplLineInfo->mnDotCount = nDotCount; +} + +void LineInfo::SetDotLen( double nDotLen ) +{ + mpImplLineInfo->mnDotLen = nDotLen; +} + +void LineInfo::SetDistance( double nDistance ) +{ + mpImplLineInfo->mnDistance = nDistance; +} + +void LineInfo::SetLineJoin(basegfx::B2DLineJoin eLineJoin) +{ + mpImplLineInfo->meLineJoin = eLineJoin; +} + +void LineInfo::SetLineCap(css::drawing::LineCap eLineCap) +{ + mpImplLineInfo->meLineCap = eLineCap; +} + +bool LineInfo::IsDefault() const +{ + return( !mpImplLineInfo->mnWidth + && ( LineStyle::Solid == mpImplLineInfo->meStyle ) + && ( css::drawing::LineCap_BUTT == mpImplLineInfo->meLineCap)); +} + +static void ReadLimitedDouble(SvStream& rIStm, double &fDest) +{ + double fTmp(0.0); + rIStm.ReadDouble(fTmp); + if (!std::isfinite(fTmp) || fTmp < std::numeric_limits<sal_Int32>::min() || fTmp > std::numeric_limits<sal_Int32>::max()) + { + SAL_WARN("vcl", "Parsing error: out of range double: " << fTmp); + return; + } + fDest = fTmp; +} + +SvStream& ReadLineInfo( SvStream& rIStm, LineInfo& rLineInfo ) +{ + VersionCompatRead aCompat( rIStm ); + sal_uInt16 nTmp16(0); + sal_Int32 nTmp32(0); + + rIStm.ReadUInt16( nTmp16 ); + rLineInfo.mpImplLineInfo->meStyle = static_cast<LineStyle>(nTmp16); + rIStm.ReadInt32( nTmp32 ); + rLineInfo.mpImplLineInfo->mnWidth = nTmp32; + + if( aCompat.GetVersion() >= 2 ) + { + // version 2 + rIStm.ReadUInt16( rLineInfo.mpImplLineInfo->mnDashCount ).ReadInt32( nTmp32 ); + rLineInfo.mpImplLineInfo->mnDashLen = nTmp32; + rIStm.ReadUInt16( rLineInfo.mpImplLineInfo->mnDotCount ).ReadInt32( nTmp32 ); + rLineInfo.mpImplLineInfo->mnDotLen = nTmp32; + rIStm.ReadInt32( nTmp32 ); + rLineInfo.mpImplLineInfo->mnDistance = nTmp32; + } + + if( aCompat.GetVersion() >= 3 ) + { + // version 3 + rIStm.ReadUInt16( nTmp16 ); + rLineInfo.mpImplLineInfo->meLineJoin = static_cast<basegfx::B2DLineJoin>(nTmp16); + } + + if( aCompat.GetVersion() >= 4 ) + { + // version 4 + rIStm.ReadUInt16( nTmp16 ); + rLineInfo.mpImplLineInfo->meLineCap = static_cast<css::drawing::LineCap>(nTmp16); + } + + if( aCompat.GetVersion() >= 5 ) + { + // version 5 + ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnWidth); + ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDashLen); + ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDotLen); + ReadLimitedDouble(rIStm, rLineInfo.mpImplLineInfo->mnDistance); + } + + return rIStm; +} + +SvStream& WriteLineInfo( SvStream& rOStm, const LineInfo& rLineInfo ) +{ + VersionCompatWrite aCompat( rOStm, 5 ); + + // version 1 + rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meStyle) ) + .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnWidth )); + + // since version2 + rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDashCount ) + .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDashLen )); + rOStm.WriteUInt16( rLineInfo.mpImplLineInfo->mnDotCount ) + .WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDotLen )); + rOStm.WriteInt32( basegfx::fround( rLineInfo.mpImplLineInfo->mnDistance )); + + // since version3 + rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meLineJoin) ); + + // since version4 + rOStm.WriteUInt16( static_cast<sal_uInt16>(rLineInfo.mpImplLineInfo->meLineCap) ); + + // since version5 + rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnWidth ); + rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDashLen ); + rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDotLen ); + rOStm.WriteDouble( rLineInfo.mpImplLineInfo->mnDistance ); + + return rOStm; +} + +std::vector< double > LineInfo::GetDotDashArray() const +{ + ::std::vector< double > fDotDashArray; + if ( GetStyle() != LineStyle::Dash ) + return fDotDashArray; + + const double fDashLen(GetDashLen()); + const double fDotLen(GetDotLen()); + const double fDistance(GetDistance()); + + for(sal_uInt16 a(0); a < GetDashCount(); a++) + { + fDotDashArray.push_back(fDashLen); + fDotDashArray.push_back(fDistance); + } + + for(sal_uInt16 b(0); b < GetDotCount(); b++) + { + fDotDashArray.push_back(fDotLen); + fDotDashArray.push_back(fDistance); + } + return fDotDashArray; +} + +void LineInfo::applyToB2DPolyPolygon( + basegfx::B2DPolyPolygon& io_rLinePolyPolygon, + basegfx::B2DPolyPolygon& o_rFillPolyPolygon) const +{ + o_rFillPolyPolygon.clear(); + + if(!io_rLinePolyPolygon.count()) + return; + + if(LineStyle::Dash == GetStyle()) + { + ::std::vector< double > fDotDashArray = GetDotDashArray(); + const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0)); + + if(fAccumulated > 0.0) + { + basegfx::B2DPolyPolygon aResult; + + for(auto const& rPolygon : std::as_const(io_rLinePolyPolygon)) + { + basegfx::B2DPolyPolygon aLineTarget; + basegfx::utils::applyLineDashing( + rPolygon, + fDotDashArray, + &aLineTarget); + aResult.append(aLineTarget); + } + + io_rLinePolyPolygon = aResult; + } + } + + if(!(GetWidth() > 1 && io_rLinePolyPolygon.count())) + return; + + const double fHalfLineWidth((GetWidth() * 0.5) + 0.5); + + for(auto const& rPolygon : std::as_const(io_rLinePolyPolygon)) + { + o_rFillPolyPolygon.append(basegfx::utils::createAreaGeometry( + rPolygon, + fHalfLineWidth, + GetLineJoin(), + GetLineCap())); + } + + io_rLinePolyPolygon.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/mapmod.cxx b/vcl/source/gdi/mapmod.cxx new file mode 100644 index 0000000000..45c7a29a81 --- /dev/null +++ b/vcl/source/gdi/mapmod.cxx @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/mapmod.hxx> + +#include <o3tl/hash_combine.hxx> +#include <tools/gen.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <vcl/TypeSerializer.hxx> + +struct MapMode::ImplMapMode +{ + MapUnit meUnit; + bool mbSimple; + Point maOrigin; + // NOTE: these Fraction must NOT have more than 32 bits precision + // because ReadFraction / WriteFraction do only 32 bits, so more than + // that cannot be stored in MetaFiles! + // => call ReduceInaccurate whenever setting these + Fraction maScaleX; + Fraction maScaleY; + + ImplMapMode(); + ImplMapMode(MapUnit eMapUnit); + ImplMapMode(const ImplMapMode& rImpMapMode); + + bool operator==( const ImplMapMode& rImpMapMode ) const; +}; + +MapMode::ImplMapMode::ImplMapMode() : + maOrigin( 0, 0 ), + maScaleX( 1, 1 ), + maScaleY( 1, 1 ) +{ + meUnit = MapUnit::MapPixel; + mbSimple = true; +} + +MapMode::ImplMapMode::ImplMapMode(MapUnit eMapUnit) : + maOrigin( 0, 0 ), + maScaleX( 1, 1 ), + maScaleY( 1, 1 ) +{ + meUnit = eMapUnit; + mbSimple = true; +} + +MapMode::ImplMapMode::ImplMapMode( const ImplMapMode& ) = default; + +bool MapMode::ImplMapMode::operator==( const ImplMapMode& rImpMapMode ) const +{ + return meUnit == rImpMapMode.meUnit + && maOrigin == rImpMapMode.maOrigin + && maScaleX == rImpMapMode.maScaleX + && maScaleY == rImpMapMode.maScaleY; +} + +namespace +{ + + MapMode::ImplType& GetGlobalDefault() + { + static MapMode::ImplType gDefault; + return gDefault; + } + MapMode::ImplType GetUnitDefault(MapUnit mapUnit) + { + static MapMode::ImplType defaults[] + { + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map100thMM) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map10thMM) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapMM) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapCM) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map1000thInch) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map100thInch) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::Map10thInch) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapInch) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapPoint) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapTwip) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapPixel) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapSysFont) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapAppFont) ), + MapMode::ImplType( MapMode::ImplMapMode(MapUnit::MapRelative) ), + }; + if (mapUnit <= MapUnit::MapRelative) { + return MapMode::ImplType(defaults[static_cast<int>(mapUnit)]); // [-loplugin:redundantfcast] false positive + } + // sometimes the SvmReader stuff will generate a bogus mapunit value + return MapMode::ImplType(MapMode::ImplMapMode(mapUnit)); + } +} + +MapMode::MapMode() : mpImplMapMode(GetGlobalDefault()) +{ +} + +MapMode::MapMode( const MapMode& ) = default; + +MapMode::MapMode( MapUnit eUnit ) : mpImplMapMode(GetUnitDefault(eUnit)) +{ +} + +MapMode::MapMode( MapUnit eUnit, const Point& rLogicOrg, + const Fraction& rScaleX, const Fraction& rScaleY ) +{ + mpImplMapMode->meUnit = eUnit; + mpImplMapMode->maOrigin = rLogicOrg; + mpImplMapMode->maScaleX = rScaleX; + mpImplMapMode->maScaleY = rScaleY; + mpImplMapMode->mbSimple = false; +} + +MapMode::~MapMode() = default; + +void MapMode::SetMapUnit( MapUnit eUnit ) +{ + mpImplMapMode->meUnit = eUnit; +} + +void MapMode::SetOrigin( const Point& rLogicOrg ) +{ + mpImplMapMode->maOrigin = rLogicOrg; + mpImplMapMode->mbSimple = false; +} + +void MapMode::SetScaleX( const Fraction& rScaleX ) +{ + mpImplMapMode->maScaleX = rScaleX; + mpImplMapMode->mbSimple = false; +} + +void MapMode::SetScaleY( const Fraction& rScaleY ) +{ + mpImplMapMode->maScaleY = rScaleY; + mpImplMapMode->mbSimple = false; +} + +MapMode& MapMode::operator=( const MapMode& ) = default; + +MapMode& MapMode::operator=( MapMode&& ) = default; + +bool MapMode::operator==( const MapMode& rMapMode ) const +{ + return mpImplMapMode == rMapMode.mpImplMapMode; +} + +bool MapMode::IsDefault() const +{ + return mpImplMapMode.same_object(GetGlobalDefault()); +} + +size_t MapMode::GetHashValue() const +{ + size_t hash = 0; + o3tl::hash_combine( hash, mpImplMapMode->meUnit ); + o3tl::hash_combine( hash, mpImplMapMode->maOrigin.GetHashValue()); + o3tl::hash_combine( hash, mpImplMapMode->maScaleX.GetHashValue()); + o3tl::hash_combine( hash, mpImplMapMode->maScaleY.GetHashValue()); + o3tl::hash_combine( hash, mpImplMapMode->mbSimple ); + return hash; +} + +MapUnit MapMode::GetMapUnit() const { return mpImplMapMode->meUnit; } + +const Point& MapMode::GetOrigin() const { return mpImplMapMode->maOrigin; } + +const Fraction& MapMode::GetScaleX() const { return mpImplMapMode->maScaleX; } + +const Fraction& MapMode::GetScaleY() const { return mpImplMapMode->maScaleY; } + +bool MapMode::IsSimple() const { return mpImplMapMode->mbSimple; } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/metaact.cxx b/vcl/source/gdi/metaact.cxx new file mode 100644 index 0000000000..2e1f3e4d84 --- /dev/null +++ b/vcl/source/gdi/metaact.cxx @@ -0,0 +1,2208 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <stdio.h> +#include <string.h> +#include <osl/thread.h> +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <tools/helpers.hxx> +#include <utility> +#include <vcl/dibtools.hxx> +#include <vcl/filter/SvmReader.hxx> +#include <vcl/filter/SvmWriter.hxx> +#include <vcl/outdev.hxx> +#include <vcl/metaact.hxx> +#include <vcl/graphictools.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/fontdefs.hxx> +#include <vcl/TypeSerializer.hxx> + +namespace +{ + +void ImplScalePoint( Point& rPt, double fScaleX, double fScaleY ) +{ + rPt.setX( FRound( fScaleX * rPt.X() ) ); + rPt.setY( FRound( fScaleY * rPt.Y() ) ); +} + +void ImplScaleRect( tools::Rectangle& rRect, double fScaleX, double fScaleY ) +{ + Point aTL( rRect.TopLeft() ); + Point aBR( rRect.BottomRight() ); + + ImplScalePoint( aTL, fScaleX, fScaleY ); + ImplScalePoint( aBR, fScaleX, fScaleY ); + + rRect = tools::Rectangle( aTL, aBR ); + rRect.Normalize(); +} + +void ImplScalePoly( tools::Polygon& rPoly, double fScaleX, double fScaleY ) +{ + for( sal_uInt16 i = 0, nCount = rPoly.GetSize(); i < nCount; i++ ) + ImplScalePoint( rPoly[ i ], fScaleX, fScaleY ); +} + +void ImplScaleLineInfo( LineInfo& rLineInfo, double fScaleX, double fScaleY ) +{ + if( !rLineInfo.IsDefault() ) + { + const double fScale = ( fabs(fScaleX) + fabs(fScaleY) ) * 0.5; + + rLineInfo.SetWidth( FRound( fScale * rLineInfo.GetWidth() ) ); + rLineInfo.SetDashLen( FRound( fScale * rLineInfo.GetDashLen() ) ); + rLineInfo.SetDotLen( FRound( fScale * rLineInfo.GetDotLen() ) ); + rLineInfo.SetDistance( FRound( fScale * rLineInfo.GetDistance() ) ); + } +} + +} //anonymous namespace + +MetaAction::MetaAction() : + mnType( MetaActionType::NONE ) +{ +} + +MetaAction::MetaAction( MetaActionType nType ) : + mnType( nType ) +{ +} + +MetaAction::MetaAction( MetaAction const & rOther ) : + SimpleReferenceObject(), mnType( rOther.mnType ) +{ +} + +MetaAction::~MetaAction() +{ +} + +void MetaAction::Execute( OutputDevice* ) +{ +} + +rtl::Reference<MetaAction> MetaAction::Clone() const +{ + return new MetaAction; +} + +void MetaAction::Move( tools::Long, tools::Long ) +{ +} + +void MetaAction::Scale( double, double ) +{ +} + +MetaPixelAction::MetaPixelAction() : + MetaAction(MetaActionType::PIXEL) +{} + +MetaPixelAction::~MetaPixelAction() +{} + +MetaPixelAction::MetaPixelAction( const Point& rPt, const Color& rColor ) : + MetaAction ( MetaActionType::PIXEL ), + maPt ( rPt ), + maColor ( rColor ) +{} + +void MetaPixelAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawPixel( maPt, maColor ); +} + +rtl::Reference<MetaAction> MetaPixelAction::Clone() const +{ + return new MetaPixelAction( *this ); +} + +void MetaPixelAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaPixelAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maPt, fScaleX, fScaleY ); +} + +MetaPointAction::MetaPointAction() : + MetaAction(MetaActionType::POINT) +{} + +MetaPointAction::~MetaPointAction() +{} + +MetaPointAction::MetaPointAction( const Point& rPt ) : + MetaAction ( MetaActionType::POINT ), + maPt ( rPt ) +{} + +void MetaPointAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawPixel( maPt ); +} + +rtl::Reference<MetaAction> MetaPointAction::Clone() const +{ + return new MetaPointAction( *this ); +} + +void MetaPointAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaPointAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maPt, fScaleX, fScaleY ); +} + +MetaLineAction::MetaLineAction() : + MetaAction(MetaActionType::LINE) +{} + +MetaLineAction::~MetaLineAction() +{} + +MetaLineAction::MetaLineAction( const Point& rStart, const Point& rEnd ) : + MetaAction ( MetaActionType::LINE ), + maStartPt ( rStart ), + maEndPt ( rEnd ) +{} + +MetaLineAction::MetaLineAction( const Point& rStart, const Point& rEnd, + LineInfo aLineInfo ) : + MetaAction ( MetaActionType::LINE ), + maLineInfo (std::move( aLineInfo )), + maStartPt ( rStart ), + maEndPt ( rEnd ) +{} + +void MetaLineAction::Execute( OutputDevice* pOut ) +{ + if( maLineInfo.IsDefault() ) + pOut->DrawLine( maStartPt, maEndPt ); + else + pOut->DrawLine( maStartPt, maEndPt, maLineInfo ); +} + +rtl::Reference<MetaAction> MetaLineAction::Clone() const +{ + return new MetaLineAction( *this ); +} + +void MetaLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maStartPt.Move( nHorzMove, nVertMove ); + maEndPt.Move( nHorzMove, nVertMove ); +} + +void MetaLineAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maStartPt, fScaleX, fScaleY ); + ImplScalePoint( maEndPt, fScaleX, fScaleY ); + ImplScaleLineInfo( maLineInfo, fScaleX, fScaleY ); +} + +MetaRectAction::MetaRectAction() : + MetaAction(MetaActionType::RECT) +{} + +MetaRectAction::~MetaRectAction() +{} + +MetaRectAction::MetaRectAction( const tools::Rectangle& rRect ) : + MetaAction ( MetaActionType::RECT ), + maRect ( rRect ) +{} + +void MetaRectAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawRect( maRect ); +} + +rtl::Reference<MetaAction> MetaRectAction::Clone() const +{ + return new MetaRectAction( *this ); +} + +void MetaRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); +} + +void MetaRectAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); +} + +MetaRoundRectAction::MetaRoundRectAction() : + MetaAction ( MetaActionType::ROUNDRECT ), + mnHorzRound ( 0 ), + mnVertRound ( 0 ) +{} + +MetaRoundRectAction::~MetaRoundRectAction() +{} + +MetaRoundRectAction::MetaRoundRectAction( const tools::Rectangle& rRect, + sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) : + MetaAction ( MetaActionType::ROUNDRECT ), + maRect ( rRect ), + mnHorzRound ( nHorzRound ), + mnVertRound ( nVertRound ) +{} + +void MetaRoundRectAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawRect( maRect, mnHorzRound, mnVertRound ); +} + +rtl::Reference<MetaAction> MetaRoundRectAction::Clone() const +{ + return new MetaRoundRectAction( *this ); +} + +void MetaRoundRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); +} + +void MetaRoundRectAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); + mnHorzRound = FRound( mnHorzRound * fabs(fScaleX) ); + mnVertRound = FRound( mnVertRound * fabs(fScaleY) ); +} + +MetaEllipseAction::MetaEllipseAction() : + MetaAction(MetaActionType::ELLIPSE) +{} + +MetaEllipseAction::~MetaEllipseAction() +{} + +MetaEllipseAction::MetaEllipseAction( const tools::Rectangle& rRect ) : + MetaAction ( MetaActionType::ELLIPSE ), + maRect ( rRect ) +{} + +void MetaEllipseAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawEllipse( maRect ); +} + +rtl::Reference<MetaAction> MetaEllipseAction::Clone() const +{ + return new MetaEllipseAction( *this ); +} + +void MetaEllipseAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); +} + +void MetaEllipseAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); +} + +MetaArcAction::MetaArcAction() : + MetaAction(MetaActionType::ARC) +{} + +MetaArcAction::~MetaArcAction() +{} + +MetaArcAction::MetaArcAction( const tools::Rectangle& rRect, + const Point& rStart, const Point& rEnd ) : + MetaAction ( MetaActionType::ARC ), + maRect ( rRect ), + maStartPt ( rStart ), + maEndPt ( rEnd ) +{} + +void MetaArcAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawArc( maRect, maStartPt, maEndPt ); +} + +rtl::Reference<MetaAction> MetaArcAction::Clone() const +{ + return new MetaArcAction( *this ); +} + +void MetaArcAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); + maStartPt.Move( nHorzMove, nVertMove ); + maEndPt.Move( nHorzMove, nVertMove ); +} + +void MetaArcAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); + ImplScalePoint( maStartPt, fScaleX, fScaleY ); + ImplScalePoint( maEndPt, fScaleX, fScaleY ); +} + +MetaPieAction::MetaPieAction() : + MetaAction(MetaActionType::PIE) +{} + +MetaPieAction::~MetaPieAction() +{} + +MetaPieAction::MetaPieAction( const tools::Rectangle& rRect, + const Point& rStart, const Point& rEnd ) : + MetaAction ( MetaActionType::PIE ), + maRect ( rRect ), + maStartPt ( rStart ), + maEndPt ( rEnd ) +{} + +void MetaPieAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawPie( maRect, maStartPt, maEndPt ); +} + +rtl::Reference<MetaAction> MetaPieAction::Clone() const +{ + return new MetaPieAction( *this ); +} + +void MetaPieAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); + maStartPt.Move( nHorzMove, nVertMove ); + maEndPt.Move( nHorzMove, nVertMove ); +} + +void MetaPieAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); + ImplScalePoint( maStartPt, fScaleX, fScaleY ); + ImplScalePoint( maEndPt, fScaleX, fScaleY ); +} + +MetaChordAction::MetaChordAction() : + MetaAction(MetaActionType::CHORD) +{} + +MetaChordAction::~MetaChordAction() +{} + +MetaChordAction::MetaChordAction( const tools::Rectangle& rRect, + const Point& rStart, const Point& rEnd ) : + MetaAction ( MetaActionType::CHORD ), + maRect ( rRect ), + maStartPt ( rStart ), + maEndPt ( rEnd ) +{} + +void MetaChordAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawChord( maRect, maStartPt, maEndPt ); +} + +rtl::Reference<MetaAction> MetaChordAction::Clone() const +{ + return new MetaChordAction( *this ); +} + +void MetaChordAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); + maStartPt.Move( nHorzMove, nVertMove ); + maEndPt.Move( nHorzMove, nVertMove ); +} + +void MetaChordAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); + ImplScalePoint( maStartPt, fScaleX, fScaleY ); + ImplScalePoint( maEndPt, fScaleX, fScaleY ); +} + +MetaPolyLineAction::MetaPolyLineAction() : + MetaAction(MetaActionType::POLYLINE) +{} + +MetaPolyLineAction::~MetaPolyLineAction() +{} + +MetaPolyLineAction::MetaPolyLineAction( tools::Polygon aPoly ) : + MetaAction ( MetaActionType::POLYLINE ), + maPoly (std::move( aPoly )) +{} + +MetaPolyLineAction::MetaPolyLineAction( tools::Polygon aPoly, LineInfo aLineInfo ) : + MetaAction ( MetaActionType::POLYLINE ), + maLineInfo (std::move( aLineInfo )), + maPoly (std::move( aPoly )) +{} + +static bool AllowDim(tools::Long nDim) +{ + static bool bFuzzing = utl::ConfigManager::IsFuzzing(); + if (bFuzzing) + { + if (nDim > 0x20000000 || nDim < -0x20000000) + { + SAL_WARN("vcl", "skipping huge dimension: " << nDim); + return false; + } + } + return true; +} + +static bool AllowPoint(const Point& rPoint) +{ + return AllowDim(rPoint.X()) && AllowDim(rPoint.Y()); +} + +static bool AllowRect(const tools::Rectangle& rRect) +{ + return AllowPoint(rRect.TopLeft()) && AllowPoint(rRect.BottomRight()); +} + +void MetaPolyLineAction::Execute( OutputDevice* pOut ) +{ + if (!AllowRect(pOut->LogicToPixel(maPoly.GetBoundRect()))) + return; + + if( maLineInfo.IsDefault() ) + pOut->DrawPolyLine( maPoly ); + else + pOut->DrawPolyLine( maPoly, maLineInfo ); +} + +rtl::Reference<MetaAction> MetaPolyLineAction::Clone() const +{ + return new MetaPolyLineAction( *this ); +} + +void MetaPolyLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPoly.Move( nHorzMove, nVertMove ); +} + +void MetaPolyLineAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoly( maPoly, fScaleX, fScaleY ); + ImplScaleLineInfo( maLineInfo, fScaleX, fScaleY ); +} + +MetaPolygonAction::MetaPolygonAction() : + MetaAction(MetaActionType::POLYGON) +{} + +MetaPolygonAction::~MetaPolygonAction() +{} + +MetaPolygonAction::MetaPolygonAction( tools::Polygon aPoly ) : + MetaAction ( MetaActionType::POLYGON ), + maPoly (std::move( aPoly )) +{} + +void MetaPolygonAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawPolygon( maPoly ); +} + +rtl::Reference<MetaAction> MetaPolygonAction::Clone() const +{ + return new MetaPolygonAction( *this ); +} + +void MetaPolygonAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPoly.Move( nHorzMove, nVertMove ); +} + +void MetaPolygonAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoly( maPoly, fScaleX, fScaleY ); +} + +MetaPolyPolygonAction::MetaPolyPolygonAction() : + MetaAction(MetaActionType::POLYPOLYGON) +{} + +MetaPolyPolygonAction::~MetaPolyPolygonAction() +{} + +MetaPolyPolygonAction::MetaPolyPolygonAction( tools::PolyPolygon aPolyPoly ) : + MetaAction ( MetaActionType::POLYPOLYGON ), + maPolyPoly (std::move( aPolyPoly )) +{} + +void MetaPolyPolygonAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawPolyPolygon( maPolyPoly ); +} + +rtl::Reference<MetaAction> MetaPolyPolygonAction::Clone() const +{ + return new MetaPolyPolygonAction( *this ); +} + +void MetaPolyPolygonAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPolyPoly.Move( nHorzMove, nVertMove ); +} + +void MetaPolyPolygonAction::Scale( double fScaleX, double fScaleY ) +{ + for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ ) + ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY ); +} + +MetaTextAction::MetaTextAction() : + MetaAction ( MetaActionType::TEXT ), + mnIndex ( 0 ), + mnLen ( 0 ) +{} + +MetaTextAction::~MetaTextAction() +{} + +MetaTextAction::MetaTextAction( const Point& rPt, OUString aStr, + sal_Int32 nIndex, sal_Int32 nLen ) : + MetaAction ( MetaActionType::TEXT ), + maPt ( rPt ), + maStr (std::move( aStr )), + mnIndex ( nIndex ), + mnLen ( nLen ) +{} + +void MetaTextAction::Execute( OutputDevice* pOut ) +{ + if (!AllowDim(pOut->LogicToPixel(maPt).Y())) + return; + + pOut->DrawText( maPt, maStr, mnIndex, mnLen ); +} + +rtl::Reference<MetaAction> MetaTextAction::Clone() const +{ + return new MetaTextAction( *this ); +} + +void MetaTextAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaTextAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maPt, fScaleX, fScaleY ); +} + +MetaTextArrayAction::MetaTextArrayAction() : + MetaAction ( MetaActionType::TEXTARRAY ), + mnIndex ( 0 ), + mnLen ( 0 ) +{} + +MetaTextArrayAction::MetaTextArrayAction( const MetaTextArrayAction& rAction ) : + MetaAction ( MetaActionType::TEXTARRAY ), + maStartPt ( rAction.maStartPt ), + maStr ( rAction.maStr ), + maDXAry ( rAction.maDXAry ), + maKashidaAry( rAction.maKashidaAry ), + mnIndex ( rAction.mnIndex ), + mnLen ( rAction.mnLen ) +{ +} + +MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt, + OUString aStr, + KernArray aDXAry, + std::vector<sal_Bool> aKashidaAry, + sal_Int32 nIndex, + sal_Int32 nLen ) : + MetaAction ( MetaActionType::TEXTARRAY ), + maStartPt ( rStartPt ), + maStr (std::move( aStr )), + maDXAry (std::move( aDXAry )), + maKashidaAry(std::move( aKashidaAry )), + mnIndex ( nIndex ), + mnLen ( nLen ) +{ +} + +MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt, + OUString aStr, + KernArraySpan pDXAry, + std::span<const sal_Bool> pKashidaAry, + sal_Int32 nIndex, + sal_Int32 nLen ) : + MetaAction ( MetaActionType::TEXTARRAY ), + maStartPt ( rStartPt ), + maStr (std::move( aStr )), + maKashidaAry( pKashidaAry.begin(), pKashidaAry.end() ), + mnIndex ( nIndex ), + mnLen ( nLen ) +{ + maDXAry.assign(pDXAry); +} + +MetaTextArrayAction::~MetaTextArrayAction() +{ +} + +void MetaTextArrayAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawTextArray( maStartPt, maStr, maDXAry, maKashidaAry, mnIndex, mnLen ); +} + +rtl::Reference<MetaAction> MetaTextArrayAction::Clone() const +{ + return new MetaTextArrayAction( *this ); +} + +void MetaTextArrayAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maStartPt.Move( nHorzMove, nVertMove ); +} + +void MetaTextArrayAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maStartPt, fScaleX, fScaleY ); + + if ( !maDXAry.empty() && mnLen ) + { + for ( sal_uInt16 i = 0, nCount = mnLen; i < nCount; i++ ) + maDXAry.set(i, FRound(maDXAry[i] * fabs(fScaleX))); + } +} + +void MetaTextArrayAction::SetDXArray(KernArray aArray) +{ + maDXAry = std::move(aArray); +} + +void MetaTextArrayAction::SetKashidaArray(std::vector<sal_Bool> aArray) +{ + maKashidaAry = std::move(aArray); +} + +MetaStretchTextAction::MetaStretchTextAction() : + MetaAction ( MetaActionType::STRETCHTEXT ), + mnWidth ( 0 ), + mnIndex ( 0 ), + mnLen ( 0 ) +{} + +MetaStretchTextAction::~MetaStretchTextAction() +{} + +MetaStretchTextAction::MetaStretchTextAction( const Point& rPt, sal_uInt32 nWidth, + OUString aStr, + sal_Int32 nIndex, sal_Int32 nLen ) : + MetaAction ( MetaActionType::STRETCHTEXT ), + maPt ( rPt ), + maStr (std::move( aStr )), + mnWidth ( nWidth ), + mnIndex ( nIndex ), + mnLen ( nLen ) +{} + +void MetaStretchTextAction::Execute( OutputDevice* pOut ) +{ + if (!AllowDim(pOut->LogicToPixel(maPt).Y())) + return; + + pOut->DrawStretchText( maPt, mnWidth, maStr, mnIndex, mnLen ); +} + +rtl::Reference<MetaAction> MetaStretchTextAction::Clone() const +{ + return new MetaStretchTextAction( *this ); +} + +void MetaStretchTextAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaStretchTextAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maPt, fScaleX, fScaleY ); + mnWidth = static_cast<sal_uLong>(FRound( mnWidth * fabs(fScaleX) )); +} +MetaTextRectAction::MetaTextRectAction() : + MetaAction ( MetaActionType::TEXTRECT ), + mnStyle ( DrawTextFlags::NONE ) +{} + +MetaTextRectAction::~MetaTextRectAction() +{} + +MetaTextRectAction::MetaTextRectAction( const tools::Rectangle& rRect, + OUString aStr, DrawTextFlags nStyle ) : + MetaAction ( MetaActionType::TEXTRECT ), + maRect ( rRect ), + maStr (std::move( aStr )), + mnStyle ( nStyle ) +{} + +void MetaTextRectAction::Execute( OutputDevice* pOut ) +{ + if (!AllowRect(pOut->LogicToPixel(maRect))) + return; + + pOut->DrawText( maRect, maStr, mnStyle ); +} + +rtl::Reference<MetaAction> MetaTextRectAction::Clone() const +{ + return new MetaTextRectAction( *this ); +} + +void MetaTextRectAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); +} + +void MetaTextRectAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); +} + +MetaTextLineAction::MetaTextLineAction() : + MetaAction ( MetaActionType::TEXTLINE ), + mnWidth ( 0 ), + meStrikeout ( STRIKEOUT_NONE ), + meUnderline ( LINESTYLE_NONE ), + meOverline ( LINESTYLE_NONE ) +{} + +MetaTextLineAction::~MetaTextLineAction() +{} + +MetaTextLineAction::MetaTextLineAction( const Point& rPos, tools::Long nWidth, + FontStrikeout eStrikeout, + FontLineStyle eUnderline, + FontLineStyle eOverline ) : + MetaAction ( MetaActionType::TEXTLINE ), + maPos ( rPos ), + mnWidth ( nWidth ), + meStrikeout ( eStrikeout ), + meUnderline ( eUnderline ), + meOverline ( eOverline ) +{} + +void MetaTextLineAction::Execute( OutputDevice* pOut ) +{ + if (mnWidth < 0) + { + SAL_WARN("vcl", "skipping line with negative width: " << mnWidth); + return; + } + pOut->DrawTextLine( maPos, mnWidth, meStrikeout, meUnderline, meOverline ); +} + +rtl::Reference<MetaAction> MetaTextLineAction::Clone() const +{ + return new MetaTextLineAction( *this ); +} + +void MetaTextLineAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPos.Move( nHorzMove, nVertMove ); +} + +void MetaTextLineAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maPos, fScaleX, fScaleY ); + mnWidth = FRound( mnWidth * fabs(fScaleX) ); +} + +MetaBmpAction::MetaBmpAction() : + MetaAction(MetaActionType::BMP) +{} + +MetaBmpAction::~MetaBmpAction() +{} + +MetaBmpAction::MetaBmpAction( const Point& rPt, const Bitmap& rBmp ) : + MetaAction ( MetaActionType::BMP ), + maBmp ( rBmp ), + maPt ( rPt ) +{} + +void MetaBmpAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawBitmap( maPt, maBmp ); +} + +rtl::Reference<MetaAction> MetaBmpAction::Clone() const +{ + return new MetaBmpAction( *this ); +} + +void MetaBmpAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaBmpAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maPt, fScaleX, fScaleY ); +} + +MetaBmpScaleAction::MetaBmpScaleAction() : + MetaAction(MetaActionType::BMPSCALE) +{} + +MetaBmpScaleAction::~MetaBmpScaleAction() +{} + +MetaBmpScaleAction::MetaBmpScaleAction( const Point& rPt, const Size& rSz, + const Bitmap& rBmp ) : + MetaAction ( MetaActionType::BMPSCALE ), + maBmp ( rBmp ), + maPt ( rPt ), + maSz ( rSz ) +{} + +static bool AllowScale(const Size& rSource, const Size& rDest) +{ + static bool bFuzzing = utl::ConfigManager::IsFuzzing(); + if (bFuzzing) + { + constexpr int nMaxScaleWhenFuzzing = 128; + + auto nSourceHeight = rSource.Height(); + auto nDestHeight = rDest.Height(); + if (nSourceHeight && std::abs(nDestHeight / nSourceHeight) > nMaxScaleWhenFuzzing) + { + SAL_WARN("vcl", "skipping large vertical scaling: " << nSourceHeight << " to " << nDestHeight); + return false; + } + + if (nDestHeight && std::abs(nSourceHeight / nDestHeight) > nMaxScaleWhenFuzzing) + { + SAL_WARN("vcl", "skipping large vertical scaling: " << nSourceHeight << " to " << nDestHeight); + return false; + } + + auto nSourceWidth = rSource.Width(); + auto nDestWidth = rDest.Width(); + if (nSourceWidth && std::abs(nDestWidth / nSourceWidth) > nMaxScaleWhenFuzzing) + { + SAL_WARN("vcl", "skipping large horizontal scaling: " << nSourceWidth << " to " << nDestWidth); + return false; + } + + if (nDestWidth && std::abs(nSourceWidth / nDestWidth) > nMaxScaleWhenFuzzing) + { + SAL_WARN("vcl", "skipping large horizontal scaling: " << nSourceWidth << " to " << nDestWidth); + return false; + } + } + + return true; +} + +void MetaBmpScaleAction::Execute( OutputDevice* pOut ) +{ + Size aPixelSize(pOut->LogicToPixel(maSz)); + if (!AllowRect(tools::Rectangle(pOut->LogicToPixel(maPt), aPixelSize)) || + !AllowScale(maBmp.GetSizePixel(), aPixelSize)) + { + return; + } + + pOut->DrawBitmap( maPt, maSz, maBmp ); +} + +rtl::Reference<MetaAction> MetaBmpScaleAction::Clone() const +{ + return new MetaBmpScaleAction( *this ); +} + +void MetaBmpScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaBmpScaleAction::Scale( double fScaleX, double fScaleY ) +{ + tools::Rectangle aRectangle(maPt, maSz); + ImplScaleRect( aRectangle, fScaleX, fScaleY ); + maPt = aRectangle.TopLeft(); + maSz = aRectangle.GetSize(); +} + +MetaBmpScalePartAction::MetaBmpScalePartAction() : + MetaAction(MetaActionType::BMPSCALEPART) +{} + +MetaBmpScalePartAction::~MetaBmpScalePartAction() +{} + +MetaBmpScalePartAction::MetaBmpScalePartAction( const Point& rDstPt, const Size& rDstSz, + const Point& rSrcPt, const Size& rSrcSz, + const Bitmap& rBmp ) : + MetaAction ( MetaActionType::BMPSCALEPART ), + maBmp ( rBmp ), + maDstPt ( rDstPt ), + maDstSz ( rDstSz ), + maSrcPt ( rSrcPt ), + maSrcSz ( rSrcSz ) +{} + +void MetaBmpScalePartAction::Execute( OutputDevice* pOut ) +{ + if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz)))) + return; + + pOut->DrawBitmap( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp ); +} + +rtl::Reference<MetaAction> MetaBmpScalePartAction::Clone() const +{ + return new MetaBmpScalePartAction( *this ); +} + +void MetaBmpScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maDstPt.Move( nHorzMove, nVertMove ); +} + +void MetaBmpScalePartAction::Scale( double fScaleX, double fScaleY ) +{ + tools::Rectangle aRectangle(maDstPt, maDstSz); + ImplScaleRect( aRectangle, fScaleX, fScaleY ); + maDstPt = aRectangle.TopLeft(); + maDstSz = aRectangle.GetSize(); +} + +MetaBmpExAction::MetaBmpExAction() : + MetaAction(MetaActionType::BMPEX) +{} + +MetaBmpExAction::~MetaBmpExAction() +{} + +MetaBmpExAction::MetaBmpExAction( const Point& rPt, const BitmapEx& rBmpEx ) : + MetaAction ( MetaActionType::BMPEX ), + maBmpEx ( rBmpEx ), + maPt ( rPt ) +{} + +void MetaBmpExAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawBitmapEx( maPt, maBmpEx ); +} + +rtl::Reference<MetaAction> MetaBmpExAction::Clone() const +{ + return new MetaBmpExAction( *this ); +} + +void MetaBmpExAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaBmpExAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maPt, fScaleX, fScaleY ); +} + +MetaBmpExScaleAction::MetaBmpExScaleAction() : + MetaAction(MetaActionType::BMPEXSCALE) +{} + +MetaBmpExScaleAction::~MetaBmpExScaleAction() +{} + +MetaBmpExScaleAction::MetaBmpExScaleAction( const Point& rPt, const Size& rSz, + const BitmapEx& rBmpEx ) : + MetaAction ( MetaActionType::BMPEXSCALE ), + maBmpEx ( rBmpEx ), + maPt ( rPt ), + maSz ( rSz ) +{} + +void MetaBmpExScaleAction::Execute( OutputDevice* pOut ) +{ + if (!AllowScale(maBmpEx.GetSizePixel(), pOut->LogicToPixel(maSz))) + return; + if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maPt, maSz)))) + return; + + pOut->DrawBitmapEx( maPt, maSz, maBmpEx ); +} + +rtl::Reference<MetaAction> MetaBmpExScaleAction::Clone() const +{ + return new MetaBmpExScaleAction( *this ); +} + +void MetaBmpExScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaBmpExScaleAction::Scale( double fScaleX, double fScaleY ) +{ + tools::Rectangle aRectangle(maPt, maSz); + ImplScaleRect( aRectangle, fScaleX, fScaleY ); + maPt = aRectangle.TopLeft(); + maSz = aRectangle.GetSize(); +} + +MetaBmpExScalePartAction::MetaBmpExScalePartAction() : + MetaAction(MetaActionType::BMPEXSCALEPART) +{} + +MetaBmpExScalePartAction::~MetaBmpExScalePartAction() +{} + +MetaBmpExScalePartAction::MetaBmpExScalePartAction( const Point& rDstPt, const Size& rDstSz, + const Point& rSrcPt, const Size& rSrcSz, + const BitmapEx& rBmpEx ) : + MetaAction ( MetaActionType::BMPEXSCALEPART ), + maBmpEx ( rBmpEx ), + maDstPt ( rDstPt ), + maDstSz ( rDstSz ), + maSrcPt ( rSrcPt ), + maSrcSz ( rSrcSz ) +{} + +void MetaBmpExScalePartAction::Execute( OutputDevice* pOut ) +{ + if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz)))) + return; + + pOut->DrawBitmapEx( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmpEx ); +} + +rtl::Reference<MetaAction> MetaBmpExScalePartAction::Clone() const +{ + return new MetaBmpExScalePartAction( *this ); +} + +void MetaBmpExScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maDstPt.Move( nHorzMove, nVertMove ); +} + +void MetaBmpExScalePartAction::Scale( double fScaleX, double fScaleY ) +{ + tools::Rectangle aRectangle(maDstPt, maDstSz); + ImplScaleRect( aRectangle, fScaleX, fScaleY ); + maDstPt = aRectangle.TopLeft(); + maDstSz = aRectangle.GetSize(); +} + +MetaMaskAction::MetaMaskAction() : + MetaAction(MetaActionType::MASK) +{} + +MetaMaskAction::~MetaMaskAction() +{} + +MetaMaskAction::MetaMaskAction( const Point& rPt, + const Bitmap& rBmp, + const Color& rColor ) : + MetaAction ( MetaActionType::MASK ), + maBmp ( rBmp ), + maColor ( rColor ), + maPt ( rPt ) +{} + +void MetaMaskAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawMask( maPt, maBmp, maColor ); +} + +rtl::Reference<MetaAction> MetaMaskAction::Clone() const +{ + return new MetaMaskAction( *this ); +} + +void MetaMaskAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaMaskAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScalePoint( maPt, fScaleX, fScaleY ); +} + +MetaMaskScaleAction::MetaMaskScaleAction() : + MetaAction(MetaActionType::MASKSCALE) +{} + +MetaMaskScaleAction::~MetaMaskScaleAction() +{} + +MetaMaskScaleAction::MetaMaskScaleAction( const Point& rPt, const Size& rSz, + const Bitmap& rBmp, + const Color& rColor ) : + MetaAction ( MetaActionType::MASKSCALE ), + maBmp ( rBmp ), + maColor ( rColor ), + maPt ( rPt ), + maSz ( rSz ) +{} + +void MetaMaskScaleAction::Execute( OutputDevice* pOut ) +{ + if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maPt, maSz)))) + return; + pOut->DrawMask( maPt, maSz, maBmp, maColor ); +} + +rtl::Reference<MetaAction> MetaMaskScaleAction::Clone() const +{ + return new MetaMaskScaleAction( *this ); +} + +void MetaMaskScaleAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPt.Move( nHorzMove, nVertMove ); +} + +void MetaMaskScaleAction::Scale( double fScaleX, double fScaleY ) +{ + tools::Rectangle aRectangle(maPt, maSz); + ImplScaleRect( aRectangle, fScaleX, fScaleY ); + maPt = aRectangle.TopLeft(); + maSz = aRectangle.GetSize(); +} + +MetaMaskScalePartAction::MetaMaskScalePartAction() : + MetaAction(MetaActionType::MASKSCALEPART) +{} + +MetaMaskScalePartAction::~MetaMaskScalePartAction() +{} + +MetaMaskScalePartAction::MetaMaskScalePartAction( const Point& rDstPt, const Size& rDstSz, + const Point& rSrcPt, const Size& rSrcSz, + const Bitmap& rBmp, + const Color& rColor ) : + MetaAction ( MetaActionType::MASKSCALEPART ), + maBmp ( rBmp ), + maColor ( rColor ), + maDstPt ( rDstPt ), + maDstSz ( rDstSz ), + maSrcPt ( rSrcPt ), + maSrcSz ( rSrcSz ) +{} + +void MetaMaskScalePartAction::Execute( OutputDevice* pOut ) +{ + if (!AllowRect(pOut->LogicToPixel(tools::Rectangle(maDstPt, maDstSz)))) + return; + + pOut->DrawMask( maDstPt, maDstSz, maSrcPt, maSrcSz, maBmp, maColor, MetaActionType::MASKSCALE ); +} + +rtl::Reference<MetaAction> MetaMaskScalePartAction::Clone() const +{ + return new MetaMaskScalePartAction( *this ); +} + +void MetaMaskScalePartAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maDstPt.Move( nHorzMove, nVertMove ); +} + +void MetaMaskScalePartAction::Scale( double fScaleX, double fScaleY ) +{ + tools::Rectangle aRectangle(maDstPt, maDstSz); + ImplScaleRect( aRectangle, fScaleX, fScaleY ); + maDstPt = aRectangle.TopLeft(); + maDstSz = aRectangle.GetSize(); +} + +MetaGradientAction::MetaGradientAction() : + MetaAction(MetaActionType::GRADIENT) +{} + +MetaGradientAction::~MetaGradientAction() +{} + +MetaGradientAction::MetaGradientAction( const tools::Rectangle& rRect, Gradient aGradient ) : + MetaAction ( MetaActionType::GRADIENT ), + maRect ( rRect ), + maGradient (std::move( aGradient )) +{} + +void MetaGradientAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawGradient( maRect, maGradient ); +} + +rtl::Reference<MetaAction> MetaGradientAction::Clone() const +{ + return new MetaGradientAction( *this ); +} + +void MetaGradientAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); +} + +void MetaGradientAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); +} + +MetaGradientExAction::MetaGradientExAction() : + MetaAction ( MetaActionType::GRADIENTEX ) +{} + +MetaGradientExAction::MetaGradientExAction( tools::PolyPolygon aPolyPoly, Gradient aGradient ) : + MetaAction ( MetaActionType::GRADIENTEX ), + maPolyPoly (std::move( aPolyPoly )), + maGradient (std::move( aGradient )) +{} + +MetaGradientExAction::~MetaGradientExAction() +{} + +void MetaGradientExAction::Execute( OutputDevice* pOut ) +{ + if( pOut->GetConnectMetaFile() ) + { + pOut->GetConnectMetaFile()->AddAction( this ); + } +} + +rtl::Reference<MetaAction> MetaGradientExAction::Clone() const +{ + return new MetaGradientExAction( *this ); +} + +void MetaGradientExAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPolyPoly.Move( nHorzMove, nVertMove ); +} + +void MetaGradientExAction::Scale( double fScaleX, double fScaleY ) +{ + for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ ) + ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY ); +} + +MetaHatchAction::MetaHatchAction() : + MetaAction(MetaActionType::HATCH) +{} + +MetaHatchAction::~MetaHatchAction() +{} + +MetaHatchAction::MetaHatchAction( tools::PolyPolygon aPolyPoly, const Hatch& rHatch ) : + MetaAction ( MetaActionType::HATCH ), + maPolyPoly (std::move( aPolyPoly )), + maHatch ( rHatch ) +{} + +void MetaHatchAction::Execute( OutputDevice* pOut ) +{ + if (!AllowRect(pOut->LogicToPixel(maPolyPoly.GetBoundRect()))) + return; + if (!AllowDim(pOut->LogicToPixel(Point(maHatch.GetDistance(), 0)).X())) + return; + + pOut->DrawHatch( maPolyPoly, maHatch ); +} + +rtl::Reference<MetaAction> MetaHatchAction::Clone() const +{ + return new MetaHatchAction( *this ); +} + +void MetaHatchAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPolyPoly.Move( nHorzMove, nVertMove ); +} + +void MetaHatchAction::Scale( double fScaleX, double fScaleY ) +{ + for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ ) + ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY ); +} + +MetaWallpaperAction::MetaWallpaperAction() : + MetaAction(MetaActionType::WALLPAPER) +{} + +MetaWallpaperAction::~MetaWallpaperAction() +{} + +MetaWallpaperAction::MetaWallpaperAction( const tools::Rectangle& rRect, + const Wallpaper& rPaper ) : + MetaAction ( MetaActionType::WALLPAPER ), + maRect ( rRect ), + maWallpaper ( rPaper ) +{} + +void MetaWallpaperAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawWallpaper( maRect, maWallpaper ); +} + +rtl::Reference<MetaAction> MetaWallpaperAction::Clone() const +{ + return new MetaWallpaperAction( *this ); +} + +void MetaWallpaperAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); +} + +void MetaWallpaperAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); +} + +MetaClipRegionAction::MetaClipRegionAction() : + MetaAction ( MetaActionType::CLIPREGION ), + mbClip ( false ) +{} + +MetaClipRegionAction::~MetaClipRegionAction() +{} + +MetaClipRegionAction::MetaClipRegionAction( vcl::Region aRegion, bool bClip ) : + MetaAction ( MetaActionType::CLIPREGION ), + maRegion (std::move( aRegion )), + mbClip ( bClip ) +{} + +void MetaClipRegionAction::Execute( OutputDevice* pOut ) +{ + if( mbClip ) + pOut->SetClipRegion( maRegion ); + else + pOut->SetClipRegion(); +} + +rtl::Reference<MetaAction> MetaClipRegionAction::Clone() const +{ + return new MetaClipRegionAction( *this ); +} + +void MetaClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRegion.Move( nHorzMove, nVertMove ); +} + +void MetaClipRegionAction::Scale( double fScaleX, double fScaleY ) +{ + maRegion.Scale( fScaleX, fScaleY ); +} + +MetaISectRectClipRegionAction::MetaISectRectClipRegionAction() : + MetaAction(MetaActionType::ISECTRECTCLIPREGION) +{} + +MetaISectRectClipRegionAction::~MetaISectRectClipRegionAction() +{} + +MetaISectRectClipRegionAction::MetaISectRectClipRegionAction( const tools::Rectangle& rRect ) : + MetaAction ( MetaActionType::ISECTRECTCLIPREGION ), + maRect ( rRect ) +{} + +void MetaISectRectClipRegionAction::Execute( OutputDevice* pOut ) +{ + pOut->IntersectClipRegion( maRect ); +} + +rtl::Reference<MetaAction> MetaISectRectClipRegionAction::Clone() const +{ + return new MetaISectRectClipRegionAction( *this ); +} + +void MetaISectRectClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRect.Move( nHorzMove, nVertMove ); +} + +void MetaISectRectClipRegionAction::Scale( double fScaleX, double fScaleY ) +{ + ImplScaleRect( maRect, fScaleX, fScaleY ); +} + +MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction() : + MetaAction(MetaActionType::ISECTREGIONCLIPREGION) +{} + +MetaISectRegionClipRegionAction::~MetaISectRegionClipRegionAction() +{} + +MetaISectRegionClipRegionAction::MetaISectRegionClipRegionAction( vcl::Region aRegion ) : + MetaAction ( MetaActionType::ISECTREGIONCLIPREGION ), + maRegion (std::move( aRegion )) +{ +} + +void MetaISectRegionClipRegionAction::Execute( OutputDevice* pOut ) +{ + if (!AllowRect(pOut->LogicToPixel(maRegion.GetBoundRect()))) + return; + + pOut->IntersectClipRegion( maRegion ); +} + +rtl::Reference<MetaAction> MetaISectRegionClipRegionAction::Clone() const +{ + return new MetaISectRegionClipRegionAction( *this ); +} + +void MetaISectRegionClipRegionAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maRegion.Move( nHorzMove, nVertMove ); +} + +void MetaISectRegionClipRegionAction::Scale( double fScaleX, double fScaleY ) +{ + maRegion.Scale( fScaleX, fScaleY ); +} + +MetaMoveClipRegionAction::MetaMoveClipRegionAction() : + MetaAction ( MetaActionType::MOVECLIPREGION ), + mnHorzMove ( 0 ), + mnVertMove ( 0 ) +{} + +MetaMoveClipRegionAction::~MetaMoveClipRegionAction() +{} + +MetaMoveClipRegionAction::MetaMoveClipRegionAction( tools::Long nHorzMove, tools::Long nVertMove ) : + MetaAction ( MetaActionType::MOVECLIPREGION ), + mnHorzMove ( nHorzMove ), + mnVertMove ( nVertMove ) +{} + +void MetaMoveClipRegionAction::Execute( OutputDevice* pOut ) +{ + if (!AllowPoint(pOut->LogicToPixel(Point(mnHorzMove, mnVertMove)))) + return; + pOut->MoveClipRegion( mnHorzMove, mnVertMove ); +} + +rtl::Reference<MetaAction> MetaMoveClipRegionAction::Clone() const +{ + return new MetaMoveClipRegionAction( *this ); +} + +void MetaMoveClipRegionAction::Scale( double fScaleX, double fScaleY ) +{ + mnHorzMove = FRound( mnHorzMove * fScaleX ); + mnVertMove = FRound( mnVertMove * fScaleY ); +} + +MetaLineColorAction::MetaLineColorAction() : + MetaAction ( MetaActionType::LINECOLOR ), + mbSet ( false ) +{} + +MetaLineColorAction::~MetaLineColorAction() +{} + +MetaLineColorAction::MetaLineColorAction( const Color& rColor, bool bSet ) : + MetaAction ( MetaActionType::LINECOLOR ), + maColor ( rColor ), + mbSet ( bSet ) +{} + +void MetaLineColorAction::Execute( OutputDevice* pOut ) +{ + if( mbSet ) + pOut->SetLineColor( maColor ); + else + pOut->SetLineColor(); +} + +rtl::Reference<MetaAction> MetaLineColorAction::Clone() const +{ + return new MetaLineColorAction( *this ); +} + +MetaFillColorAction::MetaFillColorAction() : + MetaAction ( MetaActionType::FILLCOLOR ), + mbSet ( false ) +{} + +MetaFillColorAction::~MetaFillColorAction() +{} + +MetaFillColorAction::MetaFillColorAction( const Color& rColor, bool bSet ) : + MetaAction ( MetaActionType::FILLCOLOR ), + maColor ( rColor ), + mbSet ( bSet ) +{} + +void MetaFillColorAction::Execute( OutputDevice* pOut ) +{ + if( mbSet ) + pOut->SetFillColor( maColor ); + else + pOut->SetFillColor(); +} + +rtl::Reference<MetaAction> MetaFillColorAction::Clone() const +{ + return new MetaFillColorAction( *this ); +} + +MetaTextColorAction::MetaTextColorAction() : + MetaAction(MetaActionType::TEXTCOLOR) +{} + +MetaTextColorAction::~MetaTextColorAction() +{} + +MetaTextColorAction::MetaTextColorAction( const Color& rColor ) : + MetaAction ( MetaActionType::TEXTCOLOR ), + maColor ( rColor ) +{} + +void MetaTextColorAction::Execute( OutputDevice* pOut ) +{ + pOut->SetTextColor( maColor ); +} + +rtl::Reference<MetaAction> MetaTextColorAction::Clone() const +{ + return new MetaTextColorAction( *this ); +} + +MetaTextFillColorAction::MetaTextFillColorAction() : + MetaAction ( MetaActionType::TEXTFILLCOLOR ), + mbSet ( false ) +{} + +MetaTextFillColorAction::~MetaTextFillColorAction() +{} + +MetaTextFillColorAction::MetaTextFillColorAction( const Color& rColor, bool bSet ) : + MetaAction ( MetaActionType::TEXTFILLCOLOR ), + maColor ( rColor ), + mbSet ( bSet ) +{} + +void MetaTextFillColorAction::Execute( OutputDevice* pOut ) +{ + if( mbSet ) + pOut->SetTextFillColor( maColor ); + else + pOut->SetTextFillColor(); +} + +rtl::Reference<MetaAction> MetaTextFillColorAction::Clone() const +{ + return new MetaTextFillColorAction( *this ); +} + +MetaTextLineColorAction::MetaTextLineColorAction() : + MetaAction ( MetaActionType::TEXTLINECOLOR ), + mbSet ( false ) +{} + +MetaTextLineColorAction::~MetaTextLineColorAction() +{} + +MetaTextLineColorAction::MetaTextLineColorAction( const Color& rColor, bool bSet ) : + MetaAction ( MetaActionType::TEXTLINECOLOR ), + maColor ( rColor ), + mbSet ( bSet ) +{} + +void MetaTextLineColorAction::Execute( OutputDevice* pOut ) +{ + if( mbSet ) + pOut->SetTextLineColor( maColor ); + else + pOut->SetTextLineColor(); +} + +rtl::Reference<MetaAction> MetaTextLineColorAction::Clone() const +{ + return new MetaTextLineColorAction( *this ); +} + +MetaOverlineColorAction::MetaOverlineColorAction() : + MetaAction ( MetaActionType::OVERLINECOLOR ), + mbSet ( false ) +{} + +MetaOverlineColorAction::~MetaOverlineColorAction() +{} + +MetaOverlineColorAction::MetaOverlineColorAction( const Color& rColor, bool bSet ) : + MetaAction ( MetaActionType::OVERLINECOLOR ), + maColor ( rColor ), + mbSet ( bSet ) +{} + +void MetaOverlineColorAction::Execute( OutputDevice* pOut ) +{ + if( mbSet ) + pOut->SetOverlineColor( maColor ); + else + pOut->SetOverlineColor(); +} + +rtl::Reference<MetaAction> MetaOverlineColorAction::Clone() const +{ + return new MetaOverlineColorAction( *this ); +} + +MetaTextAlignAction::MetaTextAlignAction() : + MetaAction ( MetaActionType::TEXTALIGN ), + maAlign ( ALIGN_TOP ) +{} + +MetaTextAlignAction::~MetaTextAlignAction() +{} + +MetaTextAlignAction::MetaTextAlignAction( TextAlign aAlign ) : + MetaAction ( MetaActionType::TEXTALIGN ), + maAlign ( aAlign ) +{} + +void MetaTextAlignAction::Execute( OutputDevice* pOut ) +{ + pOut->SetTextAlign( maAlign ); +} + +rtl::Reference<MetaAction> MetaTextAlignAction::Clone() const +{ + return new MetaTextAlignAction( *this ); +} + +MetaMapModeAction::MetaMapModeAction() : + MetaAction(MetaActionType::MAPMODE) +{} + +MetaMapModeAction::~MetaMapModeAction() +{} + +MetaMapModeAction::MetaMapModeAction( const MapMode& rMapMode ) : + MetaAction ( MetaActionType::MAPMODE ), + maMapMode ( rMapMode ) +{} + +void MetaMapModeAction::Execute( OutputDevice* pOut ) +{ + pOut->SetMapMode( maMapMode ); +} + +rtl::Reference<MetaAction> MetaMapModeAction::Clone() const +{ + return new MetaMapModeAction( *this ); +} + +void MetaMapModeAction::Scale( double fScaleX, double fScaleY ) +{ + Point aPoint( maMapMode.GetOrigin() ); + + ImplScalePoint( aPoint, fScaleX, fScaleY ); + maMapMode.SetOrigin( aPoint ); +} + +MetaFontAction::MetaFontAction() : + MetaAction(MetaActionType::FONT) +{} + +MetaFontAction::~MetaFontAction() +{} + +MetaFontAction::MetaFontAction( vcl::Font aFont ) : + MetaAction ( MetaActionType::FONT ), + maFont (std::move( aFont )) +{ + // #96876: because RTL_TEXTENCODING_SYMBOL is often set at the OpenSymbol font, + // we change the textencoding to RTL_TEXTENCODING_UNICODE here, which seems + // to be the right way; changing the textencoding at other sources + // is too dangerous at the moment + if ( IsOpenSymbol( maFont.GetFamilyName() ) + && ( maFont.GetCharSet() != RTL_TEXTENCODING_UNICODE ) ) + { + SAL_WARN_IF(maFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL, "vcl", "OpenSymbol should not have charset of RTL_TEXTENCODING_SYMBOL in new documents"); + maFont.SetCharSet( RTL_TEXTENCODING_UNICODE ); + } +} + +void MetaFontAction::Execute( OutputDevice* pOut ) +{ + pOut->SetFont( maFont ); +} + +rtl::Reference<MetaAction> MetaFontAction::Clone() const +{ + return new MetaFontAction( *this ); +} + +void MetaFontAction::Scale( double fScaleX, double fScaleY ) +{ + const Size aSize( + FRound(maFont.GetFontSize().Width() * fabs(fScaleX)), + FRound(maFont.GetFontSize().Height() * fabs(fScaleY))); + maFont.SetFontSize( aSize ); +} + +MetaPushAction::MetaPushAction() : + MetaAction ( MetaActionType::PUSH ), + mnFlags ( vcl::PushFlags::NONE ) +{} + +MetaPushAction::~MetaPushAction() +{} + +MetaPushAction::MetaPushAction( vcl::PushFlags nFlags ) : + MetaAction ( MetaActionType::PUSH ), + mnFlags ( nFlags ) +{} + +void MetaPushAction::Execute( OutputDevice* pOut ) +{ + pOut->Push( mnFlags ); +} + +rtl::Reference<MetaAction> MetaPushAction::Clone() const +{ + return new MetaPushAction( *this ); +} + +MetaPopAction::MetaPopAction() : + MetaAction(MetaActionType::POP) +{} + +MetaPopAction::~MetaPopAction() +{} + +void MetaPopAction::Execute( OutputDevice* pOut ) +{ + pOut->Pop(); +} + +rtl::Reference<MetaAction> MetaPopAction::Clone() const +{ + return new MetaPopAction( *this ); +} + +MetaRasterOpAction::MetaRasterOpAction() : + MetaAction ( MetaActionType::RASTEROP ), + meRasterOp ( RasterOp::OverPaint ) +{} + +MetaRasterOpAction::~MetaRasterOpAction() +{} + +MetaRasterOpAction::MetaRasterOpAction( RasterOp eRasterOp ) : + MetaAction ( MetaActionType::RASTEROP ), + meRasterOp ( eRasterOp ) +{ +} + +void MetaRasterOpAction::Execute( OutputDevice* pOut ) +{ + pOut->SetRasterOp( meRasterOp ); +} + +rtl::Reference<MetaAction> MetaRasterOpAction::Clone() const +{ + return new MetaRasterOpAction( *this ); +} + +MetaTransparentAction::MetaTransparentAction() : + MetaAction ( MetaActionType::Transparent ), + mnTransPercent ( 0 ) +{} + +MetaTransparentAction::~MetaTransparentAction() +{} + +MetaTransparentAction::MetaTransparentAction( tools::PolyPolygon aPolyPoly, sal_uInt16 nTransPercent ) : + MetaAction ( MetaActionType::Transparent ), + maPolyPoly (std::move( aPolyPoly )), + mnTransPercent ( nTransPercent ) +{} + +void MetaTransparentAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawTransparent( maPolyPoly, mnTransPercent ); +} + +rtl::Reference<MetaAction> MetaTransparentAction::Clone() const +{ + return new MetaTransparentAction( *this ); +} + +void MetaTransparentAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPolyPoly.Move( nHorzMove, nVertMove ); +} + +void MetaTransparentAction::Scale( double fScaleX, double fScaleY ) +{ + for( sal_uInt16 i = 0, nCount = maPolyPoly.Count(); i < nCount; i++ ) + ImplScalePoly( maPolyPoly[ i ], fScaleX, fScaleY ); +} + +MetaFloatTransparentAction::MetaFloatTransparentAction() : + MetaAction(MetaActionType::FLOATTRANSPARENT) +{} + +MetaFloatTransparentAction::~MetaFloatTransparentAction() +{} + +MetaFloatTransparentAction::MetaFloatTransparentAction( const GDIMetaFile& rMtf, const Point& rPos, + const Size& rSize, Gradient aGradient ) : + MetaAction ( MetaActionType::FLOATTRANSPARENT ), + maMtf ( rMtf ), + maPoint ( rPos ), + maSize ( rSize ), + maGradient (std::move( aGradient )) +{} + +void MetaFloatTransparentAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawTransparent( maMtf, maPoint, maSize, maGradient ); +} + +rtl::Reference<MetaAction> MetaFloatTransparentAction::Clone() const +{ + return new MetaFloatTransparentAction( *this ); +} + +void MetaFloatTransparentAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPoint.Move( nHorzMove, nVertMove ); +} + +void MetaFloatTransparentAction::Scale( double fScaleX, double fScaleY ) +{ + tools::Rectangle aRectangle(maPoint, maSize); + ImplScaleRect( aRectangle, fScaleX, fScaleY ); + maPoint = aRectangle.TopLeft(); + maSize = aRectangle.GetSize(); +} + +void MetaFloatTransparentAction::addSVGTransparencyColorStops(const basegfx::BColorStops& rSVGTransparencyColorStops) +{ + maSVGTransparencyColorStops = rSVGTransparencyColorStops; +} + +MetaEPSAction::MetaEPSAction() : + MetaAction(MetaActionType::EPS) +{} + +MetaEPSAction::~MetaEPSAction() +{} + +MetaEPSAction::MetaEPSAction( const Point& rPoint, const Size& rSize, + GfxLink aGfxLink, const GDIMetaFile& rSubst ) : + MetaAction ( MetaActionType::EPS ), + maGfxLink (std::move( aGfxLink )), + maSubst ( rSubst ), + maPoint ( rPoint ), + maSize ( rSize ) +{} + +void MetaEPSAction::Execute( OutputDevice* pOut ) +{ + pOut->DrawEPS( maPoint, maSize, maGfxLink, &maSubst ); +} + +rtl::Reference<MetaAction> MetaEPSAction::Clone() const +{ + return new MetaEPSAction( *this ); +} + +void MetaEPSAction::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + maPoint.Move( nHorzMove, nVertMove ); +} + +void MetaEPSAction::Scale( double fScaleX, double fScaleY ) +{ + tools::Rectangle aRectangle(maPoint, maSize); + ImplScaleRect( aRectangle, fScaleX, fScaleY ); + maPoint = aRectangle.TopLeft(); + maSize = aRectangle.GetSize(); +} + +MetaRefPointAction::MetaRefPointAction() : + MetaAction ( MetaActionType::REFPOINT ), + mbSet ( false ) +{} + +MetaRefPointAction::~MetaRefPointAction() +{} + +MetaRefPointAction::MetaRefPointAction( const Point& rRefPoint, bool bSet ) : + MetaAction ( MetaActionType::REFPOINT ), + maRefPoint ( rRefPoint ), + mbSet ( bSet ) +{} + +void MetaRefPointAction::Execute( OutputDevice* pOut ) +{ + if( mbSet ) + pOut->SetRefPoint( maRefPoint ); + else + pOut->SetRefPoint(); +} + +rtl::Reference<MetaAction> MetaRefPointAction::Clone() const +{ + return new MetaRefPointAction( *this ); +} + +MetaCommentAction::MetaCommentAction() : + MetaAction ( MetaActionType::COMMENT ), + mnValue ( 0 ) +{ + ImplInitDynamicData( nullptr, 0UL ); +} + +MetaCommentAction::MetaCommentAction( const MetaCommentAction& rAct ) : + MetaAction ( MetaActionType::COMMENT ), + maComment ( rAct.maComment ), + mnValue ( rAct.mnValue ) +{ + ImplInitDynamicData( rAct.mpData.get(), rAct.mnDataSize ); +} + +MetaCommentAction::MetaCommentAction( OString aComment, sal_Int32 nValue, const sal_uInt8* pData, sal_uInt32 nDataSize ) : + MetaAction ( MetaActionType::COMMENT ), + maComment (std::move( aComment )), + mnValue ( nValue ) +{ + ImplInitDynamicData( pData, nDataSize ); +} + +MetaCommentAction::~MetaCommentAction() +{ +} + +void MetaCommentAction::ImplInitDynamicData( const sal_uInt8* pData, sal_uInt32 nDataSize ) +{ + if ( nDataSize && pData ) + { + mnDataSize = nDataSize; + mpData.reset( new sal_uInt8[ mnDataSize ] ); + memcpy( mpData.get(), pData, mnDataSize ); + } + else + { + mnDataSize = 0; + mpData = nullptr; + } +} + +void MetaCommentAction::Execute( OutputDevice* pOut ) +{ + if ( pOut->GetConnectMetaFile() ) + { + pOut->GetConnectMetaFile()->AddAction( this ); + } +} + +rtl::Reference<MetaAction> MetaCommentAction::Clone() const +{ + return new MetaCommentAction( *this ); +} + +void MetaCommentAction::Move( tools::Long nXMove, tools::Long nYMove ) +{ + if ( !(nXMove || nYMove) ) + return; + + if ( !(mnDataSize && mpData) ) + return; + + bool bPathStroke = (maComment == "XPATHSTROKE_SEQ_BEGIN"); + if ( !(bPathStroke || maComment == "XPATHFILL_SEQ_BEGIN") ) + return; + + SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ ); + SvMemoryStream aDest; + if ( bPathStroke ) + { + SvtGraphicStroke aStroke; + ReadSvtGraphicStroke( aMemStm, aStroke ); + + tools::Polygon aPath; + aStroke.getPath( aPath ); + aPath.Move( nXMove, nYMove ); + aStroke.setPath( aPath ); + + tools::PolyPolygon aStartArrow; + aStroke.getStartArrow(aStartArrow); + aStartArrow.Move(nXMove, nYMove); + aStroke.setStartArrow(aStartArrow); + + tools::PolyPolygon aEndArrow; + aStroke.getEndArrow(aEndArrow); + aEndArrow.Move(nXMove, nYMove); + aStroke.setEndArrow(aEndArrow); + + WriteSvtGraphicStroke( aDest, aStroke ); + } + else + { + SvtGraphicFill aFill; + ReadSvtGraphicFill( aMemStm, aFill ); + + tools::PolyPolygon aPath; + aFill.getPath( aPath ); + aPath.Move( nXMove, nYMove ); + aFill.setPath( aPath ); + + WriteSvtGraphicFill( aDest, aFill ); + } + mpData.reset(); + ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() ); +} + +// SJ: 25.07.06 #i56656# we are not able to mirror certain kind of +// comments properly, especially the XPATHSTROKE and XPATHFILL lead to +// problems, so it is better to remove these comments when mirroring +// FIXME: fake comment to apply the next hunk in the right location +void MetaCommentAction::Scale( double fXScale, double fYScale ) +{ + if (( fXScale == 1.0 ) && ( fYScale == 1.0 )) + return; + + if ( !(mnDataSize && mpData) ) + return; + + bool bPathStroke = (maComment == "XPATHSTROKE_SEQ_BEGIN"); + if ( bPathStroke || maComment == "XPATHFILL_SEQ_BEGIN" ) + { + SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ ); + SvMemoryStream aDest; + if ( bPathStroke ) + { + SvtGraphicStroke aStroke; + ReadSvtGraphicStroke( aMemStm, aStroke ); + aStroke.scale( fXScale, fYScale ); + WriteSvtGraphicStroke( aDest, aStroke ); + } + else + { + SvtGraphicFill aFill; + ReadSvtGraphicFill( aMemStm, aFill ); + tools::PolyPolygon aPath; + aFill.getPath( aPath ); + aPath.Scale( fXScale, fYScale ); + aFill.setPath( aPath ); + WriteSvtGraphicFill( aDest, aFill ); + } + mpData.reset(); + ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() ); + } else if( maComment == "EMF_PLUS_HEADER_INFO" ){ + SvMemoryStream aMemStm( static_cast<void*>(mpData.get()), mnDataSize, StreamMode::READ ); + SvMemoryStream aDest; + + sal_Int32 nLeft(0), nRight(0), nTop(0), nBottom(0); + sal_Int32 nPixX(0), nPixY(0), nMillX(0), nMillY(0); + float m11(0), m12(0), m21(0), m22(0), mdx(0), mdy(0); + + // read data + aMemStm.ReadInt32( nLeft ).ReadInt32( nTop ).ReadInt32( nRight ).ReadInt32( nBottom ); + aMemStm.ReadInt32( nPixX ).ReadInt32( nPixY ).ReadInt32( nMillX ).ReadInt32( nMillY ); + aMemStm.ReadFloat( m11 ).ReadFloat( m12 ).ReadFloat( m21 ).ReadFloat( m22 ).ReadFloat( mdx ).ReadFloat( mdy ); + + // add scale to the transformation + m11 *= fXScale; + m12 *= fXScale; + m22 *= fYScale; + m21 *= fYScale; + + // prepare new data + aDest.WriteInt32( nLeft ).WriteInt32( nTop ).WriteInt32( nRight ).WriteInt32( nBottom ); + aDest.WriteInt32( nPixX ).WriteInt32( nPixY ).WriteInt32( nMillX ).WriteInt32( nMillY ); + aDest.WriteFloat( m11 ).WriteFloat( m12 ).WriteFloat( m21 ).WriteFloat( m22 ).WriteFloat( mdx ).WriteFloat( mdy ); + + // save them + ImplInitDynamicData( static_cast<const sal_uInt8*>( aDest.GetData() ), aDest.Tell() ); + } +} + +MetaLayoutModeAction::MetaLayoutModeAction() : + MetaAction ( MetaActionType::LAYOUTMODE ), + mnLayoutMode( vcl::text::ComplexTextLayoutFlags::Default ) +{} + +MetaLayoutModeAction::~MetaLayoutModeAction() +{} + +MetaLayoutModeAction::MetaLayoutModeAction( vcl::text::ComplexTextLayoutFlags nLayoutMode ) : + MetaAction ( MetaActionType::LAYOUTMODE ), + mnLayoutMode( nLayoutMode ) +{} + +void MetaLayoutModeAction::Execute( OutputDevice* pOut ) +{ + pOut->SetLayoutMode( mnLayoutMode ); +} + +rtl::Reference<MetaAction> MetaLayoutModeAction::Clone() const +{ + return new MetaLayoutModeAction( *this ); +} + +MetaTextLanguageAction::MetaTextLanguageAction() : + MetaAction ( MetaActionType::TEXTLANGUAGE ), + meTextLanguage( LANGUAGE_DONTKNOW ) +{} + +MetaTextLanguageAction::~MetaTextLanguageAction() +{} + +MetaTextLanguageAction::MetaTextLanguageAction( LanguageType eTextLanguage ) : + MetaAction ( MetaActionType::TEXTLANGUAGE ), + meTextLanguage( eTextLanguage ) +{} + +void MetaTextLanguageAction::Execute( OutputDevice* pOut ) +{ + pOut->SetDigitLanguage( meTextLanguage ); +} + +rtl::Reference<MetaAction> MetaTextLanguageAction::Clone() const +{ + return new MetaTextLanguageAction( *this ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/mtfxmldump.cxx b/vcl/source/gdi/mtfxmldump.cxx new file mode 100644 index 0000000000..e6b1adc80e --- /dev/null +++ b/vcl/source/gdi/mtfxmldump.cxx @@ -0,0 +1,1568 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <vcl/mtfxmldump.hxx> +#include <tools/XmlWriter.hxx> +#include <tools/fract.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/BitmapReadAccess.hxx> + +#include <rtl/string.hxx> +#include <rtl/ustrbuf.hxx> + +#include <comphelper/hash.hxx> + +#include <sstream> + +namespace +{ + +OUString collectPushFlags(vcl::PushFlags nFlags) +{ + if ((nFlags & vcl::PushFlags::ALL) == vcl::PushFlags::ALL) + return "PushAll"; + else if ((nFlags & PUSH_ALLFONT) == PUSH_ALLFONT) + return "PushAllFont"; + + std::vector<OUString> aStrings; + + if (nFlags & vcl::PushFlags::LINECOLOR) + aStrings.emplace_back("PushLineColor"); + if (nFlags & vcl::PushFlags::FILLCOLOR) + aStrings.emplace_back("PushFillColor"); + if (nFlags & vcl::PushFlags::FONT) + aStrings.emplace_back("PushFont"); + if (nFlags & vcl::PushFlags::TEXTCOLOR) + aStrings.emplace_back("PushTextColor"); + if (nFlags & vcl::PushFlags::MAPMODE) + aStrings.emplace_back("PushMapMode"); + if (nFlags & vcl::PushFlags::CLIPREGION) + aStrings.emplace_back("PushClipRegion"); + if (nFlags & vcl::PushFlags::RASTEROP) + aStrings.emplace_back("PushRasterOp"); + if (nFlags & vcl::PushFlags::TEXTFILLCOLOR) + aStrings.emplace_back("PushTextFillColor"); + if (nFlags & vcl::PushFlags::TEXTALIGN) + aStrings.emplace_back("PushTextAlign"); + if (nFlags & vcl::PushFlags::REFPOINT) + aStrings.emplace_back("PushRefPoint"); + if (nFlags & vcl::PushFlags::TEXTLINECOLOR) + aStrings.emplace_back("PushTextLineColor"); + if (nFlags & vcl::PushFlags::TEXTLAYOUTMODE) + aStrings.emplace_back("PushTextLayoutMode"); + if (nFlags & vcl::PushFlags::TEXTLANGUAGE) + aStrings.emplace_back("PushTextLanguage"); + if (nFlags & vcl::PushFlags::OVERLINECOLOR) + aStrings.emplace_back("PushOverlineColor"); + if (nFlags & vcl::PushFlags::RTLENABLED) + aStrings.emplace_back("PushRTLEnabled"); + + OUString aString; + + if (aStrings.empty()) + return aString; + + aString = aStrings[0]; + for (size_t i = 1; i < aStrings.size(); ++i) + { + aString += ", " + aStrings[i]; + } + return aString; +} + +OUString convertDrawTextFlagsToString(DrawTextFlags eDrawTextFlags) +{ + std::vector<OUString> aStrings; + if (eDrawTextFlags & DrawTextFlags::Disable) + aStrings.emplace_back("Disable"); + if (eDrawTextFlags & DrawTextFlags::Mnemonic) + aStrings.emplace_back("Mnemonic"); + if (eDrawTextFlags & DrawTextFlags::Mono) + aStrings.emplace_back("Mono"); + if (eDrawTextFlags & DrawTextFlags::Clip) + aStrings.emplace_back("Clip"); + if (eDrawTextFlags & DrawTextFlags::Left) + aStrings.emplace_back("Left"); + if (eDrawTextFlags & DrawTextFlags::Center) + aStrings.emplace_back("Center"); + if (eDrawTextFlags & DrawTextFlags::Right) + aStrings.emplace_back("Right"); + if (eDrawTextFlags & DrawTextFlags::Top) + aStrings.emplace_back("Top"); + if (eDrawTextFlags & DrawTextFlags::VCenter) + aStrings.emplace_back("VCenter"); + if (eDrawTextFlags & DrawTextFlags::Bottom) + aStrings.emplace_back("Bottom"); + if (eDrawTextFlags & DrawTextFlags::EndEllipsis) + aStrings.emplace_back("EndEllipsis"); + if (eDrawTextFlags & DrawTextFlags::PathEllipsis) + aStrings.emplace_back("PathEllipsis"); + if (eDrawTextFlags & DrawTextFlags::MultiLine) + aStrings.emplace_back("MultiLine"); + if (eDrawTextFlags & DrawTextFlags::WordBreak) + aStrings.emplace_back("WordBreak"); + if (eDrawTextFlags & DrawTextFlags::NewsEllipsis) + aStrings.emplace_back("NewsEllipsis"); + if (eDrawTextFlags & DrawTextFlags::WordBreakHyphenation) + aStrings.emplace_back("WordBreakHyphenation"); + if (eDrawTextFlags & DrawTextFlags::CenterEllipsis) + aStrings.emplace_back("CenterEllipsis"); + + OUString aString; + + if (aStrings.empty()) + return "None"; + + aString = aStrings[0]; + for (size_t i = 1; i < aStrings.size(); ++i) + { + aString += " " + aStrings[i]; + } + return aString; +}; + +OUString convertRopToString(RasterOp eRop) +{ + switch (eRop) + { + case RasterOp::OverPaint: return "overpaint"; + case RasterOp::Xor: return "xor"; + case RasterOp::N0: return "0"; + case RasterOp::N1: return "1"; + case RasterOp::Invert: return "invert"; + } + return OUString(); +} + +OUString convertTextAlignToString(TextAlign eAlign) +{ + switch (eAlign) + { + case ALIGN_BASELINE: return "baseline"; + case ALIGN_BOTTOM: return "bottom"; + case ALIGN_TOP: return "top"; + case TextAlign_FORCE_EQUAL_SIZE: return "equalsize"; + } + return OUString(); +} + +OUString convertColorToString(Color aColor) +{ + OUString aRGBString = aColor.AsRGBHexString(); + return "#" + aRGBString; +} + +OUString convertLineStyleToString(LineStyle eAlign) +{ + switch (eAlign) + { + case LineStyle::NONE: return "none"; + case LineStyle::Solid: return "solid"; + case LineStyle::Dash: return "dash"; + default: break; + } + return OUString(); +} + +OUString convertLineJoinToString(basegfx::B2DLineJoin eJoin) +{ + switch (eJoin) + { + default: + case basegfx::B2DLineJoin::NONE: return "none"; + case basegfx::B2DLineJoin::Bevel: return "bevel"; + case basegfx::B2DLineJoin::Miter: return "miter"; + case basegfx::B2DLineJoin::Round: return "round"; + } +} + +OUString convertLineCapToString(css::drawing::LineCap eCap) +{ + switch (eCap) + { + default: + case css::drawing::LineCap_BUTT: return "butt"; + case css::drawing::LineCap_ROUND: return "round"; + case css::drawing::LineCap_SQUARE: return "square"; + } +} + +OUString convertPolygonFlags(PolyFlags eFlags) +{ + switch (eFlags) + { + default: + case PolyFlags::Normal: return "normal"; + case PolyFlags::Control: return "control"; + case PolyFlags::Smooth: return "smooth"; + case PolyFlags::Symmetric: return "symmetric"; + } +} + +OUString convertFontWeightToString(FontWeight eFontWeight) +{ + switch (eFontWeight) + { + case WEIGHT_DONTKNOW: return "unknown"; + case WEIGHT_THIN: return "thin"; + case WEIGHT_ULTRALIGHT: return "ultralight"; + case WEIGHT_LIGHT: return "light"; + case WEIGHT_SEMILIGHT: return "semilight"; + case WEIGHT_NORMAL: return "normal"; + case WEIGHT_MEDIUM: return "medium"; + case WEIGHT_SEMIBOLD: return "semibold"; + case WEIGHT_BOLD: return "bold"; + case WEIGHT_ULTRABOLD: return "ultrabold"; + case WEIGHT_BLACK: return "black"; + case FontWeight_FORCE_EQUAL_SIZE: return "equalsize"; + } + return OUString(); +} + +OUString convertFontStrikeoutToString(FontStrikeout eFontStrikeout) +{ + switch (eFontStrikeout) + { + case STRIKEOUT_NONE: return "none"; + case STRIKEOUT_SINGLE: return "single"; + case STRIKEOUT_DOUBLE: return "double"; + case STRIKEOUT_DONTKNOW: return "dontknow"; + case STRIKEOUT_BOLD: return "bold"; + case STRIKEOUT_SLASH: return "slash"; + case STRIKEOUT_X: return "x"; + case FontStrikeout_FORCE_EQUAL_SIZE: return "equalsize"; + } + return OUString(); +} + +OUString convertFontLineStyleToString(FontLineStyle eFontLineStyle) +{ + switch (eFontLineStyle) + { + case LINESTYLE_NONE: return "none"; + case LINESTYLE_SINGLE: return "single"; + case LINESTYLE_DOUBLE: return "double"; + case LINESTYLE_DOTTED: return "dotted"; + case LINESTYLE_DONTKNOW: return "dontknow"; + case LINESTYLE_DASH: return "dash"; + case LINESTYLE_LONGDASH: return "longdash"; + case LINESTYLE_DASHDOT: return "dashdot"; + case LINESTYLE_DASHDOTDOT: return "dashdotdot"; + case LINESTYLE_SMALLWAVE: return "smallwave"; + case LINESTYLE_WAVE: return "wave"; + case LINESTYLE_DOUBLEWAVE: return "doublewave"; + case LINESTYLE_BOLD: return "bold"; + case LINESTYLE_BOLDDOTTED: return "bolddotted"; + case LINESTYLE_BOLDDASH: return "bolddash"; + case LINESTYLE_BOLDLONGDASH: return "boldlongdash"; + case LINESTYLE_BOLDDASHDOT: return "bolddashdot"; + case LINESTYLE_BOLDDASHDOTDOT: return "bolddashdotdot"; + case LINESTYLE_BOLDWAVE: return "boldwave"; + case FontLineStyle_FORCE_EQUAL_SIZE: return "equalsize"; + } + return OUString(); +} + +OString convertLineStyleToString(const MetaActionType nActionType) +{ + switch (nActionType) + { + case MetaActionType::NONE: return "null"_ostr; + case MetaActionType::PIXEL: return "pixel"_ostr; + case MetaActionType::POINT: return "point"_ostr; + case MetaActionType::LINE: return "line"_ostr; + case MetaActionType::RECT: return "rect"_ostr; + case MetaActionType::ROUNDRECT: return "roundrect"_ostr; + case MetaActionType::ELLIPSE: return "ellipse"_ostr; + case MetaActionType::ARC: return "arc"_ostr; + case MetaActionType::PIE: return "pie"_ostr; + case MetaActionType::CHORD: return "chord"_ostr; + case MetaActionType::POLYLINE: return "polyline"_ostr; + case MetaActionType::POLYGON: return "polygon"_ostr; + case MetaActionType::POLYPOLYGON: return "polypolygon"_ostr; + case MetaActionType::TEXT: return "text"_ostr; + case MetaActionType::TEXTARRAY: return "textarray"_ostr; + case MetaActionType::STRETCHTEXT: return "stretchtext"_ostr; + case MetaActionType::TEXTRECT: return "textrect"_ostr; + case MetaActionType::TEXTLINE: return "textline"_ostr; + case MetaActionType::BMP: return "bmp"_ostr; + case MetaActionType::BMPSCALE: return "bmpscale"_ostr; + case MetaActionType::BMPSCALEPART: return "bmpscalepart"_ostr; + case MetaActionType::BMPEX: return "bmpex"_ostr; + case MetaActionType::BMPEXSCALE: return "bmpexscale"_ostr; + case MetaActionType::BMPEXSCALEPART: return "bmpexscalepart"_ostr; + case MetaActionType::MASK: return "mask"_ostr; + case MetaActionType::MASKSCALE: return "maskscale"_ostr; + case MetaActionType::MASKSCALEPART: return "maskscalepart"_ostr; + case MetaActionType::GRADIENT: return "gradient"_ostr; + case MetaActionType::GRADIENTEX: return "gradientex"_ostr; + case MetaActionType::HATCH: return "hatch"_ostr; + case MetaActionType::WALLPAPER: return "wallpaper"_ostr; + case MetaActionType::CLIPREGION: return "clipregion"_ostr; + case MetaActionType::ISECTRECTCLIPREGION: return "sectrectclipregion"_ostr; + case MetaActionType::ISECTREGIONCLIPREGION: return "sectregionclipregion"_ostr; + case MetaActionType::MOVECLIPREGION: return "moveclipregion"_ostr; + case MetaActionType::LINECOLOR: return "linecolor"_ostr; + case MetaActionType::FILLCOLOR: return "fillcolor"_ostr; + case MetaActionType::TEXTCOLOR: return "textcolor"_ostr; + case MetaActionType::TEXTFILLCOLOR: return "textfillcolor"_ostr; + case MetaActionType::TEXTLINECOLOR: return "textlinecolor"_ostr; + case MetaActionType::OVERLINECOLOR: return "overlinecolor"_ostr; + case MetaActionType::TEXTALIGN: return "textalign"_ostr; + case MetaActionType::MAPMODE: return "mapmode"_ostr; + case MetaActionType::FONT: return "font"_ostr; + case MetaActionType::PUSH: return "push"_ostr; + case MetaActionType::POP: return "pop"_ostr; + case MetaActionType::RASTEROP: return "rasterop"_ostr; + case MetaActionType::Transparent: return "transparent"_ostr; + case MetaActionType::FLOATTRANSPARENT: return "floattransparent"_ostr; + case MetaActionType::EPS: return "eps"_ostr; + case MetaActionType::REFPOINT: return "refpoint"_ostr; + case MetaActionType::COMMENT: return "comment"_ostr; + case MetaActionType::LAYOUTMODE: return "layoutmode"_ostr; + case MetaActionType::TEXTLANGUAGE: return "textlanguage"_ostr; + } + return ""_ostr; +} + +OUString convertBitmapExTransparentType(BitmapEx const & rBitmapEx) +{ + if (rBitmapEx.IsAlpha()) + return "bitmap"; + else + return "none"; +} + +OUString convertMapUnitToString(MapUnit eUnit) +{ + switch (eUnit) + { + default: + case MapUnit::LASTENUMDUMMY: return "LASTENUMDUMMY"; + case MapUnit::Map1000thInch: return "Map1000thInch"; + case MapUnit::Map100thInch: return "Map100thInch"; + case MapUnit::Map100thMM: return "Map100thMM"; + case MapUnit::Map10thInch: return "Map10thInch"; + case MapUnit::Map10thMM: return "Map10thMM"; + case MapUnit::MapAppFont: return "MapAppFont"; + case MapUnit::MapCM: return "MapCM"; + case MapUnit::MapInch: return "MapInch"; + case MapUnit::MapMM: return "MapMM"; + case MapUnit::MapPixel: return "MapPixel"; + case MapUnit::MapPoint: return "MapPoint"; + case MapUnit::MapRelative: return "MapRelative"; + case MapUnit::MapSysFont: return "MapSysFont"; + case MapUnit::MapTwip: return "MapTwip"; + } +} + +OUString convertFractionToString(const Fraction& aFraction) +{ + std::stringstream ss; + + ss << aFraction; + + return OUString::createFromAscii(ss.str()); +} + +OUString convertGradientStyleToOUString(css::awt::GradientStyle eStyle) +{ + switch (eStyle) + { + case css::awt::GradientStyle_LINEAR: return "Linear"; + case css::awt::GradientStyle_AXIAL: return "Axial"; + case css::awt::GradientStyle_RADIAL: return "Radial"; + case css::awt::GradientStyle_ELLIPTICAL: return "Elliptical"; + case css::awt::GradientStyle_SQUARE: return "Square"; + case css::awt::GradientStyle_RECT: return "Rect"; + case css::awt::GradientStyle::GradientStyle_MAKE_FIXED_SIZE: return "ForceEqualSize"; + } + return OUString(); +} + +OUString convertHatchStyle(HatchStyle eStyle) +{ + switch (eStyle) + { + case HatchStyle::Single: return "Single"; + case HatchStyle::Double: return "Double"; + case HatchStyle::Triple: return "Triple"; + case HatchStyle::FORCE_EQUAL_SIZE: return "ForceEqualSize"; + } + return OUString(); +} + +OUString convertLanguageTypeToString(LanguageType rLanguageType) +{ + std::stringstream ss; + ss << std::hex << std::setfill ('0') << std::setw(4) << rLanguageType.get(); + return "#" + OUString::createFromAscii(ss.str()); +} + +OUString convertWallpaperStyleToString(WallpaperStyle eWallpaperStyle) +{ + switch (eWallpaperStyle) + { + case WallpaperStyle::NONE: return "NONE"; + case WallpaperStyle::Tile: return "Tile"; + case WallpaperStyle::Center: return "Center"; + case WallpaperStyle::Scale: return "Scale"; + case WallpaperStyle::TopLeft: return "TopLeft"; + case WallpaperStyle::Top: return "Top"; + case WallpaperStyle::TopRight: return "TopRight"; + case WallpaperStyle::Left: return "Left"; + case WallpaperStyle::Right: return "Right"; + case WallpaperStyle::BottomLeft: return "BottomLeft"; + case WallpaperStyle::Bottom: return "Bottom"; + case WallpaperStyle::BottomRight: return "BottomRight"; + case WallpaperStyle::ApplicationGradient: return "ApplicationGradient"; + } + return OUString(); +} + +OUString convertPixelFormatToString(vcl::PixelFormat ePixelFormat) +{ + switch (ePixelFormat) + { + case vcl::PixelFormat::INVALID: return "INVALID"; + case vcl::PixelFormat::N8_BPP: return "8BPP"; + case vcl::PixelFormat::N24_BPP: return "24BPP"; + case vcl::PixelFormat::N32_BPP: return "32BPP"; + } + return OUString(); +} + +OUString convertComplexTestLayoutFlags(vcl::text::ComplexTextLayoutFlags nFlags) +{ + if (nFlags == vcl::text::ComplexTextLayoutFlags::Default) + return "Default"; + + std::vector<OUString> aStrings; + + if (nFlags & vcl::text::ComplexTextLayoutFlags::BiDiRtl) + aStrings.emplace_back("BiDiRtl"); + if (nFlags & vcl::text::ComplexTextLayoutFlags::BiDiStrong) + aStrings.emplace_back("BiDiStrong"); + if (nFlags & vcl::text::ComplexTextLayoutFlags::TextOriginLeft) + aStrings.emplace_back("TextOriginLeft"); + if (nFlags & vcl::text::ComplexTextLayoutFlags::TextOriginRight) + aStrings.emplace_back("TextOriginRight"); + + OUString aString; + + if (aStrings.empty()) + return aString; + + aString = aStrings[0]; + for (size_t i = 1; i < aStrings.size(); ++i) + { + aString += ", " + aStrings[i]; + } + return aString; +} + +OUString convertGfxLinkTypeToString(GfxLinkType eGfxLinkType) +{ + switch(eGfxLinkType) + { + case GfxLinkType::EpsBuffer: return "EpsBuffer"; + case GfxLinkType::NativeBmp: return "NativeBmp"; + case GfxLinkType::NativeGif: return "NativeGif"; + case GfxLinkType::NativeJpg: return "NativeJpg"; + case GfxLinkType::NativeMet: return "NativeMet"; + case GfxLinkType::NativeMov: return "NativeMov"; + case GfxLinkType::NativePct: return "NativePct"; + case GfxLinkType::NativePdf: return "NativePdf"; + case GfxLinkType::NativePng: return "NativePng"; + case GfxLinkType::NativeSvg: return "NativeSvg"; + case GfxLinkType::NativeTif: return "NativeTif"; + case GfxLinkType::NativeWmf: return "NativeWmf"; + case GfxLinkType::NativeWebp: return "NativeWebp"; + case GfxLinkType::NONE: return "None"; + } + return OUString(); +} + +OUString hex32(sal_uInt32 nNumber) +{ + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::setw(8) << nNumber; + return OUString::createFromAscii(ss.str()); +} + +OUString toHexString(const sal_uInt8* nData, sal_uInt32 nDataSize){ + + std::stringstream aStrm; + for (sal_uInt32 i = 0; i < nDataSize; i++) + { + aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(nData[i]); + } + + return OUString::createFromAscii(aStrm.str()); +} + +void writePoint(tools::XmlWriter& rWriter, Point const& rPoint) +{ + rWriter.attribute("x", rPoint.X()); + rWriter.attribute("y", rPoint.Y()); +} + +void writeStartPoint(tools::XmlWriter& rWriter, Point const& rPoint) +{ + rWriter.attribute("startx", rPoint.X()); + rWriter.attribute("starty", rPoint.Y()); +} + +void writeEndPoint(tools::XmlWriter& rWriter, Point const& rPoint) +{ + rWriter.attribute("endx", rPoint.X()); + rWriter.attribute("endy", rPoint.Y()); +} + +void writeSize(tools::XmlWriter& rWriter, Size const& rSize) +{ + rWriter.attribute("width", rSize.Width()); + rWriter.attribute("height", rSize.Height()); +} + +void writeRectangle(tools::XmlWriter& rWriter, tools::Rectangle const& rRectangle) +{ + rWriter.attribute("left", rRectangle.Left()); + rWriter.attribute("top", rRectangle.Top()); + if (rRectangle.IsWidthEmpty()) + rWriter.attribute("right", "empty"_ostr); + else + rWriter.attribute("right", rRectangle.Right()); + if (rRectangle.IsHeightEmpty()) + rWriter.attribute("bottom", "empty"_ostr); + else + rWriter.attribute("bottom", rRectangle.Bottom()); +} + +void writeMapMode(tools::XmlWriter& rWriter, MapMode const& rMapMode) +{ + rWriter.attribute("mapunit", convertMapUnitToString( rMapMode.GetMapUnit() )); + writePoint(rWriter, rMapMode.GetOrigin()); + rWriter.attribute("scalex", convertFractionToString(rMapMode.GetScaleX())); + rWriter.attribute("scaley", convertFractionToString(rMapMode.GetScaleY())); +} + +void writeLineInfo(tools::XmlWriter& rWriter, LineInfo const& rLineInfo) +{ + rWriter.attribute("style", convertLineStyleToString(rLineInfo.GetStyle())); + rWriter.attribute("width", rLineInfo.GetWidth()); + rWriter.attribute("dashlen", rLineInfo.GetDashLen()); + rWriter.attribute("dashcount", rLineInfo.GetDashCount()); + rWriter.attribute("dotlen", rLineInfo.GetDotLen()); + rWriter.attribute("dotcount", rLineInfo.GetDotCount()); + rWriter.attribute("distance", rLineInfo.GetDistance()); + rWriter.attribute("join", convertLineJoinToString(rLineInfo.GetLineJoin())); + rWriter.attribute("cap", convertLineCapToString(rLineInfo.GetLineCap())); +} + +void writeGradient(tools::XmlWriter& rWriter, Gradient const& rGradient) +{ + rWriter.attribute("style", convertGradientStyleToOUString(rGradient.GetStyle())); + rWriter.attribute("startcolor", convertColorToString(rGradient.GetStartColor())); + rWriter.attribute("endcolor", convertColorToString(rGradient.GetEndColor())); + rWriter.attribute("angle", rGradient.GetAngle().get()); + rWriter.attribute("border", rGradient.GetBorder()); + rWriter.attribute("offsetx", rGradient.GetOfsX()); + rWriter.attribute("offsety", rGradient.GetOfsY()); + rWriter.attribute("startintensity", rGradient.GetStartIntensity()); + rWriter.attribute("endintensity", rGradient.GetEndIntensity()); + rWriter.attribute("steps", rGradient.GetSteps()); +} + +OString toHexString(const std::vector<unsigned char>& a) +{ + std::stringstream aStrm; + for (auto& i : a) + { + aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(i); + } + + return OString(aStrm.str()); +} + +void writeBitmapContentChecksum(tools::XmlWriter& rWriter, Bitmap const& rBitmap) +{ + Bitmap aBitmap(rBitmap); + + comphelper::Hash aHashEngine(comphelper::HashType::SHA1); + BitmapScopedReadAccess pReadAccess(aBitmap); + assert(pReadAccess); + + for (tools::Long y = 0 ; y < pReadAccess->Height() ; ++y) + { + for (tools::Long x = 0 ; x < pReadAccess->Width() ; ++x) + { + BitmapColor aColor = pReadAccess->GetColor(y, x); + sal_uInt8 r = aColor.GetRed(); + sal_uInt8 g = aColor.GetGreen(); + sal_uInt8 b = aColor.GetBlue(); + sal_uInt8 a = aColor.GetAlpha(); + aHashEngine.update(&r, 1); + aHashEngine.update(&g, 1); + aHashEngine.update(&b, 1); + aHashEngine.update(&a, 1); + } + } + std::vector<unsigned char> aVector = aHashEngine.finalize(); + rWriter.attribute("contentchecksum", toHexString(aVector)); +} + +void writeBitmap(tools::XmlWriter& rWriter, Bitmap const& rBitmap) +{ + writeBitmapContentChecksum(rWriter, rBitmap); + rWriter.attribute("bitmapwidth", rBitmap.GetSizePixel().Width()); + rWriter.attribute("bitmapheight", rBitmap.GetSizePixel().Height()); + rWriter.attribute("pixelformat", convertPixelFormatToString(rBitmap.getPixelFormat())); + rWriter.attribute("crc", hex32(rBitmap.GetChecksum())); +} + +} // anonymous namespace + +MetafileXmlDump::MetafileXmlDump() +{ + maFilter.fill(false); +} + +void MetafileXmlDump::filterActionType(const MetaActionType nActionType, bool bShouldFilter) +{ + maFilter[nActionType] = bShouldFilter; +} + +void MetafileXmlDump::filterAllActionTypes() +{ + maFilter.fill(true); +} + +void MetafileXmlDump::dump(const GDIMetaFile& rMetaFile, SvStream& rStream) +{ + tools::XmlWriter aWriter(&rStream); + aWriter.startDocument(); + aWriter.startElement("metafile"); + + writeXml(rMetaFile, aWriter); + + aWriter.endElement(); + aWriter.endDocument(); +} + +void MetafileXmlDump::writeXml(const GDIMetaFile& rMetaFile, tools::XmlWriter& rWriter) +{ + MapMode aMtfMapMode = rMetaFile.GetPrefMapMode(); + rWriter.attribute("mapunit", convertMapUnitToString(aMtfMapMode.GetMapUnit())); + writePoint(rWriter, aMtfMapMode.GetOrigin()); + rWriter.attribute("scalex", convertFractionToString(aMtfMapMode.GetScaleX())); + rWriter.attribute("scaley", convertFractionToString(aMtfMapMode.GetScaleY())); + + Size aMtfSize = rMetaFile.GetPrefSize(); + writeSize(rWriter, aMtfSize); + + for(size_t nAction = 0; nAction < rMetaFile.GetActionSize(); ++nAction) + { + MetaAction* pAction = rMetaFile.GetAction(nAction); + const MetaActionType nActionType = pAction->GetType(); + if (maFilter[nActionType]) + continue; + + OString sCurrentElementTag = convertLineStyleToString(nActionType); + + switch (nActionType) + { + case MetaActionType::NONE: + { + rWriter.startElement(sCurrentElementTag); + rWriter.endElement(); + } + break; + + case MetaActionType::PIXEL: + { + auto* pMetaAction = static_cast<MetaPixelAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMetaAction->GetPoint()); + rWriter.attribute("color", convertColorToString(pMetaAction->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::POINT: + { + auto* pMetaAction = static_cast<MetaPointAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMetaAction->GetPoint()); + rWriter.endElement(); + } + break; + + case MetaActionType::LINE: + { + MetaLineAction* pMetaLineAction = static_cast<MetaLineAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writeStartPoint(rWriter, pMetaLineAction->GetStartPoint()); + writeEndPoint(rWriter, pMetaLineAction->GetEndPoint()); + + writeLineInfo(rWriter, pMetaLineAction->GetLineInfo()); + rWriter.endElement(); + } + break; + + case MetaActionType::RECT: + { + MetaRectAction* pMetaAction = static_cast<MetaRectAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writeRectangle(rWriter, pMetaAction->GetRect()); + rWriter.endElement(); + } + break; + + case MetaActionType::ROUNDRECT: + { + auto pMetaAction = static_cast<MetaRoundRectAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writeRectangle(rWriter, pMetaAction->GetRect()); + rWriter.attribute("horizontalround", pMetaAction->GetHorzRound()); + rWriter.attribute("verticalround", pMetaAction->GetVertRound()); + rWriter.endElement(); + } + break; + + case MetaActionType::ELLIPSE: + { + auto pMetaAction = static_cast<MetaEllipseAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writeRectangle(rWriter, pMetaAction->GetRect()); + rWriter.endElement(); + } + break; + + case MetaActionType::ARC: + { + auto pMetaAction = static_cast<MetaArcAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writeRectangle(rWriter, pMetaAction->GetRect()); + writeStartPoint(rWriter, pMetaAction->GetStartPoint()); + writeEndPoint(rWriter, pMetaAction->GetEndPoint()); + rWriter.endElement(); + } + break; + + case MetaActionType::PIE: + { + auto pMetaAction = static_cast<MetaPieAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writeRectangle(rWriter, pMetaAction->GetRect()); + writeStartPoint(rWriter, pMetaAction->GetStartPoint()); + writeEndPoint(rWriter, pMetaAction->GetEndPoint()); + rWriter.endElement(); + } + break; + + case MetaActionType::CHORD: + { + auto pMetaAction = static_cast<MetaChordAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writeRectangle(rWriter, pMetaAction->GetRect()); + writeStartPoint(rWriter, pMetaAction->GetStartPoint()); + writeEndPoint(rWriter, pMetaAction->GetEndPoint()); + rWriter.endElement(); + } + break; + + case MetaActionType::POLYLINE: + { + MetaPolyLineAction* pMetaPolyLineAction = static_cast<MetaPolyLineAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + writeLineInfo(rWriter, pMetaPolyLineAction->GetLineInfo()); + + tools::Polygon aPolygon = pMetaPolyLineAction->GetPolygon(); + bool bFlags = aPolygon.HasFlags(); + for (sal_uInt16 i = 0; i < aPolygon.GetSize(); i++) + { + rWriter.startElement("point"); + writePoint(rWriter, aPolygon[i]); + if (bFlags) + rWriter.attribute("flags", convertPolygonFlags(aPolygon.GetFlags(i))); + rWriter.endElement(); + } + + rWriter.endElement(); + } + break; + + case MetaActionType::POLYGON: + { + MetaPolygonAction* pMetaPolygonAction = static_cast<MetaPolygonAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + tools::Polygon aPolygon = pMetaPolygonAction->GetPolygon(); + bool bFlags = aPolygon.HasFlags(); + for (sal_uInt16 i = 0; i < aPolygon.GetSize(); i++) + { + rWriter.startElement("point"); + writePoint(rWriter, aPolygon[i]); + if (bFlags) + rWriter.attribute("flags", convertPolygonFlags(aPolygon.GetFlags(i))); + rWriter.endElement(); + } + + rWriter.endElement(); + } + break; + + case MetaActionType::POLYPOLYGON: + { + MetaPolyPolygonAction *const pMetaPolyPolygonAction = static_cast<MetaPolyPolygonAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + tools::PolyPolygon const& rPolyPolygon(pMetaPolyPolygonAction->GetPolyPolygon()); + + for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j) + { + rWriter.startElement("polygon"); + tools::Polygon const& rPolygon = rPolyPolygon[j]; + bool bFlags = rPolygon.HasFlags(); + for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i) + { + rWriter.startElement("point"); + writePoint(rWriter, rPolygon[i]); + if (bFlags) + rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i))); + rWriter.endElement(); + } + rWriter.endElement(); + } + rWriter.endElement(); + } + break; + + case MetaActionType::TEXT: + { + auto* pMeta = static_cast<MetaTextAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetPoint()); + rWriter.attribute("index", pMeta->GetIndex()); + rWriter.attribute("length", pMeta->GetLen()); + rWriter.startElement("textcontent"); + rWriter.content(pMeta->GetText()); + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::TEXTARRAY: + { + MetaTextArrayAction* pMetaTextArrayAction = static_cast<MetaTextArrayAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + sal_Int32 aIndex = pMetaTextArrayAction->GetIndex(); + sal_Int32 aLength = pMetaTextArrayAction->GetLen(); + + writePoint(rWriter, pMetaTextArrayAction->GetPoint()); + rWriter.attribute("index", aIndex); + rWriter.attribute("length", aLength); + + if (!pMetaTextArrayAction->GetDXArray().empty()) + { + auto & rArray = pMetaTextArrayAction->GetDXArray(); + rWriter.startElement("dxarray"); + if (aIndex < o3tl::narrowing<sal_Int32>(rArray.size())) + rWriter.attribute("first", rArray[aIndex]); + if (aIndex + aLength - 1 < o3tl::narrowing<sal_Int32>(rArray.size())) + rWriter.attribute("last", rArray[aIndex + aLength - 1]); + OUStringBuffer sDxLengthString(std::max((aLength - aIndex) * 4, sal_Int32(0))); + for (sal_Int32 i = 0; i < aLength - aIndex; ++i) + { + sDxLengthString.append(OUString::number(rArray[aIndex + i]) + " "); + } + rWriter.content(sDxLengthString); + rWriter.endElement(); + } + + rWriter.startElement("text"); + + const OUString& rStr = pMetaTextArrayAction->GetText(); + // fix bad XML dump by removing forbidden 0x01 + // FIXME: expand footnote anchor point 0x01 instead of this + if ( rStr.indexOf(0x01) > -1 ) + rWriter.content(rStr.replaceAll("\001", "")); + else + rWriter.content(rStr); + + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::STRETCHTEXT: + { + auto* pMeta = static_cast<MetaStretchTextAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + writePoint(rWriter, pMeta->GetPoint()); + rWriter.attribute("index", pMeta->GetIndex()); + rWriter.attribute("length", pMeta->GetLen()); + rWriter.attribute("width", pMeta->GetWidth()); + + rWriter.startElement("textcontent"); + rWriter.content(pMeta->GetText()); + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::TEXTRECT: + { + auto* pMeta = static_cast<MetaTextRectAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writeRectangle(rWriter, pMeta->GetRect()); + rWriter.startElement("textcontent"); + rWriter.content(pMeta->GetText()); + rWriter.endElement(); + + rWriter.startElement("style"); + rWriter.content(convertDrawTextFlagsToString(pMeta->GetStyle())); + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::BMP: + { + auto pMeta = static_cast<MetaBmpAction*>(pAction); + Bitmap aBitmap = pMeta->GetBitmap(); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetPoint()); + writeBitmap(rWriter, aBitmap); + rWriter.endElement(); + } + break; + + case MetaActionType::BMPSCALE: + { + auto pMeta = static_cast<MetaBmpScaleAction*>(pAction); + Bitmap aBitmap = pMeta->GetBitmap(); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetPoint()); + writeSize(rWriter, pMeta->GetSize()); + writeBitmap(rWriter, aBitmap); + rWriter.endElement(); + } + break; + + case MetaActionType::BMPSCALEPART: + { + auto pMeta = static_cast<MetaBmpScalePartAction*>(pAction); + Bitmap aBitmap = pMeta->GetBitmap(); + rWriter.startElement(sCurrentElementTag); + rWriter.attribute("destx", pMeta->GetDestPoint().X()); + rWriter.attribute("desty", pMeta->GetDestPoint().Y()); + rWriter.attribute("destwidth", pMeta->GetDestSize().Width()); + rWriter.attribute("destheight", pMeta->GetDestSize().Height()); + rWriter.attribute("srcx", pMeta->GetSrcPoint().X()); + rWriter.attribute("srcy", pMeta->GetSrcPoint().Y()); + rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width()); + rWriter.attribute("srcheight", pMeta->GetSrcSize().Height()); + writeBitmap(rWriter, aBitmap); + rWriter.endElement(); + } + break; + + case MetaActionType::BMPEX: + { + auto pMeta = static_cast<MetaBmpExAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetPoint()); + Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap(); + rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx())); + writeBitmap(rWriter, aBitmap); + rWriter.endElement(); + } + break; + + case MetaActionType::BMPEXSCALE: + { + auto pMeta = static_cast<MetaBmpExScaleAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetPoint()); + writeSize(rWriter, pMeta->GetSize()); + Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap(); + rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx())); + writeBitmap(rWriter, aBitmap); + rWriter.endElement(); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + auto pMeta = static_cast<MetaBmpExScalePartAction*>(pAction); + Bitmap aBitmap = pMeta->GetBitmapEx().GetBitmap(); + rWriter.startElement(sCurrentElementTag); + rWriter.attribute("destx", pMeta->GetDestPoint().X()); + rWriter.attribute("desty", pMeta->GetDestPoint().Y()); + rWriter.attribute("destwidth", pMeta->GetDestSize().Width()); + rWriter.attribute("destheight", pMeta->GetDestSize().Height()); + rWriter.attribute("srcx", pMeta->GetSrcPoint().X()); + rWriter.attribute("srcy", pMeta->GetSrcPoint().Y()); + rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width()); + rWriter.attribute("srcheight", pMeta->GetSrcSize().Height()); + rWriter.attribute("transparenttype", convertBitmapExTransparentType(pMeta->GetBitmapEx())); + writeBitmap(rWriter, aBitmap); + rWriter.endElement(); + } + break; + + case MetaActionType::MASK: + { + auto pMeta = static_cast<MetaMaskAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetPoint()); + rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum())); + rWriter.attribute("color", convertColorToString(pMeta->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::MASKSCALE: + { + auto pMeta = static_cast<MetaMaskScaleAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetPoint()); + writeSize(rWriter, pMeta->GetSize()); + rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum())); + rWriter.attribute("color", convertColorToString(pMeta->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::MASKSCALEPART: + { + auto pMeta = static_cast<MetaMaskScalePartAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + rWriter.attribute("destx", pMeta->GetDestPoint().X()); + rWriter.attribute("desty", pMeta->GetDestPoint().Y()); + rWriter.attribute("destwidth", pMeta->GetDestSize().Width()); + rWriter.attribute("destheight", pMeta->GetDestSize().Height()); + rWriter.attribute("srcx", pMeta->GetSrcPoint().X()); + rWriter.attribute("srcy", pMeta->GetSrcPoint().Y()); + rWriter.attribute("srcwidth", pMeta->GetSrcSize().Width()); + rWriter.attribute("srcheight", pMeta->GetSrcSize().Height()); + rWriter.attribute("crc", hex32(pMeta->GetBitmap().GetChecksum())); + rWriter.attribute("color", convertColorToString(pMeta->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::GRADIENT: + { + const MetaGradientAction* pMeta = static_cast<MetaGradientAction*>(pAction); + + rWriter.startElement(sCurrentElementTag); + writeGradient(rWriter, pMeta->GetGradient()); + + rWriter.startElement("rectangle"); + writeRectangle(rWriter, pMeta->GetRect()); + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::HATCH: + { + auto* const pMetaHatchAction = static_cast<MetaHatchAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + tools::PolyPolygon const& rPolyPolygon(pMetaHatchAction->GetPolyPolygon()); + + for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j) + { + rWriter.startElement("polygon"); + tools::Polygon const& rPolygon = rPolyPolygon[j]; + bool bFlags = rPolygon.HasFlags(); + for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i) + { + rWriter.startElement("point"); + writePoint(rWriter, rPolygon[i]); + if (bFlags) + rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i))); + rWriter.endElement(); + } + rWriter.endElement(); + } + + rWriter.startElement("hatch"); + const auto& rHatch = pMetaHatchAction->GetHatch(); + rWriter.attribute("style", convertHatchStyle(rHatch.GetStyle())); + rWriter.attribute("color", convertColorToString(rHatch.GetColor())); + rWriter.attribute("distance", sal_Int32(rHatch.GetDistance())); + rWriter.attribute("angle", sal_Int32(rHatch.GetAngle().get())); + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::WALLPAPER: + { + const auto* pMetaAction = static_cast<const MetaWallpaperAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + writeRectangle(rWriter, pMetaAction->GetRect()); + + rWriter.startElement("wallpaper"); + const auto& rWallpaper = pMetaAction->GetWallpaper(); + + rWriter.attribute("color", convertColorToString(rWallpaper.GetColor())); + + WallpaperStyle eStyle = rWallpaper.GetStyle(); + rWriter.attribute("style", convertWallpaperStyleToString(eStyle)); + + if (rWallpaper.IsBitmap()) + { + rWriter.startElement("bitmap"); + BitmapEx const & rBitmapEx = rWallpaper.GetBitmap(); + rWriter.attribute("crc", hex32(rBitmapEx.GetChecksum())); + rWriter.attribute("transparenttype", convertBitmapExTransparentType(rBitmapEx)); + rWriter.attribute("pixelformat", convertPixelFormatToString(rBitmapEx.GetBitmap().getPixelFormat())); + rWriter.attribute("width", hex32(rBitmapEx.GetSizePixel().Width())); + rWriter.attribute("height", hex32(rBitmapEx.GetSizePixel().Height())); + rWriter.endElement(); + } + + if (rWallpaper.IsGradient()) + { + rWriter.startElement("gradient"); + Gradient aGradient = rWallpaper.GetGradient(); + writeGradient(rWriter, aGradient); + rWriter.endElement(); + } + + if (rWallpaper.IsRect()) + { + tools::Rectangle aRect = rWallpaper.GetRect(); + rWriter.startElement("rectangle"); + writeRectangle(rWriter, aRect); + rWriter.endElement(); + } + + rWriter.attribute("fixed", rWallpaper.IsFixed() ? "true" : "false"); + rWriter.attribute("scrollable", rWallpaper.IsScrollable() ? "true" : "false"); + + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::CLIPREGION: + { + const auto* pMetaClipRegionAction = static_cast<const MetaClipRegionAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + tools::Rectangle aRectangle = pMetaClipRegionAction->GetRegion().GetBoundRect(); + writeRectangle(rWriter, aRectangle); + + vcl::Region aRegion = pMetaClipRegionAction->GetRegion(); + + if (aRegion.HasPolyPolygonOrB2DPolyPolygon()) + { + tools::PolyPolygon aPolyPolygon = aRegion.GetAsPolyPolygon(); + + for (sal_uInt16 j = 0; j < aPolyPolygon.Count(); ++j) + { + rWriter.startElement("polygon"); + tools::Polygon const& rPolygon = aPolyPolygon[j]; + bool bFlags = rPolygon.HasFlags(); + for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i) + { + rWriter.startElement("point"); + writePoint(rWriter, rPolygon[i]); + if (bFlags) + rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i))); + rWriter.endElement(); + } + rWriter.endElement(); + } + } + + rWriter.endElement(); + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + MetaISectRectClipRegionAction* pMetaISectRectClipRegionAction = static_cast<MetaISectRectClipRegionAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + tools::Rectangle aRectangle = pMetaISectRectClipRegionAction->GetRect(); + writeRectangle(rWriter, aRectangle); + rWriter.endElement(); + } + break; + + case MetaActionType::ISECTREGIONCLIPREGION: + { + MetaISectRegionClipRegionAction* pMetaISectRegionClipRegionAction = static_cast<MetaISectRegionClipRegionAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + // FIXME for now we dump only the bounding box; this is + // enough for the tests we have, but may need extending to + // dumping the real polypolygon in the future + tools::Rectangle aRectangle = pMetaISectRegionClipRegionAction->GetRegion().GetBoundRect(); + writeRectangle(rWriter, aRectangle); + rWriter.endElement(); + } + break; + + case MetaActionType::MOVECLIPREGION: + { + const auto* pMetaMoveClipRegionAction = static_cast<MetaMoveClipRegionAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + rWriter.attribute("horzmove", pMetaMoveClipRegionAction->GetHorzMove()); + rWriter.attribute("vertmove", pMetaMoveClipRegionAction->GetVertMove()); + rWriter.endElement(); + } + break; + + case MetaActionType::LINECOLOR: + { + MetaLineColorAction* pMetaLineColorAction = static_cast<MetaLineColorAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + rWriter.attribute("color", convertColorToString(pMetaLineColorAction->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::FILLCOLOR: + { + MetaFillColorAction* pMetaFillColorAction = static_cast<MetaFillColorAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + rWriter.attribute("color", convertColorToString(pMetaFillColorAction->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::TEXTCOLOR: + { + MetaTextColorAction* pMetaTextColorAction = static_cast<MetaTextColorAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + rWriter.attribute("color", convertColorToString(pMetaTextColorAction->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::TEXTFILLCOLOR: + { + MetaTextFillColorAction* pMetaTextFillColorAction = static_cast<MetaTextFillColorAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + rWriter.attribute("color", convertColorToString(pMetaTextFillColorAction->GetColor())); + + if (pMetaTextFillColorAction->IsSetting()) + rWriter.attribute("setting", u"true"); + + rWriter.endElement(); + } + break; + + case MetaActionType::TEXTALIGN: + { + MetaTextAlignAction* pMetaTextAlignAction = static_cast<MetaTextAlignAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + OUString sAlign = convertTextAlignToString(pMetaTextAlignAction->GetTextAlign()); + if (!sAlign.isEmpty()) + rWriter.attribute("align", sAlign); + rWriter.endElement(); + } + break; + + case MetaActionType::MAPMODE: + { + const MetaMapModeAction* pMeta = static_cast<MetaMapModeAction*>(pAction); + MapMode aMapMode = pMeta->GetMapMode(); + rWriter.startElement(sCurrentElementTag); + rWriter.attribute("mapunit", convertMapUnitToString( aMapMode.GetMapUnit() )); + writePoint(rWriter, aMapMode.GetOrigin()); + rWriter.attribute("scalex", convertFractionToString(aMapMode.GetScaleX())); + rWriter.attribute("scaley", convertFractionToString(aMapMode.GetScaleY())); + rWriter.endElement(); + } + break; + + case MetaActionType::FONT: + { + MetaFontAction* pMetaFontAction = static_cast<MetaFontAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + vcl::Font aFont = pMetaFontAction->GetFont(); + + rWriter.attribute("color", convertColorToString(aFont.GetColor())); + rWriter.attribute("fillcolor", convertColorToString(aFont.GetFillColor())); + rWriter.attribute("name", aFont.GetFamilyName()); + rWriter.attribute("stylename", aFont.GetStyleName()); + rWriter.attribute("width", aFont.GetFontSize().Width()); + rWriter.attribute("height", aFont.GetFontSize().Height()); + rWriter.attribute("orientation", aFont.GetOrientation().get()); + rWriter.attribute("weight", convertFontWeightToString(aFont.GetWeight())); + rWriter.attribute("vertical", aFont.IsVertical() ? "true" : "false"); + + rWriter.endElement(); + } + break; + + case MetaActionType::PUSH: + { + MetaPushAction* pMetaPushAction = static_cast<MetaPushAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + rWriter.attribute("flags", collectPushFlags(pMetaPushAction->GetFlags())); + } + break; + + case MetaActionType::POP: + { + rWriter.endElement(); + } + break; + + case MetaActionType::RASTEROP: + { + MetaRasterOpAction* pMetaRasterOpAction = static_cast<MetaRasterOpAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + if (pMetaRasterOpAction->GetRasterOp() != RasterOp::OverPaint) + { + rWriter.attribute("operation", convertRopToString(pMetaRasterOpAction->GetRasterOp())); + } + rWriter.endElement(); + } + break; + + case MetaActionType::Transparent: + { + const MetaTransparentAction* pMeta = static_cast<MetaTransparentAction*>(pAction); + + rWriter.startElement(sCurrentElementTag); + rWriter.attribute("transparence", pMeta->GetTransparence()); + + tools::PolyPolygon const& rPolyPolygon(pMeta->GetPolyPolygon()); + + for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j) + { + rWriter.startElement("polygon"); + tools::Polygon const& rPolygon = rPolyPolygon[j]; + bool bFlags = rPolygon.HasFlags(); + for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i) + { + rWriter.startElement("point"); + writePoint(rWriter, rPolygon[i]); + if (bFlags) + rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i))); + rWriter.endElement(); + } + rWriter.endElement(); + } + + rWriter.endElement(); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pMetaEPSAction = static_cast<MetaEPSAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + writePoint(rWriter, pMetaEPSAction->GetPoint()); + writeSize(rWriter, pMetaEPSAction->GetSize()); + + rWriter.startElement("gfxlink"); + writeSize(rWriter, pMetaEPSAction->GetLink().GetPrefSize()); + rWriter.attribute("type", convertGfxLinkTypeToString(pMetaEPSAction->GetLink().GetType())); + rWriter.attribute("userid", pMetaEPSAction->GetLink().GetUserId()); + rWriter.attribute("datasize", pMetaEPSAction->GetLink().GetDataSize()); + rWriter.attribute("data", toHexString(pMetaEPSAction->GetLink().GetData(), pMetaEPSAction->GetLink().GetDataSize())); + rWriter.attribute("native", pMetaEPSAction->GetLink().IsNative() ? "true" : "false"); + rWriter.attribute("emf", pMetaEPSAction->GetLink().IsEMF() ? "true" : "false"); + rWriter.attribute("validmapmode", pMetaEPSAction->GetLink().IsPrefMapModeValid() ? "true" : "false"); + rWriter.startElement("prefmapmode"); + writeMapMode(rWriter, pMetaEPSAction->GetLink().GetPrefMapMode()); + rWriter.endElement(); + rWriter.endElement(); + + rWriter.startElement("metafile"); + writeXml(pMetaEPSAction->GetSubstitute(), rWriter); + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::REFPOINT: + { + auto* pMeta = static_cast<MetaRefPointAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetRefPoint()); + rWriter.attribute("set", pMeta->IsSetting() ? "true" : "false"); + rWriter.endElement(); + } + break; + + case MetaActionType::TEXTLINECOLOR: + { + auto* pMeta = static_cast<MetaTextLineColorAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + rWriter.attribute("color", convertColorToString(pMeta->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::TEXTLINE: + { + auto* pMeta = static_cast<MetaTextLineAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetStartPoint()); + rWriter.attribute("width", pMeta->GetWidth()); + rWriter.attribute("strikeout", convertFontStrikeoutToString(pMeta->GetStrikeout())); + rWriter.attribute("underline", convertFontLineStyleToString(pMeta->GetUnderline())); + rWriter.attribute("overline", convertFontLineStyleToString(pMeta->GetOverline())); + rWriter.endElement(); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + const auto* pMeta = static_cast<MetaFloatTransparentAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + writePoint(rWriter, pMeta->GetPoint()); + writeSize(rWriter, pMeta->GetSize()); + rWriter.attribute("transparent", pMeta->IsTransparent() ? "true" : "false"); + + rWriter.startElement("gradient"); + writeGradient(rWriter, pMeta->GetGradient()); + rWriter.endElement(); + + rWriter.startElement("metafile"); + writeXml(pMeta->GetGDIMetaFile(), rWriter); + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case MetaActionType::GRADIENTEX: + { + const MetaGradientExAction* pMetaGradientExAction = static_cast<MetaGradientExAction*>(pAction); + + rWriter.startElement(sCurrentElementTag); + writeGradient(rWriter, pMetaGradientExAction->GetGradient()); + + tools::PolyPolygon const& rPolyPolygon(pMetaGradientExAction->GetPolyPolygon()); + for (sal_uInt16 j = 0; j < rPolyPolygon.Count(); ++j) + { + rWriter.startElement("polygon"); + tools::Polygon const& rPolygon = rPolyPolygon[j]; + bool bFlags = rPolygon.HasFlags(); + for (sal_uInt16 i = 0; i < rPolygon.GetSize(); ++i) + { + rWriter.startElement("point"); + writePoint(rWriter, rPolygon[i]); + if (bFlags) + rWriter.attribute("flags", convertPolygonFlags(rPolygon.GetFlags(i))); + rWriter.endElement(); + } + rWriter.endElement(); + } + + rWriter.endElement(); + } + break; + + case MetaActionType::LAYOUTMODE: + { + const MetaLayoutModeAction* pMetaLayoutModeAction = static_cast<MetaLayoutModeAction*>(pAction); + + rWriter.startElement(sCurrentElementTag); + + rWriter.attribute("textlayout", convertComplexTestLayoutFlags(pMetaLayoutModeAction->GetLayoutMode())); + + rWriter.endElement(); + } + break; + + case MetaActionType::TEXTLANGUAGE: + { + const MetaTextLanguageAction* pMetaTextLanguageAction = static_cast<MetaTextLanguageAction*>(pAction); + + rWriter.startElement(sCurrentElementTag); + + rWriter.attribute("language", convertLanguageTypeToString(pMetaTextLanguageAction->GetTextLanguage())); + + rWriter.endElement(); + } + break; + + case MetaActionType::OVERLINECOLOR: + { + const auto* pMetaAction = static_cast<MetaOverlineColorAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + rWriter.attribute("color", convertColorToString(pMetaAction->GetColor())); + rWriter.endElement(); + } + break; + + case MetaActionType::COMMENT: + { + MetaCommentAction* pMetaCommentAction = static_cast<MetaCommentAction*>(pAction); + rWriter.startElement(sCurrentElementTag); + + if (pMetaCommentAction->GetDataSize() > 0) + { + rWriter.attribute("datasize", pMetaCommentAction->GetDataSize()); + rWriter.attribute("data", toHexString(pMetaCommentAction->GetData(), pMetaCommentAction->GetDataSize())); + } + rWriter.attribute("value", pMetaCommentAction->GetValue()); + + if (!pMetaCommentAction->GetComment().isEmpty()) + { + rWriter.startElement("comment"); + rWriter.content(pMetaCommentAction->GetComment()); + rWriter.endElement(); + } + rWriter.endElement(); + } + break; + + default: + { + rWriter.startElement(sCurrentElementTag); + rWriter.attribute("note", "not implemented in xml dump"_ostr); + rWriter.endElement(); + } + break; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/oldprintadaptor.cxx b/vcl/source/gdi/oldprintadaptor.cxx new file mode 100644 index 0000000000..599e1a543f --- /dev/null +++ b/vcl/source/gdi/oldprintadaptor.cxx @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/oldprintadaptor.hxx> +#include <vcl/gdimtf.hxx> + +#include <com/sun/star/awt/Size.hpp> + +#include <vector> + +using namespace vcl; +using namespace cppu; +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; + +namespace vcl +{ + namespace { + + struct AdaptorPage + { + GDIMetaFile maPage; + css::awt::Size maPageSize; + }; + + } + + struct ImplOldStyleAdaptorData + { + std::vector< AdaptorPage > maPages; + }; +} + +OldStylePrintAdaptor::OldStylePrintAdaptor(const VclPtr<Printer>& i_xPrinter, weld::Window* i_pWindow) + : PrinterController(i_xPrinter, i_pWindow) + , mpData(new ImplOldStyleAdaptorData) +{ +} + +OldStylePrintAdaptor::~OldStylePrintAdaptor() +{ +} + +void OldStylePrintAdaptor::StartPage() +{ + Size aPaperSize( getPrinter()->PixelToLogic( getPrinter()->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) ) ); + mpData->maPages.emplace_back( ); + mpData->maPages.back().maPageSize.Width = aPaperSize.getWidth(); + mpData->maPages.back().maPageSize.Height = aPaperSize.getHeight(); + getPrinter()->SetConnectMetaFile( &mpData->maPages.back().maPage ); + + // copy state into metafile + VclPtr<Printer> xPrinter( getPrinter() ); + xPrinter->SetMapMode(xPrinter->GetMapMode()); + xPrinter->SetFont(xPrinter->GetFont()); + xPrinter->SetDrawMode(xPrinter->GetDrawMode()); + xPrinter->SetLineColor(xPrinter->GetLineColor()); + xPrinter->SetFillColor(xPrinter->GetFillColor()); +} + +void OldStylePrintAdaptor::EndPage() +{ + getPrinter()->SetConnectMetaFile( nullptr ); + mpData->maPages.back().maPage.WindStart(); +} + +int OldStylePrintAdaptor::getPageCount() const +{ + return int(mpData->maPages.size()); +} + +Sequence< PropertyValue > OldStylePrintAdaptor::getPageParameters( int i_nPage ) const +{ + css::awt::Size aSize; + if( i_nPage < int(mpData->maPages.size() ) ) + aSize = mpData->maPages[i_nPage].maPageSize; + return { comphelper::makePropertyValue("PageSize", css::uno::Any(aSize)) }; +} + +void OldStylePrintAdaptor::printPage( int i_nPage ) const +{ + if( i_nPage < int(mpData->maPages.size()) ) + { + mpData->maPages[ i_nPage ].maPage.WindStart(); + mpData->maPages[ i_nPage ].maPage.Play(*getPrinter()); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/pdfbuildin_fonts.cxx b/vcl/source/gdi/pdfbuildin_fonts.cxx new file mode 100644 index 0000000000..7f80bfdd03 --- /dev/null +++ b/vcl/source/gdi/pdfbuildin_fonts.cxx @@ -0,0 +1,760 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <rtl/strbuf.hxx> + +#include <pdf/pdfbuildin_fonts.hxx> + +namespace vcl::pdf +{ +OString BuildinFont::getNameObject() const +{ + OStringBuffer aBuf(16); + aBuf.append('/'); + const char* pRun = m_pPSName; + + unsigned int nCopied = 0; + while (*pRun) + { + if (*pRun >= 'A' && *pRun <= 'Z') + nCopied = 0; + if (nCopied++ < 2) + aBuf.append(*pRun); + pRun++; + } + return aBuf.makeStringAndClear(); +} + +const FontCharMapRef& BuildinFont::GetFontCharMap() const +{ + assert(false && "pdf::BuildinFont doesn't provide correct char maps!"); + if (m_xFontCharMap.is()) + return m_xFontCharMap; + + m_xFontCharMap = FontCharMap::GetDefaultMap(m_eCharSet != RTL_TEXTENCODING_MS_1252); + return m_xFontCharMap; +} + +FontAttributes BuildinFont::GetFontAttributes() const +{ + FontAttributes aDFA; + aDFA.SetFamilyName(OUString::createFromAscii(m_pName)); + aDFA.SetStyleName(OUString::createFromAscii(m_pStyleName)); + aDFA.SetFamilyType(m_eFamily); + // dubious, see BuildinFont::GetFontCharMap + aDFA.SetMicrosoftSymbolEncoded(m_eCharSet != RTL_TEXTENCODING_MS_1252); + aDFA.SetPitch(m_ePitch); + aDFA.SetWeight(m_eWeight); + aDFA.SetItalic(m_eItalic); + aDFA.SetWidthType(m_eWidthType); + aDFA.SetQuality(50000); + return aDFA; +} + +const BuildinFont BuildinFontFace::m_aBuildinFonts[14] + = { { "Courier", // family name + "Normal", // style + "Courier", // PSName + 629, + -157, // ascend, descend + FAMILY_MODERN, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_FIXED, // pitch + WIDTH_NORMAL, // width type + WEIGHT_NORMAL, // weight type + ITALIC_NONE, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39 + 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47 + 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55 + 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63 + 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71 + 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79 + 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87 + 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95 + 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103 + 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111 + 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119 + 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127 + 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135 + 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143 + 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151 + 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159 + 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167 + 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175 + 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183 + 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191 + 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199 + 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207 + 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215 + 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223 + 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231 + 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239 + 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247 + 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255 + }, + nullptr }, + + { "Courier", // family name + "Italic", // style + "Courier-Oblique", // PSName + 629, + -157, // ascend, descend + FAMILY_MODERN, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_FIXED, // pitch + WIDTH_NORMAL, // width type + WEIGHT_NORMAL, // weight type + ITALIC_NORMAL, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39 + 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47 + 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55 + 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63 + 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71 + 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79 + 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87 + 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95 + 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103 + 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111 + 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119 + 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127 + 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135 + 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143 + 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151 + 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159 + 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167 + 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175 + 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183 + 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191 + 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199 + 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207 + 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215 + 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223 + 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231 + 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239 + 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247 + 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255 + }, + nullptr }, + + { "Courier", // family name + "Bold", // style + "Courier-Bold", // PSName + 629, + -157, // ascend, descend + FAMILY_MODERN, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_FIXED, // pitch + WIDTH_NORMAL, // width type + WEIGHT_BOLD, // weight type + ITALIC_NONE, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39 + 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47 + 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55 + 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63 + 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71 + 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79 + 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87 + 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95 + 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103 + 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111 + 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119 + 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127 + 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135 + 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143 + 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151 + 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159 + 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167 + 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175 + 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183 + 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191 + 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199 + 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207 + 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215 + 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223 + 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231 + 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239 + 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247 + 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255 + }, + nullptr }, + + { "Courier", // family name + "Bold Italic", // style + "Courier-BoldOblique", // PSName + 629, + -157, // ascend, descend + FAMILY_MODERN, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_FIXED, // pitch + WIDTH_NORMAL, // width type + WEIGHT_BOLD, // weight type + ITALIC_NORMAL, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 600, 600, 600, 600, 600, 600, 600, 600, // 32 - 39 + 600, 600, 600, 600, 600, 600, 600, 600, // 40 - 47 + 600, 600, 600, 600, 600, 600, 600, 600, // 48 - 55 + 600, 600, 600, 600, 600, 600, 600, 600, // 56 - 63 + 600, 600, 600, 600, 600, 600, 600, 600, // 64 - 71 + 600, 600, 600, 600, 600, 600, 600, 600, // 72 - 79 + 600, 600, 600, 600, 600, 600, 600, 600, // 80 - 87 + 600, 600, 600, 600, 600, 600, 600, 600, // 88 - 95 + 600, 600, 600, 600, 600, 600, 600, 600, // 96 - 103 + 600, 600, 600, 600, 600, 600, 600, 600, // 104 - 111 + 600, 600, 600, 600, 600, 600, 600, 600, // 112 - 119 + 600, 600, 600, 600, 600, 600, 600, 0, // 120 - 127 + 600, 0, 600, 600, 600, 600, 600, 600, // 128 - 135 + 600, 600, 600, 600, 600, 0, 600, 0, // 136 - 143 + 0, 600, 600, 600, 600, 600, 600, 600, // 144 - 151 + 600, 600, 600, 600, 600, 0, 600, 600, // 152 - 159 + 600, 600, 600, 600, 600, 600, 600, 600, // 160 - 167 + 600, 600, 600, 600, 600, 600, 600, 600, // 168 - 175 + 600, 600, 600, 600, 600, 600, 600, 600, // 176 - 183 + 600, 600, 600, 600, 600, 600, 600, 600, // 184 - 191 + 600, 600, 600, 600, 600, 600, 600, 600, // 192 - 199 + 600, 600, 600, 600, 600, 600, 600, 600, // 200 - 207 + 600, 600, 600, 600, 600, 600, 600, 600, // 208 - 215 + 600, 600, 600, 600, 600, 600, 600, 600, // 216 - 223 + 600, 600, 600, 600, 600, 600, 600, 600, // 224 - 231 + 600, 600, 600, 600, 600, 600, 600, 600, // 232 - 239 + 600, 600, 600, 600, 600, 600, 600, 600, // 240 - 247 + 600, 600, 600, 600, 600, 600, 600, 600 // 248 - 255 + }, + nullptr }, + + { "Helvetica", // family name + "Normal", // style + "Helvetica", // PSName + 718, + -207, // ascend, descend + FAMILY_SWISS, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_NORMAL, // weight type + ITALIC_NONE, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 278, 278, 355, 556, 556, 889, 667, 191, // 32 - 39 + 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47 + 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55 + 556, 556, 278, 278, 584, 584, 584, 556, // 56 - 63 + 1015, 667, 667, 722, 722, 667, 611, 778, // 64 - 71 + 722, 278, 500, 667, 556, 833, 722, 778, // 72 - 79 + 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87 + 667, 667, 611, 278, 278, 278, 469, 556, // 88 - 95 + 333, 556, 556, 500, 556, 556, 278, 556, // 96 - 103 + 556, 222, 222, 500, 222, 833, 556, 556, // 104 - 111 + 556, 556, 333, 500, 278, 556, 500, 722, // 112 - 119 + 500, 500, 500, 334, 260, 334, 584, 0, // 120 - 127 + 556, 0, 222, 556, 333, 1000, 556, 556, // 128 - 135 + 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143 + 0, 222, 222, 333, 333, 350, 556, 1000, // 144 - 151 + 333, 1000, 500, 333, 944, 0, 500, 667, // 152 - 159 + 278, 333, 556, 556, 556, 556, 260, 556, // 160 - 167 + 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175 + 400, 584, 333, 333, 333, 556, 537, 278, // 176 - 183 + 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191 + 667, 667, 667, 667, 667, 667, 1000, 722, // 192 - 199 + 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207 + 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215 + 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223 + 556, 556, 556, 556, 556, 556, 889, 500, // 224 - 231 + 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239 + 556, 556, 556, 556, 556, 556, 556, 584, // 240 - 247 + 611, 556, 556, 556, 556, 500, 556, 500 // 248 - 255 + }, + nullptr }, + + { "Helvetica", // family name + "Italic", // style + "Helvetica-Oblique", // PSName + 718, + -207, // ascend, descend + FAMILY_SWISS, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_NORMAL, // weight type + ITALIC_NORMAL, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 278, 278, 355, 556, 556, 889, 667, 191, // 32 - 39 + 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47 + 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55 + 556, 556, 278, 278, 584, 584, 584, 556, // 56 - 63 + 1015, 667, 667, 722, 722, 667, 611, 778, // 64 - 71 + 722, 278, 500, 667, 556, 833, 722, 778, // 72 - 79 + 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87 + 667, 667, 611, 278, 278, 278, 469, 556, // 88 - 95 + 333, 556, 556, 500, 556, 556, 278, 556, // 96 - 103 + 556, 222, 222, 500, 222, 833, 556, 556, // 104 - 111 + 556, 556, 333, 500, 278, 556, 500, 722, // 112 - 119 + 500, 500, 500, 334, 260, 334, 584, 0, // 120 - 127 + 556, 0, 222, 556, 333, 1000, 556, 556, // 128 - 135 + 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143 + 0, 222, 222, 333, 333, 350, 556, 1000, // 144 - 151 + 333, 1000, 500, 333, 944, 0, 500, 667, // 152 - 159 + 278, 333, 556, 556, 556, 556, 260, 556, // 160 - 167 + 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175 + 400, 584, 333, 333, 333, 556, 537, 278, // 176 - 183 + 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191 + 667, 667, 667, 667, 667, 667, 1000, 722, // 192 - 199 + 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207 + 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215 + 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223 + 556, 556, 556, 556, 556, 556, 889, 500, // 224 - 231 + 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239 + 556, 556, 556, 556, 556, 556, 556, 584, // 240 - 247 + 611, 556, 556, 556, 556, 500, 556, 500 // 248 - 255 + }, + nullptr }, + + { "Helvetica", // family name + "Bold", // style + "Helvetica-Bold", // PSName + 718, + -207, // ascend, descend + FAMILY_SWISS, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_BOLD, // weight type + ITALIC_NONE, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 278, 333, 474, 556, 556, 889, 722, 238, // 32 - 39 + 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47 + 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55 + 556, 556, 333, 333, 584, 584, 584, 611, // 56 - 63 + 975, 722, 722, 722, 722, 667, 611, 778, // 64 - 71 + 722, 278, 556, 722, 611, 833, 722, 778, // 72 - 79 + 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87 + 667, 667, 611, 333, 278, 333, 584, 556, // 88 - 95 + 333, 556, 611, 556, 611, 556, 333, 611, // 96 - 103 + 611, 278, 278, 556, 278, 889, 611, 611, // 104 - 111 + 611, 611, 389, 556, 333, 611, 556, 778, // 112 - 119 + 556, 556, 500, 389, 280, 389, 584, 0, // 120 - 127 + 556, 0, 278, 556, 500, 1000, 556, 556, // 128 - 135 + 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143 + 0, 278, 278, 500, 500, 350, 556, 1000, // 144 - 151 + 333, 1000, 556, 333, 944, 0, 500, 667, // 152 - 159 + 278, 333, 556, 556, 556, 556, 280, 556, // 160 - 167 + 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175 + 400, 584, 333, 333, 333, 611, 556, 278, // 176 - 183 + 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191 + 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199 + 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207 + 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215 + 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223 + 556, 556, 556, 556, 556, 556, 889, 556, // 224 - 231 + 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239 + 611, 611, 611, 611, 611, 611, 611, 584, // 240 - 247 + 611, 611, 611, 611, 611, 556, 611, 556 // 248 - 255 + }, + nullptr }, + + { "Helvetica", // family name + "Bold Italic", // style + "Helvetica-BoldOblique", // PSName + 718, + -207, // ascend, descend + FAMILY_SWISS, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_BOLD, // weight type + ITALIC_NORMAL, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 278, 333, 474, 556, 556, 889, 722, 238, // 32 - 39 + 333, 333, 389, 584, 278, 333, 278, 278, // 40 - 47 + 556, 556, 556, 556, 556, 556, 556, 556, // 48 - 55 + 556, 556, 333, 333, 584, 584, 584, 611, // 56 - 63 + 975, 722, 722, 722, 722, 667, 611, 778, // 64 - 71 + 722, 278, 556, 722, 611, 833, 722, 778, // 72 - 79 + 667, 778, 722, 667, 611, 722, 667, 944, // 80 - 87 + 667, 667, 611, 333, 278, 333, 584, 556, // 88 - 95 + 333, 556, 611, 556, 611, 556, 333, 611, // 96 - 103 + 611, 278, 278, 556, 278, 889, 611, 611, // 104 - 111 + 611, 611, 389, 556, 333, 611, 556, 778, // 112 - 119 + 556, 556, 500, 389, 280, 389, 584, 0, // 120 - 127 + 556, 0, 278, 556, 500, 1000, 556, 556, // 128 - 135 + 333, 1000, 667, 333, 1000, 0, 500, 0, // 136 - 143 + 0, 278, 278, 500, 500, 350, 556, 1000, // 144 - 151 + 333, 1000, 556, 333, 944, 0, 500, 667, // 152 - 159 + 278, 333, 556, 556, 556, 556, 280, 556, // 160 - 167 + 333, 737, 370, 556, 584, 333, 737, 333, // 168 - 175 + 400, 584, 333, 333, 333, 611, 556, 278, // 176 - 183 + 333, 333, 365, 556, 834, 834, 834, 611, // 184 - 191 + 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199 + 667, 667, 667, 667, 278, 278, 278, 278, // 200 - 207 + 722, 722, 778, 778, 778, 778, 778, 584, // 208 - 215 + 778, 722, 722, 722, 722, 667, 667, 611, // 216 - 223 + 556, 556, 556, 556, 556, 556, 889, 556, // 224 - 231 + 556, 556, 556, 556, 278, 278, 278, 278, // 232 - 239 + 611, 611, 611, 611, 611, 611, 611, 584, // 240 - 247 + 611, 611, 611, 611, 611, 556, 611, 556 // 248 - 255 + }, + nullptr }, + + { "Times", // family name + "Normal", // style + "Times-Roman", // PSName + 683, + -217, // ascend, descend + FAMILY_ROMAN, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_NORMAL, // weight type + ITALIC_NONE, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 250, 333, 408, 500, 500, 833, 778, 180, // 32 - 39 + 333, 333, 500, 564, 250, 333, 250, 278, // 40 - 47 + 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55 + 500, 500, 278, 278, 564, 564, 564, 444, // 56 - 63 + 921, 722, 667, 667, 722, 611, 556, 722, // 64 - 71 + 722, 333, 389, 722, 611, 889, 722, 722, // 72 - 79 + 556, 722, 667, 556, 611, 722, 722, 944, // 80 - 87 + 722, 722, 611, 333, 278, 333, 469, 500, // 88 - 95 + 333, 444, 500, 444, 500, 444, 333, 500, // 96 - 103 + 500, 278, 278, 500, 278, 778, 500, 500, // 104 - 111 + 500, 500, 333, 389, 278, 500, 500, 722, // 112 - 119 + 500, 500, 444, 480, 200, 480, 541, 0, // 120 - 127 + 500, 0, 333, 500, 444, 1000, 500, 500, // 128 - 135 + 333, 1000, 556, 333, 889, 0, 444, 0, // 136 - 143 + 0, 333, 333, 444, 444, 350, 500, 1000, // 144 - 151 + 333, 980, 389, 333, 722, 0, 444, 722, // 152 - 159 + 250, 333, 500, 500, 500, 500, 200, 500, // 160 - 167 + 333, 760, 276, 500, 564, 333, 760, 333, // 168 - 175 + 400, 564, 300, 300, 333, 500, 453, 250, // 176 - 183 + 333, 300, 310, 500, 750, 750, 750, 444, // 184 - 191 + 722, 722, 722, 722, 722, 722, 889, 667, // 192 - 199 + 611, 611, 611, 611, 333, 333, 333, 333, // 200 - 207 + 722, 722, 722, 722, 722, 722, 722, 564, // 208 - 215 + 722, 722, 722, 722, 722, 722, 556, 500, // 216 - 223 + 444, 444, 444, 444, 444, 444, 667, 444, // 224 - 231 + 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239 + 500, 500, 500, 500, 500, 500, 500, 564, // 240 - 247 + 500, 500, 500, 500, 500, 500, 500, 500 // 248 - 255 + }, + nullptr }, + + { "Times", // family name + "Italic", // style + "Times-Italic", // PSName + 683, + -217, // ascend, descend + FAMILY_ROMAN, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_NORMAL, // weight type + ITALIC_NORMAL, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 250, 333, 420, 500, 500, 833, 778, 214, // 32 - 39 + 333, 333, 500, 675, 250, 333, 250, 278, // 40 - 47 + 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55 + 500, 500, 333, 333, 675, 675, 675, 500, // 56 - 63 + 920, 611, 611, 667, 722, 611, 611, 722, // 64 - 71 + 722, 333, 444, 667, 556, 833, 667, 722, // 72 - 79 + 611, 722, 611, 500, 556, 722, 611, 833, // 80 - 87 + 611, 556, 556, 389, 278, 389, 422, 500, // 88 - 95 + 333, 500, 500, 444, 500, 444, 278, 500, // 96 - 103 + 500, 278, 278, 444, 278, 722, 500, 500, // 104 - 111 + 500, 500, 389, 389, 278, 500, 444, 667, // 112 - 119 + 444, 444, 389, 400, 275, 400, 541, 0, // 120 - 127 + 500, 0, 333, 500, 556, 889, 500, 500, // 128 - 135 + 333, 1000, 500, 333, 944, 0, 389, 0, // 136 - 143 + 0, 333, 333, 556, 556, 350, 500, 889, // 144 - 151 + 333, 980, 389, 333, 667, 0, 389, 556, // 152 - 159 + 250, 389, 500, 500, 500, 500, 275, 500, // 160 - 167 + 333, 760, 276, 500, 675, 333, 760, 333, // 168 - 175 + 400, 675, 300, 300, 333, 500, 523, 250, // 176 - 183 + 333, 300, 310, 500, 750, 750, 750, 500, // 184 - 191 + 611, 611, 611, 611, 611, 611, 889, 667, // 192 - 199 + 611, 611, 611, 611, 333, 333, 333, 333, // 200 - 207 + 722, 667, 722, 722, 722, 722, 722, 675, // 208 - 215 + 722, 722, 722, 722, 722, 556, 611, 500, // 216 - 223 + 500, 500, 500, 500, 500, 500, 667, 444, // 224 - 231 + 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239 + 500, 500, 500, 500, 500, 500, 500, 675, // 240 - 247 + 500, 500, 500, 500, 500, 444, 500, 444 // 248 - 255 + }, + nullptr }, + + { "Times", // family name + "Bold", // style + "Times-Bold", // PSName + 683, + -217, // ascend, descend + FAMILY_ROMAN, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_BOLD, // weight type + ITALIC_NONE, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 250, 333, 555, 500, 500, 1000, 833, 278, // 32 - 39 + 333, 333, 500, 570, 250, 333, 250, 278, // 40 - 47 + 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55 + 500, 500, 333, 333, 570, 570, 570, 500, // 56 - 63 + 930, 722, 667, 722, 722, 667, 611, 778, // 64 - 71 + 778, 389, 500, 778, 667, 944, 722, 778, // 72 - 79 + 611, 778, 722, 556, 667, 722, 722, 1000, // 80 - 87 + 722, 722, 667, 333, 278, 333, 581, 500, // 88 - 95 + 333, 500, 556, 444, 556, 444, 333, 500, // 96 - 103 + 556, 278, 333, 556, 278, 833, 556, 500, // 104 - 111 + 556, 556, 444, 389, 333, 556, 500, 722, // 112 - 119 + 500, 500, 444, 394, 220, 394, 520, 0, // 120 - 127 + 500, 0, 333, 500, 500, 1000, 500, 500, // 128 - 135 + 333, 1000, 556, 333, 1000, 0, 444, 0, // 136 - 143 + 0, 333, 333, 500, 500, 350, 500, 1000, // 144 - 151 + 333, 1000, 389, 333, 722, 0, 444, 722, // 152 - 159 + 250, 333, 500, 500, 500, 500, 220, 500, // 160 - 167 + 333, 747, 300, 500, 570, 333, 747, 333, // 168 - 175 + 400, 570, 300, 300, 333, 556, 540, 250, // 176 - 183 + 333, 300, 330, 500, 750, 750, 750, 500, // 184 - 191 + 722, 722, 722, 722, 722, 722, 1000, 722, // 192 - 199 + 667, 667, 667, 667, 389, 389, 389, 389, // 200 - 207 + 722, 722, 778, 778, 778, 778, 778, 570, // 208 - 215 + 778, 722, 722, 722, 722, 722, 611, 556, // 216 - 223 + 500, 500, 500, 500, 500, 500, 722, 444, // 224 - 231 + 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239 + 500, 556, 500, 500, 500, 500, 500, 570, // 240 - 247 + 500, 556, 556, 556, 556, 500, 556, 500 // 248 - 255 + }, + nullptr }, + + { "Times", // family name + "Bold Italic", // style + "Times-BoldItalic", // PSName + 683, + -217, // ascend, descend + FAMILY_ROMAN, // family style + RTL_TEXTENCODING_MS_1252, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_BOLD, // weight type + ITALIC_NORMAL, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 250, 389, 555, 500, 500, 833, 778, 278, // 32 - 39 + 333, 333, 500, 570, 250, 333, 250, 278, // 40 - 47 + 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55 + 500, 500, 333, 333, 570, 570, 570, 500, // 56 - 63 + 832, 667, 667, 667, 722, 667, 667, 722, // 64 - 71 + 778, 389, 500, 667, 611, 889, 722, 722, // 72 - 79 + 611, 722, 667, 556, 611, 722, 667, 889, // 80 - 87 + 667, 611, 611, 333, 278, 333, 570, 500, // 88 - 95 + 333, 500, 500, 444, 500, 444, 333, 500, // 96 - 103 + 556, 278, 278, 500, 278, 778, 556, 500, // 104 - 111 + 500, 500, 389, 389, 278, 556, 444, 667, // 112 - 119 + 500, 444, 389, 348, 220, 348, 570, 0, // 120 - 127 + 500, 0, 333, 500, 500, 1000, 500, 500, // 128 - 135 + 333, 1000, 556, 333, 944, 0, 389, 0, // 136 - 143 + 0, 333, 333, 500, 500, 350, 500, 1000, // 144 - 151 + 333, 1000, 389, 333, 722, 0, 389, 611, // 152 - 159 + 250, 389, 500, 500, 500, 500, 220, 500, // 160 - 167 + 333, 747, 266, 500, 606, 333, 747, 333, // 168 - 175 + 400, 570, 300, 300, 333, 576, 500, 250, // 176 - 183 + 333, 300, 300, 500, 750, 750, 750, 500, // 184 - 191 + 667, 667, 667, 667, 667, 667, 944, 667, // 192 - 199 + 667, 667, 667, 667, 389, 389, 389, 389, // 200 - 207 + 722, 722, 722, 722, 722, 722, 722, 570, // 208 - 215 + 722, 722, 722, 722, 722, 611, 611, 500, // 216 - 223 + 500, 500, 500, 500, 500, 500, 722, 444, // 224 - 231 + 444, 444, 444, 444, 278, 278, 278, 278, // 232 - 239 + 500, 556, 500, 500, 500, 500, 500, 570, // 240 - 247 + 500, 556, 556, 556, 556, 444, 500, 444 // 248 - 255 + }, + nullptr }, + + // The font name "Symbol" is too generic and causes plenty of trouble. + // To ensure WYSIWIG the PDF-Base14 variant gets a not-confusable name + { "PDF_Base14_Symbol", // family name + "Normal", // style + "Symbol", // PSName + 1010, + -293, // ascend, descend + FAMILY_DONTKNOW, // family style + RTL_TEXTENCODING_ADOBE_SYMBOL, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_NORMAL, // weight type + ITALIC_NONE, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 250, 333, 713, 500, 549, 833, 778, 439, // 32 - 39 + 333, 333, 500, 549, 250, 549, 250, 278, // 40 - 47 + 500, 500, 500, 500, 500, 500, 500, 500, // 48 - 55 + 500, 500, 278, 278, 549, 549, 549, 444, // 56 - 63 + 549, 722, 667, 722, 612, 611, 763, 603, // 64 - 71 + 722, 333, 631, 722, 686, 889, 722, 722, // 72 - 79 + 768, 741, 556, 592, 611, 690, 439, 768, // 80 - 87 + 645, 795, 611, 333, 863, 333, 658, 500, // 88 - 95 + 500, 631, 549, 549, 494, 439, 521, 411, // 96 - 103 + 603, 329, 603, 549, 549, 576, 521, 549, // 104 - 111 + 549, 521, 549, 603, 439, 576, 713, 686, // 112 - 119 + 493, 686, 494, 480, 200, 480, 549, 0, // 120 - 127 + 0, 0, 0, 0, 0, 0, 0, 0, // 128 - 135 + 0, 0, 0, 0, 0, 0, 0, 0, // 136 - 143 + 0, 0, 0, 0, 0, 0, 0, 0, // 144 - 151 + 0, 0, 0, 0, 0, 0, 0, 0, // 152 - 159 + 750, 620, 247, 549, 167, 713, 500, 753, // 160 - 167 + 753, 753, 753, 1042, 987, 603, 987, 603, // 168 - 175 + 400, 549, 411, 549, 549, 713, 494, 460, // 176 - 183 + 549, 549, 549, 549, 1000, 603, 1000, 658, // 184 - 191 + 823, 686, 795, 987, 768, 768, 823, 768, // 192 - 199 + 768, 713, 713, 713, 713, 713, 713, 713, // 200 - 207 + 768, 713, 790, 790, 890, 823, 549, 250, // 208 - 215 + 713, 603, 603, 1042, 987, 603, 987, 603, // 216 - 223 + 494, 329, 790, 790, 786, 713, 384, 384, // 224 - 231 + 384, 384, 384, 384, 494, 494, 494, 494, // 232 - 239 + 0, 329, 274, 686, 686, 686, 384, 384, // 240 - 247 + 384, 384, 384, 384, 494, 494, 494, 0 // 248 - 255 + }, + nullptr }, + + { "ZapfDingbats", // family name + "Normal", // style + "ZapfDingbats", // PSName + 820, + -143, // ascend, descend + FAMILY_DONTKNOW, // family style + RTL_TEXTENCODING_ADOBE_DINGBATS, // charset + PITCH_VARIABLE, // pitch + WIDTH_NORMAL, // width type + WEIGHT_NORMAL, // weight type + ITALIC_NONE, // italic type + { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, // 24 - 31 + 278, 974, 961, 974, 980, 719, 789, 790, // 32 - 39 + 791, 690, 960, 939, 549, 855, 911, 933, // 40 - 47 + 911, 945, 974, 755, 846, 762, 761, 571, // 48 - 55 + 677, 763, 760, 759, 754, 494, 552, 537, // 56 - 63 + 577, 692, 786, 788, 788, 790, 793, 794, // 64 - 71 + 816, 823, 789, 841, 823, 833, 816, 831, // 72 - 79 + 923, 744, 723, 749, 790, 792, 695, 776, // 80 - 87 + 768, 792, 759, 707, 708, 682, 701, 826, // 88 - 95 + 815, 789, 789, 707, 687, 696, 689, 786, // 96 - 103 + 787, 713, 791, 785, 791, 873, 761, 762, // 104 - 111 + 762, 759, 759, 892, 892, 788, 784, 438, // 112 - 119 + 138, 277, 415, 392, 392, 668, 668, 0, // 120 - 127 + 390, 390, 317, 317, 276, 276, 509, 509, // 128 - 135 + 410, 410, 234, 234, 334, 334, 0, 0, // 136 - 143 + 0, 0, 0, 0, 0, 0, 0, 0, // 144 - 151 + 0, 0, 0, 0, 0, 0, 0, 0, // 152 - 159 + 0, 732, 544, 544, 910, 667, 760, 760, // 160 - 167 + 776, 595, 694, 626, 788, 788, 788, 788, // 168 - 175 + 788, 788, 788, 788, 788, 788, 788, 788, // 176 - 183 + 788, 788, 788, 788, 788, 788, 788, 788, // 184 - 191 + 788, 788, 788, 788, 788, 788, 788, 788, // 192 - 199 + 788, 788, 788, 788, 788, 788, 788, 788, // 200 - 207 + 788, 788, 788, 788, 894, 838, 1016, 458, // 208 - 215 + 748, 924, 748, 918, 927, 928, 928, 834, // 216 - 223 + 873, 828, 924, 924, 917, 930, 931, 463, // 224 - 231 + 883, 836, 836, 867, 867, 696, 696, 874, // 232 - 239 + 0, 874, 760, 946, 771, 865, 771, 888, // 240 - 247 + 967, 888, 831, 873, 927, 970, 918, 0 // 248 - 255 + }, + nullptr } + + }; + +BuildinFontInstance::BuildinFontInstance(const vcl::font::PhysicalFontFace& rFontFace, + const vcl::font::FontSelectPattern& rFSP) + : LogicalFontInstance(rFontFace, rFSP) +{ +} + +bool BuildinFontInstance::GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const +{ + return false; +} + +BuildinFontFace::BuildinFontFace(int nId) + : vcl::font::PhysicalFontFace(m_aBuildinFonts[nId].GetFontAttributes()) + , mrBuildin(m_aBuildinFonts[nId]) +{ +} + +rtl::Reference<LogicalFontInstance> +BuildinFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSP) const +{ + return new BuildinFontInstance(*this, rFSP); +} + +} // namespace vcl::pdf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/pdfextoutdevdata.cxx b/vcl/source/gdi/pdfextoutdevdata.cxx new file mode 100644 index 0000000000..c2c838d5db --- /dev/null +++ b/vcl/source/gdi/pdfextoutdevdata.cxx @@ -0,0 +1,919 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/canvastools.hxx> +#include <vcl/pdfextoutdevdata.hxx> +#include <vcl/graph.hxx> +#include <vcl/outdev.hxx> +#include <vcl/gfxlink.hxx> +#include <vcl/metaact.hxx> +#include <vcl/graphicfilter.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <tools/stream.hxx> + +#include <memory> +#include <map> +#include <variant> + +namespace vcl +{ +namespace { + +struct CreateNamedDest { + OUString maDestName; + MapMode maParaMapMode; + PDFWriter::DestAreaType mnParaDestAreaType; + tools::Rectangle maParaRect; + sal_Int32 mnPage; +}; +struct CreateDest { + MapMode maParaMapMode; + PDFWriter::DestAreaType mnParaDestAreaType; + tools::Rectangle maParaRect; + sal_Int32 mnPage; +}; +struct CreateControlLink { sal_Int32 mnControlId; }; +struct CreateLink { + OUString maAltText; + MapMode maParaMapMode; + tools::Rectangle maParaRect; + sal_Int32 mnPage; +}; +struct CreateScreen { + OUString maAltText; + OUString maMimeType; + MapMode maParaMapMode; + tools::Rectangle maParaRect; + sal_Int32 mnPage; +}; +struct SetLinkDest { + sal_Int32 mnLinkId; + sal_Int32 mnDestId; +}; +struct SetLinkURL { + OUString maLinkURL; + sal_Int32 mnLinkId; +}; +struct SetScreenURL { + OUString maScreenURL; + sal_Int32 mnScreenId; +}; +struct SetScreenStream { + OUString maScreenStream; + sal_Int32 mnScreenId; +}; +struct RegisterDest { sal_Int32 mnDestId; }; +struct CreateOutlineItem { + OUString maText; + sal_Int32 mnParent; + sal_Int32 mnDestID; +}; +struct CreateNote { + MapMode maParaMapMode; + PDFNote maParaPDFNote; + tools::Rectangle maParaRect; + sal_Int32 mnPage; +}; +struct SetPageTransition { + PDFWriter::PageTransition maParaPageTransition; + sal_uInt32 mnMilliSec; + sal_Int32 mnPage; +}; +struct EnsureStructureElement { sal_Int32 mnId; }; +struct InitStructureElement { + PDFWriter::StructElement mParaStructElement; + OUString maAlias; + sal_Int32 mnId; +}; +struct BeginStructureElement { sal_Int32 mnId; }; +struct EndStructureElement{}; +struct SetCurrentStructureElement { sal_Int32 mnStructId; }; +struct SetStructureAttribute { + PDFWriter::StructAttribute mParaStructAttribute; + PDFWriter::StructAttributeValue mParaStructAttributeValue; +}; +struct SetStructureAttributeNumerical { PDFWriter::StructAttribute mParaStructAttribute; sal_Int32 mnId; }; +struct SetStructureBoundingBox { tools::Rectangle mRect; }; +struct SetStructureAnnotIds { + ::std::vector<sal_Int32> annotIds; +}; +struct SetActualText { OUString maText; }; +struct SetAlternateText { OUString maText; }; +struct CreateControl { + std::shared_ptr< PDFWriter::AnyWidget > mxControl; +}; +struct BeginGroup {}; +struct EndGroupGfxLink { + Graphic maGraphic; + tools::Rectangle maOutputRect, maVisibleOutputRect; + sal_Int32 mnTransparency; +}; + +typedef std::variant<CreateNamedDest, + CreateDest, + CreateControlLink, + CreateLink, + CreateScreen, + SetLinkDest, + SetLinkURL, + SetScreenURL, + SetScreenStream, + RegisterDest, + CreateOutlineItem, + CreateNote, + SetPageTransition> GlobalActionData; + +typedef std::variant<EnsureStructureElement, + InitStructureElement, + BeginStructureElement, + EndStructureElement, + SetCurrentStructureElement, + SetStructureAttribute, + SetStructureAttributeNumerical, + SetStructureBoundingBox, + SetStructureAnnotIds, + SetActualText, + SetAlternateText, + CreateControl, + BeginGroup, + EndGroupGfxLink> PageActionData; + +struct PDFExtOutDevDataSyncPage +{ + sal_uInt32 nIdx; + PageActionData eAct; +}; + +struct PDFLinkDestination +{ + tools::Rectangle mRect; + MapMode mMapMode; + sal_Int32 mPageNr; + PDFWriter::DestAreaType mAreaType; +}; +} + +struct GlobalSyncData +{ + std::deque< GlobalActionData > mActions; + ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations; + + sal_Int32 GetMappedId(sal_Int32 nLinkId); + + /** the way this appears to work: (only) everything that increments mCurId + at recording time must put an item into mParaIds at playback time, + so that the mCurId becomes the eventual index into mParaIds. + */ + sal_Int32 mCurId; + std::vector< sal_Int32 > mParaIds; + std::map<void const*, sal_Int32> mSEMap; + + sal_Int32 mCurrentStructElement; + std::vector< sal_Int32 > mStructParents; + GlobalSyncData() : + mCurId ( 0 ), + mCurrentStructElement( 0 ) + { + mStructParents.push_back(0); // because PDFWriterImpl has a dummy root + } + void PlayGlobalActions( PDFWriter& rWriter ); +}; + +sal_Int32 GlobalSyncData::GetMappedId(sal_Int32 nLinkId) +{ + /* negative values are intentionally passed as invalid IDs + * e.g. to create a new top level outline item + */ + if( nLinkId >= 0 ) + { + if ( o3tl::make_unsigned(nLinkId) < mParaIds.size() ) + nLinkId = mParaIds[ nLinkId ]; + else + nLinkId = -1; + + SAL_WARN_IF( nLinkId < 0, "vcl", "unmapped id in GlobalSyncData" ); + } + + return nLinkId; +} + +void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter ) +{ + for (auto const& action : mActions) + { + if (std::holds_alternative<CreateNamedDest>(action)) { //i56629 + const vcl::CreateNamedDest& rCreateNamedDest = std::get<CreateNamedDest>(action); + rWriter.Push( PushFlags::MAPMODE ); + rWriter.SetMapMode( rCreateNamedDest.maParaMapMode ); + mParaIds.push_back( rWriter.CreateNamedDest( rCreateNamedDest.maDestName, rCreateNamedDest.maParaRect, rCreateNamedDest.mnPage, rCreateNamedDest.mnParaDestAreaType ) ); + rWriter.Pop(); + } + else if (std::holds_alternative<CreateDest>(action)) { + const vcl::CreateDest& rCreateDest = std::get<CreateDest>(action); + rWriter.Push( PushFlags::MAPMODE ); + rWriter.SetMapMode( rCreateDest.maParaMapMode ); + mParaIds.push_back( rWriter.CreateDest( rCreateDest.maParaRect, rCreateDest.mnPage, rCreateDest.mnParaDestAreaType ) ); + rWriter.Pop(); + } + else if (std::holds_alternative<CreateControlLink>(action)) { + const vcl::CreateControlLink& rCreateControlLink = std::get<CreateControlLink>(action); + // tdf#157397: this must be called *in order* with CreateLink etc. + rWriter.SetLinkPropertyID(rCreateControlLink.mnControlId, sal_Int32(mParaIds.size())); + mParaIds.push_back(rCreateControlLink.mnControlId); + } + else if (std::holds_alternative<CreateLink>(action)) { + const vcl::CreateLink& rCreateLink = std::get<CreateLink>(action); + rWriter.Push( PushFlags::MAPMODE ); + rWriter.SetMapMode( rCreateLink.maParaMapMode ); + mParaIds.push_back( rWriter.CreateLink(rCreateLink.maParaRect, rCreateLink.mnPage, rCreateLink.maAltText) ); + // resolve LinkAnnotation structural attribute + rWriter.SetLinkPropertyID( mParaIds.back(), sal_Int32(mParaIds.size()-1) ); + rWriter.Pop(); + } + else if (std::holds_alternative<CreateScreen>(action)) { + const vcl::CreateScreen& rCreateScreen = std::get<CreateScreen>(action); + rWriter.Push(PushFlags::MAPMODE); + rWriter.SetMapMode(rCreateScreen.maParaMapMode); + mParaIds.push_back(rWriter.CreateScreen(rCreateScreen.maParaRect, rCreateScreen.mnPage, rCreateScreen.maAltText, rCreateScreen.maMimeType)); + // resolve AnnotIds structural attribute + rWriter.SetLinkPropertyID(mParaIds.back(), sal_Int32(mParaIds.size()-1)); + rWriter.Pop(); + } + else if (std::holds_alternative<SetLinkDest>(action)) { + const vcl::SetLinkDest& rSetLinkDest = std::get<SetLinkDest>(action); + sal_Int32 nLinkId = GetMappedId(rSetLinkDest.mnLinkId); + sal_Int32 nDestId = GetMappedId(rSetLinkDest.mnDestId); + rWriter.SetLinkDest( nLinkId, nDestId ); + } + else if (std::holds_alternative<SetLinkURL>(action)) { + const vcl::SetLinkURL& rSetLinkURL = std::get<SetLinkURL>(action); + sal_Int32 nLinkId = GetMappedId(rSetLinkURL.mnLinkId); + rWriter.SetLinkURL( nLinkId, rSetLinkURL.maLinkURL ); + } + else if (std::holds_alternative<SetScreenURL>(action)) { + const vcl::SetScreenURL& rSetScreenURL = std::get<SetScreenURL>(action); + sal_Int32 nScreenId = GetMappedId(rSetScreenURL.mnScreenId); + rWriter.SetScreenURL(nScreenId, rSetScreenURL.maScreenURL); + } + else if (std::holds_alternative<SetScreenStream>(action)) { + const vcl::SetScreenStream& rSetScreenStream = std::get<SetScreenStream>(action); + sal_Int32 nScreenId = GetMappedId(rSetScreenStream.mnScreenId); + rWriter.SetScreenStream(nScreenId, rSetScreenStream.maScreenStream); + } + else if (std::holds_alternative<RegisterDest>(action)) { + const vcl::RegisterDest& rRegisterDest = std::get<RegisterDest>(action); + const sal_Int32 nDestId = rRegisterDest.mnDestId; + OSL_ENSURE( mFutureDestinations.find( nDestId ) != mFutureDestinations.end(), + "GlobalSyncData::PlayGlobalActions: DescribeRegisteredRequest has not been called for that destination!" ); + + PDFLinkDestination& rDest = mFutureDestinations[ nDestId ]; + + rWriter.Push( PushFlags::MAPMODE ); + rWriter.SetMapMode( rDest.mMapMode ); + mParaIds.push_back( rWriter.RegisterDestReference( nDestId, rDest.mRect, rDest.mPageNr, rDest.mAreaType ) ); + rWriter.Pop(); + } + else if (std::holds_alternative<CreateOutlineItem>(action)) { + const vcl::CreateOutlineItem& rCreateOutlineItem = std::get<CreateOutlineItem>(action); + sal_Int32 nParent = GetMappedId(rCreateOutlineItem.mnParent); + sal_Int32 nLinkId = GetMappedId(rCreateOutlineItem.mnDestID); + mParaIds.push_back( rWriter.CreateOutlineItem( nParent, rCreateOutlineItem.maText, nLinkId ) ); + } + else if (std::holds_alternative<CreateNote>(action)) { + const vcl::CreateNote& rCreateNote = std::get<CreateNote>(action); + rWriter.Push( PushFlags::MAPMODE ); + rWriter.SetMapMode( rCreateNote.maParaMapMode ); + rWriter.CreateNote( rCreateNote.maParaRect, rCreateNote.maParaPDFNote, rCreateNote.mnPage ); + } + else if (std::holds_alternative<SetPageTransition>(action)) { + const vcl::SetPageTransition& rSetPageTransition = std::get<SetPageTransition>(action); + rWriter.SetPageTransition( rSetPageTransition.maParaPageTransition, rSetPageTransition.mnMilliSec, rSetPageTransition.mnPage ); + } + } +} + +struct PageSyncData +{ + std::deque< PDFExtOutDevDataSyncPage > mActions; + Graphic mCurrentGraphic; + GlobalSyncData* mpGlobalData; + + bool mbGroupIgnoreGDIMtfActions; + + + explicit PageSyncData( GlobalSyncData* pGlobal ) + : mbGroupIgnoreGDIMtfActions ( false ) + { mpGlobalData = pGlobal; } + + void PushAction( const OutputDevice& rOutDev, PageActionData eAct ); + bool PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData ); +}; + +void PageSyncData::PushAction( const OutputDevice& rOutDev, PageActionData eAct ) +{ + GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile(); + SAL_WARN_IF( !pMtf, "vcl", "PageSyncData::PushAction -> no ConnectMetaFile !!!" ); + + PDFExtOutDevDataSyncPage aSync; + aSync.eAct = std::move(eAct); + if ( pMtf ) + aSync.nIdx = pMtf->GetActionSize(); + else + aSync.nIdx = 0x7fffffff; // sync not possible + mActions.emplace_back( std::move(aSync) ); +} +bool PageSyncData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData ) +{ + bool bRet = false; + if ( !mActions.empty() && ( mActions.front().nIdx == rCurGDIMtfAction ) ) + { + bRet = true; + PDFExtOutDevDataSyncPage aDataSync = std::move(mActions.front()); + mActions.pop_front(); + if (std::holds_alternative<EnsureStructureElement>(aDataSync.eAct)) { +#ifndef NDEBUG + const vcl::EnsureStructureElement& rEnsureStructureElement = std::get<EnsureStructureElement>(aDataSync.eAct); + sal_Int32 const id = +#endif + rWriter.EnsureStructureElement(); + assert(id == -1 || id == rEnsureStructureElement.mnId); // identity mapping + } + else if (std::holds_alternative<InitStructureElement>(aDataSync.eAct)) { + const vcl::InitStructureElement& rInitStructureElement = std::get<InitStructureElement>(aDataSync.eAct); + rWriter.InitStructureElement(rInitStructureElement.mnId, rInitStructureElement.mParaStructElement, rInitStructureElement.maAlias); + } + else if (std::holds_alternative<BeginStructureElement>(aDataSync.eAct)) { + const vcl::BeginStructureElement& rBeginStructureElement = std::get<BeginStructureElement>(aDataSync.eAct); + rWriter.BeginStructureElement(rBeginStructureElement.mnId); + } + else if (std::holds_alternative<EndStructureElement>(aDataSync.eAct)) { + rWriter.EndStructureElement(); + } + else if (std::holds_alternative<SetCurrentStructureElement>(aDataSync.eAct)) { + const vcl::SetCurrentStructureElement& rSetCurrentStructureElement = std::get<SetCurrentStructureElement>(aDataSync.eAct); + rWriter.SetCurrentStructureElement(rSetCurrentStructureElement.mnStructId); + } + else if (std::holds_alternative<SetStructureAttribute>(aDataSync.eAct)) { + const vcl::SetStructureAttribute& rSetStructureAttribute = std::get<SetStructureAttribute>(aDataSync.eAct); + rWriter.SetStructureAttribute( rSetStructureAttribute.mParaStructAttribute, rSetStructureAttribute.mParaStructAttributeValue ); + } + else if (std::holds_alternative<SetStructureAttributeNumerical>(aDataSync.eAct)) { + const vcl::SetStructureAttributeNumerical& rSetStructureAttributeNumerical = std::get<SetStructureAttributeNumerical>(aDataSync.eAct); + rWriter.SetStructureAttributeNumerical( rSetStructureAttributeNumerical.mParaStructAttribute, rSetStructureAttributeNumerical.mnId ); + } + else if (std::holds_alternative<SetStructureBoundingBox>(aDataSync.eAct)) { + const vcl::SetStructureBoundingBox& rSetStructureBoundingBox = std::get<SetStructureBoundingBox>(aDataSync.eAct); + rWriter.SetStructureBoundingBox( rSetStructureBoundingBox.mRect ); + } + else if (std::holds_alternative<SetStructureAnnotIds>(aDataSync.eAct)) { + const vcl::SetStructureAnnotIds& rSetStructureAnnotIds = std::get<SetStructureAnnotIds>(aDataSync.eAct); + rWriter.SetStructureAnnotIds(rSetStructureAnnotIds.annotIds); + } + else if (std::holds_alternative<SetActualText>(aDataSync.eAct)) { + const vcl::SetActualText& rSetActualText = std::get<SetActualText>(aDataSync.eAct); + rWriter.SetActualText( rSetActualText.maText ); + } + else if (std::holds_alternative<SetAlternateText>(aDataSync.eAct)) { + const vcl::SetAlternateText& rSetAlternateText = std::get<SetAlternateText>(aDataSync.eAct); + rWriter.SetAlternateText( rSetAlternateText.maText ); + } + else if (std::holds_alternative<CreateControl>(aDataSync.eAct)) { + const vcl::CreateControl& rCreateControl = std::get<CreateControl>(aDataSync.eAct); + std::shared_ptr< PDFWriter::AnyWidget > pControl( rCreateControl.mxControl ); + SAL_WARN_IF( !pControl, "vcl", "PageSyncData::PlaySyncPageAct: invalid widget!" ); + if ( pControl ) + { + sal_Int32 const n = rWriter.CreateControl(*pControl); + // resolve AnnotIds structural attribute + ::std::vector<sal_Int32> const annotIds{ sal_Int32(mpGlobalData->mCurId) }; + rWriter.SetStructureAnnotIds(annotIds); + // tdf#157397: this must be called *in order* with CreateLink etc. + mpGlobalData->mActions.push_back(CreateControlLink{n}); + mpGlobalData->mCurId++; + } + } + else if (std::holds_alternative<BeginGroup>(aDataSync.eAct)) { + /* first determining if this BeginGroup is starting a GfxLink, + by searching for an EndGroup or an EndGroupGfxLink */ + mbGroupIgnoreGDIMtfActions = false; + auto itStartingGfxLink = std::find_if(mActions.begin(), mActions.end(), + [](const PDFExtOutDevDataSyncPage& rAction) { return std::holds_alternative<EndGroupGfxLink>(rAction.eAct); }); + if ( itStartingGfxLink != mActions.end() ) + { + EndGroupGfxLink& rEndGroup = std::get<EndGroupGfxLink>(itStartingGfxLink->eAct); + Graphic& rGraphic = rEndGroup.maGraphic; + if ( rGraphic.IsGfxLink() ) + { + GfxLinkType eType = rGraphic.GetGfxLink().GetType(); + if ( eType == GfxLinkType::NativeJpg ) + { + mbGroupIgnoreGDIMtfActions = rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect); + if ( !mbGroupIgnoreGDIMtfActions ) + mCurrentGraphic = rGraphic; + } + else if ( eType == GfxLinkType::NativePng || eType == GfxLinkType::NativePdf ) + { + if ( eType == GfxLinkType::NativePdf || rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect) ) + mCurrentGraphic = rGraphic; + } + } + } + } + else if (std::holds_alternative<EndGroupGfxLink>(aDataSync.eAct)) { + EndGroupGfxLink& rEndGroup = std::get<EndGroupGfxLink>(aDataSync.eAct); + tools::Rectangle aOutputRect, aVisibleOutputRect; + Graphic aGraphic( rEndGroup.maGraphic ); + + sal_Int32 nTransparency = rEndGroup.mnTransparency; + aOutputRect = rEndGroup.maOutputRect; + aVisibleOutputRect = rEndGroup.maVisibleOutputRect; + + if ( mbGroupIgnoreGDIMtfActions ) + { + bool bClippingNeeded = ( aOutputRect != aVisibleOutputRect ) && !aVisibleOutputRect.IsEmpty(); + + GfxLink aGfxLink( aGraphic.GetGfxLink() ); + if ( aGfxLink.GetType() == GfxLinkType::NativeJpg ) + { + if ( bClippingNeeded ) + { + rWriter.Push(); + basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(aVisibleOutputRect) ) ); + rWriter.SetClipRegion( aRect); + } + + AlphaMask aAlphaMask; + if (nTransparency) + { + aAlphaMask = AlphaMask(aGraphic.GetSizePixel()); + aAlphaMask.Erase(nTransparency); + } + + SvMemoryStream aTmp; + const sal_uInt8* pData = aGfxLink.GetData(); + sal_uInt32 nBytes = aGfxLink.GetDataSize(); + if( pData && nBytes ) + { + aTmp.WriteBytes( pData, nBytes ); + + // Look up the output rectangle from the previous + // bitmap scale action if possible. This has the + // correct position and size for images with a + // custom translation (Writer header) or scaling + // (Impress notes page). + if (rCurGDIMtfAction > 0) + { + const MetaAction* pAction = rMtf.GetAction(rCurGDIMtfAction - 1); + if (pAction && pAction->GetType() == MetaActionType::BMPSCALE) + { + const MetaBmpScaleAction* pA + = static_cast<const MetaBmpScaleAction*>(pAction); + aOutputRect.SetPos(pA->GetPoint()); + aOutputRect.SetSize(pA->GetSize()); + } + } + auto ePixelFormat = aGraphic.GetBitmapEx().getPixelFormat(); + rWriter.DrawJPGBitmap(aTmp, ePixelFormat > vcl::PixelFormat::N8_BPP, aGraphic.GetSizePixel(), aOutputRect, aAlphaMask, aGraphic); + } + + if ( bClippingNeeded ) + rWriter.Pop(); + } + mbGroupIgnoreGDIMtfActions = false; + } + mCurrentGraphic.Clear(); + } + } + else if ( mbGroupIgnoreGDIMtfActions ) + { + rCurGDIMtfAction++; + bRet = true; + } + return bRet; +} + +PDFExtOutDevData::PDFExtOutDevData( const OutputDevice& rOutDev ) : + mrOutDev ( rOutDev ), + mbTaggedPDF ( false ), + mbExportNotes ( true ), + mbExportNotesInMargin ( false ), + mbExportNotesPages ( false ), + mbTransitionEffects ( true ), + mbUseLosslessCompression( true ), + mbReduceImageResolution ( false ), + mbExportFormFields ( false ), + mbExportBookmarks ( false ), + mbExportHiddenSlides ( false ), + mbSinglePageSheets ( false ), + mbExportNDests ( false ), + mnPage ( -1 ), + mnCompressionQuality ( 90 ), + mpGlobalSyncData ( new GlobalSyncData() ) +{ + mpPageSyncData.reset( new PageSyncData( mpGlobalSyncData.get() ) ); +} + +PDFExtOutDevData::~PDFExtOutDevData() +{ + mpPageSyncData.reset(); + mpGlobalSyncData.reset(); +} + +const Graphic& PDFExtOutDevData::GetCurrentGraphic() const +{ + return mpPageSyncData->mCurrentGraphic; +} + +void PDFExtOutDevData::SetDocumentLocale( const css::lang::Locale& rLoc ) +{ + maDocLocale = rLoc; +} +void PDFExtOutDevData::SetCurrentPageNumber( const sal_Int32 nPage ) +{ + mnPage = nPage; +} +void PDFExtOutDevData::SetIsLosslessCompression( const bool bUseLosslessCompression ) +{ + mbUseLosslessCompression = bUseLosslessCompression; +} +void PDFExtOutDevData::SetCompressionQuality( const sal_Int32 nQuality ) +{ + mnCompressionQuality = nQuality; +} +void PDFExtOutDevData::SetIsReduceImageResolution( const bool bReduceImageResolution ) +{ + mbReduceImageResolution = bReduceImageResolution; +} +void PDFExtOutDevData::SetIsExportNotes( const bool bExportNotes ) +{ + mbExportNotes = bExportNotes; +} +void PDFExtOutDevData::SetIsExportNotesInMargin( const bool bExportNotesInMargin ) +{ + mbExportNotesInMargin = bExportNotesInMargin; +} +void PDFExtOutDevData::SetIsExportNotesPages( const bool bExportNotesPages ) +{ + mbExportNotesPages = bExportNotesPages; +} +void PDFExtOutDevData::SetIsExportTaggedPDF( const bool bTaggedPDF ) +{ + mbTaggedPDF = bTaggedPDF; +} +void PDFExtOutDevData::SetIsExportTransitionEffects( const bool bTransitionEffects ) +{ + mbTransitionEffects = bTransitionEffects; +} +void PDFExtOutDevData::SetIsExportFormFields( const bool bExportFomtFields ) +{ + mbExportFormFields = bExportFomtFields; +} +void PDFExtOutDevData::SetIsExportBookmarks( const bool bExportBookmarks ) +{ + mbExportBookmarks = bExportBookmarks; +} +void PDFExtOutDevData::SetIsExportHiddenSlides( const bool bExportHiddenSlides ) +{ + mbExportHiddenSlides = bExportHiddenSlides; +} +void PDFExtOutDevData::SetIsSinglePageSheets( const bool bSinglePageSheets ) +{ + mbSinglePageSheets = bSinglePageSheets; +} +void PDFExtOutDevData::SetIsExportNamedDestinations( const bool bExportNDests ) +{ + mbExportNDests = bExportNDests; +} +void PDFExtOutDevData::ResetSyncData(PDFWriter *const pWriter) +{ + if (pWriter != nullptr) + { + // tdf#157182 HACK: all PDF actions on this page will be deleted; to have + // matching SE IDs on the *next* page, replay EnsureStructureElement actions + for (PDFExtOutDevDataSyncPage const& rAction : mpPageSyncData->mActions) + { + if (std::holds_alternative<struct EnsureStructureElement>(rAction.eAct)) + { + pWriter->EnsureStructureElement(); + } + } + } + *mpPageSyncData = PageSyncData( mpGlobalSyncData.get() ); +} +bool PDFExtOutDevData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rIdx, const GDIMetaFile& rMtf ) +{ + return mpPageSyncData->PlaySyncPageAct( rWriter, rIdx, rMtf, *this ); +} +void PDFExtOutDevData::PlayGlobalActions( PDFWriter& rWriter ) +{ + mpGlobalSyncData->PlayGlobalActions( rWriter ); +} + +/* global actions, synchronisation to the recorded metafile isn't needed, + all actions will be played after the last page was recorded +*/ +//--->i56629 +sal_Int32 PDFExtOutDevData::CreateNamedDest(const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr ) +{ + mpGlobalSyncData->mActions.push_back( + vcl::CreateNamedDest{ sDestName, mrOutDev.GetMapMode(), PDFWriter::DestAreaType::XYZ, rRect, nPageNr == -1 ? mnPage : nPageNr } ); + + return mpGlobalSyncData->mCurId++; +} +//<---i56629 +sal_Int32 PDFExtOutDevData::RegisterDest() +{ + const sal_Int32 nLinkDestID = mpGlobalSyncData->mCurId++; + mpGlobalSyncData->mActions.push_back( vcl::RegisterDest{ nLinkDestID } ); + + return nLinkDestID; +} +void PDFExtOutDevData::DescribeRegisteredDest( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) +{ + OSL_PRECOND( nDestId != -1, "PDFExtOutDevData::DescribeRegisteredDest: invalid destination Id!" ); + PDFLinkDestination aLinkDestination; + aLinkDestination.mRect = rRect; + aLinkDestination.mMapMode = mrOutDev.GetMapMode(); + aLinkDestination.mPageNr = nPageNr == -1 ? mnPage : nPageNr; + aLinkDestination.mAreaType = eType; + mpGlobalSyncData->mFutureDestinations[ nDestId ] = aLinkDestination; +} +sal_Int32 PDFExtOutDevData::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) +{ + mpGlobalSyncData->mActions.push_back( + vcl::CreateDest{ mrOutDev.GetMapMode(), eType, rRect, nPageNr == -1 ? mnPage : nPageNr } ); + return mpGlobalSyncData->mCurId++; +} +sal_Int32 PDFExtOutDevData::CreateLink(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr) +{ + mpGlobalSyncData->mActions.push_back( + vcl::CreateLink{rAltText, mrOutDev.GetMapMode(), rRect, nPageNr == -1 ? mnPage : nPageNr } ); + return mpGlobalSyncData->mCurId++; +} + +sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect, + OUString const& rAltText, OUString const& rMimeType, + sal_Int32 nPageNr, SdrObject const*const pObj) +{ + mpGlobalSyncData->mActions.push_back(vcl::CreateScreen{ rAltText, rMimeType, mrOutDev.GetMapMode(), rRect, nPageNr }); + auto const ret(mpGlobalSyncData->mCurId++); + m_ScreenAnnotations[pObj].push_back(ret); + return ret; +} + +::std::vector<sal_Int32> const& PDFExtOutDevData::GetScreenAnnotIds(SdrObject const*const pObj) const +{ + auto const it(m_ScreenAnnotations.find(pObj)); + if (it == m_ScreenAnnotations.end()) + { + assert(false); // expected? + } + return it->second; +} + +void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) +{ + mpGlobalSyncData->mActions.push_back( vcl::SetLinkDest{ nLinkId, nDestId } ); +} +void PDFExtOutDevData::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL ) +{ + mpGlobalSyncData->mActions.push_back( vcl::SetLinkURL{ rURL, nLinkId } ); +} + +void PDFExtOutDevData::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL) +{ + mpGlobalSyncData->mActions.push_back(vcl::SetScreenURL{ rURL, nScreenId }); +} + +void PDFExtOutDevData::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL) +{ + mpGlobalSyncData->mActions.push_back(vcl::SetScreenStream{ rURL, nScreenId }); +} + +sal_Int32 PDFExtOutDevData::CreateOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID ) +{ + if (nParent == -1) + // Has no parent, it's a chapter / heading 1. + maChapterNames.push_back(rText); + + mpGlobalSyncData->mActions.push_back( vcl::CreateOutlineItem{ rText, nParent, nDestID } ); + return mpGlobalSyncData->mCurId++; +} +void PDFExtOutDevData::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr ) +{ + mpGlobalSyncData->mActions.push_back( + vcl::CreateNote{ mrOutDev.GetMapMode(), rNote, rRect, nPageNr == -1 ? mnPage : nPageNr } ); +} +void PDFExtOutDevData::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec ) +{ + mpGlobalSyncData->mActions.push_back( vcl::SetPageTransition{ eType, nMilliSec, mnPage } ); +} + +/* local (page), actions have to be played synchronously to the actions of + of the recorded metafile (created by each xRenderable->render()) */ + +sal_Int32 PDFExtOutDevData::EnsureStructureElement(void const*const key) +{ + sal_Int32 id(-1); + if (key != nullptr) + { + auto const it(mpGlobalSyncData->mSEMap.find(key)); + if (it != mpGlobalSyncData->mSEMap.end()) + { + id = it->second; + } + } + if (id == -1) + { + id = mpGlobalSyncData->mStructParents.size(); + mpPageSyncData->PushAction(mrOutDev, vcl::EnsureStructureElement{ id }); + mpGlobalSyncData->mStructParents.push_back(mpGlobalSyncData->mCurrentStructElement); + if (key != nullptr) + { + mpGlobalSyncData->mSEMap.emplace(key, id); + } + } + return id; +} + +void PDFExtOutDevData::InitStructureElement(sal_Int32 const id, + PDFWriter::StructElement const eType, const OUString& rAlias) +{ + mpPageSyncData->PushAction(mrOutDev, vcl::InitStructureElement{ eType, rAlias, id }); + // update parent: required for hell fly anchor frames in sw, so that on the actual + // anchor frame EndStructureElement() resets mCurrentStructElement properly. + mpGlobalSyncData->mStructParents[id] = mpGlobalSyncData->mCurrentStructElement; +} + +void PDFExtOutDevData::BeginStructureElement(sal_Int32 const id) +{ + mpPageSyncData->PushAction( mrOutDev, vcl::BeginStructureElement{ id } ); + mpGlobalSyncData->mCurrentStructElement = id; +} + +sal_Int32 PDFExtOutDevData::WrapBeginStructureElement( + PDFWriter::StructElement const eType, const OUString& rAlias) +{ + sal_Int32 const id = EnsureStructureElement(nullptr); + InitStructureElement(id, eType, rAlias); + BeginStructureElement(id); + return id; +} + +void PDFExtOutDevData::EndStructureElement() +{ + assert(mpGlobalSyncData->mCurrentStructElement != 0); // underflow? + mpPageSyncData->PushAction( mrOutDev, vcl::EndStructureElement{} ); + mpGlobalSyncData->mCurrentStructElement = mpGlobalSyncData->mStructParents[ mpGlobalSyncData->mCurrentStructElement ]; +} + +void PDFExtOutDevData::SetCurrentStructureElement(sal_Int32 const nStructId) +{ + assert(o3tl::make_unsigned(nStructId) < mpGlobalSyncData->mStructParents.size()); + mpGlobalSyncData->mCurrentStructElement = nStructId; + mpPageSyncData->PushAction( mrOutDev, vcl::SetCurrentStructureElement{ nStructId } ); +} + +sal_Int32 PDFExtOutDevData::GetCurrentStructureElement() const +{ + return mpGlobalSyncData->mCurrentStructElement; +} + +void PDFExtOutDevData::SetStructureAttribute( PDFWriter::StructAttribute eAttr, PDFWriter::StructAttributeValue eVal ) +{ + mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttribute{ eAttr, eVal } ); +} +void PDFExtOutDevData::SetStructureAttributeNumerical( PDFWriter::StructAttribute eAttr, sal_Int32 nValue ) +{ + mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttributeNumerical { eAttr, nValue } ); +} +void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect ) +{ + mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureBoundingBox{ rRect } ); +} + +void PDFExtOutDevData::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds) +{ + mpPageSyncData->PushAction(mrOutDev, vcl::SetStructureAnnotIds{ rAnnotIds }); +} + +void PDFExtOutDevData::SetActualText( const OUString& rText ) +{ + mpPageSyncData->PushAction( mrOutDev, vcl::SetActualText{ rText } ); +} +void PDFExtOutDevData::SetAlternateText( const OUString& rText ) +{ + mpPageSyncData->PushAction( mrOutDev, vcl::SetAlternateText{ rText } ); +} + +void PDFExtOutDevData::CreateControl( const PDFWriter::AnyWidget& rControlType ) +{ + std::shared_ptr< PDFWriter::AnyWidget > pClone( rControlType.Clone() ); + mpPageSyncData->PushAction( mrOutDev, vcl::CreateControl{ pClone } ); +} + +void PDFExtOutDevData::BeginGroup() +{ + mpPageSyncData->PushAction( mrOutDev, vcl::BeginGroup{} ); +} + +void PDFExtOutDevData::EndGroup( const Graphic& rGraphic, + sal_uInt8 nTransparency, + const tools::Rectangle& rOutputRect, + const tools::Rectangle& rVisibleOutputRect ) +{ + mpPageSyncData->PushAction( mrOutDev, + vcl::EndGroupGfxLink{rGraphic, rOutputRect, rVisibleOutputRect, nTransparency} ); +} + +// Avoids expensive de-compression and re-compression of large images. +bool PDFExtOutDevData::HasAdequateCompression( const Graphic &rGraphic, + const tools::Rectangle & rOutputRect, + const tools::Rectangle & rVisibleOutputRect ) const +{ + assert(rGraphic.IsGfxLink() && + (rGraphic.GetGfxLink().GetType() == GfxLinkType::NativeJpg || + rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePng || + rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePdf)); + + if (rOutputRect != rVisibleOutputRect) + // rOutputRect is the crop rectangle, re-compress cropped image. + return false; + + if (mbReduceImageResolution) + // Reducing resolution was requested, implies that re-compressing is + // wanted. + return false; + + auto nSize = rGraphic.GetGfxLink().GetDataSize(); + if (nSize == 0) + return false; + + GfxLink aLink = rGraphic.GetGfxLink(); + SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(aLink.GetData()), aLink.GetDataSize(), + StreamMode::READ | StreamMode::WRITE); + GraphicDescriptor aDescriptor(aMemoryStream, nullptr); + if (aDescriptor.Detect(true) && aDescriptor.GetNumberOfImageComponents() == 4) + // 4 means CMYK, which is not handled. + return false; + + const Size aSize = rGraphic.GetSizePixel(); + + // small items better off as PNG anyway + if ( aSize.Width() < 32 && + aSize.Height() < 32 ) + return false; + + if (GetIsLosslessCompression()) + return !GetIsReduceImageResolution(); + + // FIXME: ideally we'd also pre-empt the DPI related scaling too. + sal_Int32 nCurrentRatio = (100 * aSize.Width() * aSize.Height() * 4) / + nSize; + + static const struct { + sal_Int32 mnQuality; + sal_Int32 mnRatio; + } aRatios[] = { // minimum tolerable compression ratios + { 100, 400 }, { 95, 700 }, { 90, 1000 }, { 85, 1200 }, + { 80, 1500 }, { 75, 1700 } + }; + sal_Int32 nTargetRatio = 10000; + bool bIsTargetRatioReached = false; + for (auto & rRatio : aRatios) + { + if ( mnCompressionQuality > rRatio.mnQuality ) + { + bIsTargetRatioReached = true; + break; + } + nTargetRatio = rRatio.mnRatio; + } + + return ((nCurrentRatio > nTargetRatio) && bIsTargetRatioReached); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/pdfobjectcopier.cxx b/vcl/source/gdi/pdfobjectcopier.cxx new file mode 100644 index 0000000000..0d07c0df7f --- /dev/null +++ b/vcl/source/gdi/pdfobjectcopier.cxx @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/log.hxx> +#include <sal/types.h> +#include <rtl/strbuf.hxx> +#include <tools/stream.hxx> +#include <tools/zcodec.hxx> + +#include <vcl/filter/pdfdocument.hxx> +#include <vcl/filter/pdfobjectcontainer.hxx> + +#include <pdf/objectcopier.hxx> +#include <pdf/pdfwriter_impl.hxx> + +namespace vcl +{ +PDFObjectCopier::PDFObjectCopier(PDFObjectContainer& rContainer) + : m_rContainer(rContainer) +{ +} + +void PDFObjectCopier::copyRecursively(OStringBuffer& rLine, filter::PDFElement& rInputElement, + SvMemoryStream& rDocBuffer, + std::map<sal_Int32, sal_Int32>& rCopiedResources) +{ + if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(&rInputElement)) + { + filter::PDFObjectElement* pReferenced = pReference->LookupObject(); + if (pReferenced) + { + // Copy the referenced object. + sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources); + + // Write the updated reference. + rLine.append(nRef); + rLine.append(" 0 R"); + } + } + else if (auto pInputArray = dynamic_cast<filter::PDFArrayElement*>(&rInputElement)) + { + rLine.append("[ "); + for (auto const& pElement : pInputArray->GetElements()) + { + copyRecursively(rLine, *pElement, rDocBuffer, rCopiedResources); + rLine.append(" "); + } + rLine.append("] "); + } + else if (auto pInputDictionary = dynamic_cast<filter::PDFDictionaryElement*>(&rInputElement)) + { + rLine.append("<< "); + for (auto const& pPair : pInputDictionary->GetItems()) + { + rLine.append("/"); + rLine.append(pPair.first); + rLine.append(" "); + copyRecursively(rLine, *pPair.second, rDocBuffer, rCopiedResources); + rLine.append(" "); + } + rLine.append(">> "); + } + else + { + rInputElement.writeString(rLine); + } +} + +sal_Int32 PDFObjectCopier::copyExternalResource(SvMemoryStream& rDocBuffer, + filter::PDFObjectElement& rObject, + std::map<sal_Int32, sal_Int32>& rCopiedResources) +{ + auto it = rCopiedResources.find(rObject.GetObjectValue()); + if (it != rCopiedResources.end()) + { + // This resource was already copied once, nothing to do. + return it->second; + } + + sal_Int32 nObject = m_rContainer.createObject(); + // Remember what is the ID of this object in our output. + rCopiedResources[rObject.GetObjectValue()] = nObject; + SAL_INFO("vcl.pdfwriter", "PDFObjectCopier::copyExternalResource: " << rObject.GetObjectValue() + << " -> " << nObject); + + OStringBuffer aLine = OString::number(nObject) + " 0 obj\n"; + + if (rObject.GetDictionary()) + { + aLine.append("<< "); + bool bFirst = true; + for (auto const& rPair : rObject.GetDictionaryItems()) + { + if (bFirst) + bFirst = false; + else + aLine.append(" "); + + aLine.append("/" + rPair.first + " "); + copyRecursively(aLine, *rPair.second, rDocBuffer, rCopiedResources); + } + + aLine.append(" >>\n"); + } + + filter::PDFStreamElement* pStream = rObject.GetStream(); + if (pStream) + { + aLine.append("stream\n"); + } + + if (filter::PDFArrayElement* pArray = rObject.GetArray()) + { + aLine.append("[ "); + + const std::vector<filter::PDFElement*>& rElements = pArray->GetElements(); + + bool bFirst = true; + for (auto const& pElement : rElements) + { + if (bFirst) + bFirst = false; + else + aLine.append(" "); + copyRecursively(aLine, *pElement, rDocBuffer, rCopiedResources); + } + aLine.append("]\n"); + } + + // If the object has a number element outside a dictionary or array, copy that. + if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement()) + { + pNumber->writeString(aLine); + aLine.append("\n"); + } + + // We have the whole object, now write it to the output. + if (!m_rContainer.updateObject(nObject)) + return -1; + if (!m_rContainer.writeBuffer(aLine)) + return -1; + aLine.setLength(0); + + if (pStream) + { + SvMemoryStream& rStream = pStream->GetMemory(); + m_rContainer.checkAndEnableStreamEncryption(nObject); + aLine.append(static_cast<const char*>(rStream.GetData()), rStream.GetSize()); + if (!m_rContainer.writeBuffer(aLine)) + return -1; + aLine.setLength(0); + m_rContainer.disableStreamEncryption(); + + aLine.append("\nendstream\n"); + if (!m_rContainer.writeBuffer(aLine)) + return -1; + aLine.setLength(0); + } + + aLine.append("endobj\n\n"); + if (!m_rContainer.writeBuffer(aLine)) + return -1; + + return nObject; +} + +OString PDFObjectCopier::copyExternalResources(filter::PDFObjectElement& rPage, + const OString& rKind, + std::map<sal_Int32, sal_Int32>& rCopiedResources) +{ + // A name - object ID map, IDs as they appear in our output, not the + // original ones. + std::map<OString, sal_Int32> aRet; + + // Get the rKind subset of the resource dictionary. + std::map<OString, filter::PDFElement*> aItems; + filter::PDFObjectElement* pKindObject = nullptr; + if (auto pResources + = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources"_ostr))) + { + // Resources is a direct dictionary. + filter::PDFElement* pLookup = pResources->LookupElement(rKind); + if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pLookup)) + { + // rKind is an inline dictionary. + aItems = pDictionary->GetItems(); + } + else if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pLookup)) + { + // rKind refers to a dictionary. + filter::PDFObjectElement* pReferenced = pReference->LookupObject(); + if (!pReferenced) + { + return {}; + } + + pKindObject = pReferenced; + aItems = pReferenced->GetDictionaryItems(); + } + } + else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"_ostr)) + { + // Resources is an indirect object. + filter::PDFElement* pValue = pPageResources->Lookup(rKind); + if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue)) + { + // Kind is a direct dictionary. + aItems = pDictionary->GetItems(); + } + else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind)) + { + // Kind is an indirect object. + aItems = pObject->GetDictionaryItems(); + pKindObject = pObject; + } + } + if (aItems.empty()) + return {}; + + SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer(); + bool bHasDictValue = false; + + for (const auto& rItem : aItems) + { + // For each item copy it over to our output then insert it into aRet. + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second); + if (!pReference) + { + if (pKindObject && dynamic_cast<filter::PDFDictionaryElement*>(rItem.second)) + { + bHasDictValue = true; + break; + } + + continue; + } + + filter::PDFObjectElement* pValue = pReference->LookupObject(); + if (!pValue) + continue; + + // Then copying over an object copy its dictionary and its stream. + sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources); + aRet[rItem.first] = nObject; + } + + if (bHasDictValue && pKindObject) + { + sal_Int32 nObject = copyExternalResource(rDocBuffer, *pKindObject, rCopiedResources); + return "/" + rKind + " " + OString::number(nObject) + " 0 R"; + } + + // Build the dictionary entry string. + OStringBuffer sRet("/" + rKind + "<<"); + for (const auto& rPair : aRet) + { + sRet.append("/" + rPair.first + " " + OString::number(rPair.second) + " 0 R"); + } + sRet.append(">>"); + + return sRet.makeStringAndClear(); +} + +void PDFObjectCopier::copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine) +{ + // Maps from source object id (PDF image) to target object id (export result). + std::map<sal_Int32, sal_Int32> aCopiedResources; + copyPageResources(pPage, rLine, aCopiedResources); +} + +void PDFObjectCopier::copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine, + std::map<sal_Int32, sal_Int32>& rCopiedResources) +{ + rLine.append(" /Resources <<"); + static const std::initializer_list<OString> aKeys + = { "ColorSpace"_ostr, "ExtGState"_ostr, "Font"_ostr, + "XObject"_ostr, "Shading"_ostr, "Pattern"_ostr }; + for (const auto& rKey : aKeys) + { + rLine.append(copyExternalResources(*pPage, rKey, rCopiedResources)); + } + rLine.append(">>"); +} + +sal_Int32 PDFObjectCopier::copyPageStreams(std::vector<filter::PDFObjectElement*>& rContentStreams, + SvMemoryStream& rStream, bool& rCompressed) +{ + for (auto pContent : rContentStreams) + { + filter::PDFStreamElement* pPageStream = pContent->GetStream(); + if (!pPageStream) + { + SAL_WARN("vcl.pdfwriter", "PDFObjectCopier::copyPageStreams: contents has no stream"); + continue; + } + + SvMemoryStream& rPageStream = pPageStream->GetMemory(); + + auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"_ostr)); + auto pFilterArray = dynamic_cast<filter::PDFArrayElement*>(pContent->Lookup("Filter"_ostr)); + if (!pFilter && pFilterArray) + { + auto& aElements = pFilterArray->GetElements(); + if (!aElements.empty()) + pFilter = dynamic_cast<filter::PDFNameElement*>(aElements[0]); + } + + if (pFilter) + { + if (pFilter->GetValue() != "FlateDecode") + { + continue; + } + + SvMemoryStream aMemoryStream; + ZCodec aZCodec; + rPageStream.Seek(0); + aZCodec.BeginCompression(); + aZCodec.Decompress(rPageStream, aMemoryStream); + if (!aZCodec.EndCompression()) + { + SAL_WARN("vcl.pdfwriter", "PDFObjectCopier::copyPageStreams: decompression failed"); + continue; + } + + rStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize()); + } + else + { + rStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize()); + } + } + + rCompressed = PDFWriterImpl::compressStream(&rStream); + + return rStream.Tell(); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx new file mode 100644 index 0000000000..60437f55fe --- /dev/null +++ b/vcl/source/gdi/pdfwriter.cxx @@ -0,0 +1,485 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/bitmapex.hxx> + +#include <pdf/pdfwriter_impl.hxx> + +using namespace vcl; + +PDFWriter::AnyWidget::~AnyWidget() +{ +} + +PDFWriter::PDFWriter( const PDFWriter::PDFWriterContext& rContext, const css::uno::Reference< css::beans::XMaterialHolder >& xEnc ) + : + xImplementation( VclPtr<PDFWriterImpl>::Create(rContext, xEnc, *this) ) +{ +} + +PDFWriter::~PDFWriter() +{ + xImplementation.disposeAndClear(); +} + +OutputDevice* PDFWriter::GetReferenceDevice() +{ + return xImplementation.get(); +} + +void PDFWriter::NewPage( double nPageWidth, double nPageHeight, Orientation eOrientation ) +{ + xImplementation->newPage( nPageWidth, nPageHeight, eOrientation ); +} + +bool PDFWriter::Emit() +{ + return xImplementation->emit(); +} + +void PDFWriter::SetDocumentLocale( const css::lang::Locale& rLoc ) +{ + xImplementation->setDocumentLocale( rLoc ); +} + +void PDFWriter::SetFont( const vcl::Font& rFont ) +{ + xImplementation->setFont( rFont ); +} + +void PDFWriter::DrawText( const Point& rPos, const OUString& rText ) +{ + xImplementation->drawText( rPos, rText, 0, rText.getLength() ); +} + +void PDFWriter::DrawTextLine( + const Point& rPos, + tools::Long nWidth, + FontStrikeout eStrikeout, + FontLineStyle eUnderline, + FontLineStyle eOverline ) +{ + xImplementation->drawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, false/*bUnderlineAbove*/ ); +} + +void PDFWriter::DrawTextArray( + const Point& rStartPt, + const OUString& rStr, + KernArraySpan pDXAry, + std::span<const sal_Bool> pKashidaAry, + sal_Int32 nIndex, + sal_Int32 nLen ) +{ + xImplementation->drawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen ); +} + +void PDFWriter::DrawStretchText( + const Point& rStartPt, + sal_Int32 nWidth, + const OUString& rStr, + sal_Int32 nIndex, + sal_Int32 nLen ) +{ + xImplementation->drawStretchText( rStartPt, nWidth, rStr, nIndex, nLen ); +} + +void PDFWriter::DrawText( + const tools::Rectangle& rRect, + const OUString& rStr, + DrawTextFlags nStyle ) +{ + xImplementation->drawText( rRect, rStr, nStyle ); +} + +void PDFWriter::DrawLine( const Point& rStart, const Point& rStop ) +{ + xImplementation->drawLine( rStart, rStop ); +} + +void PDFWriter::DrawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo ) +{ + xImplementation->drawLine( rStart, rStop, rInfo ); +} + +void PDFWriter::DrawPolygon( const tools::Polygon& rPoly ) +{ + xImplementation->drawPolygon( rPoly ); +} + +void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly ) +{ + xImplementation->drawPolyLine( rPoly ); +} + +void PDFWriter::DrawRect( const tools::Rectangle& rRect ) +{ + xImplementation->drawRectangle( rRect ); +} + +void PDFWriter::DrawRect( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) +{ + xImplementation->drawRectangle( rRect, nHorzRound, nVertRound ); +} + +void PDFWriter::DrawEllipse( const tools::Rectangle& rRect ) +{ + xImplementation->drawEllipse( rRect ); +} + +void PDFWriter::DrawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop ) +{ + xImplementation->drawArc( rRect, rStart, rStop, false, false ); +} + +void PDFWriter::DrawPie( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop ) +{ + xImplementation->drawArc( rRect, rStart, rStop, true, false ); +} + +void PDFWriter::DrawChord( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop ) +{ + xImplementation->drawArc( rRect, rStart, rStop, false, true ); +} + +void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo ) +{ + xImplementation->drawPolyLine( rPoly, rInfo ); +} + +void PDFWriter::DrawPolyLine( const tools::Polygon& rPoly, const ExtLineInfo& rInfo ) +{ + xImplementation->drawPolyLine( rPoly, rInfo ); +} + +void PDFWriter::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly ) +{ + xImplementation->drawPolyPolygon( rPolyPoly ); +} + +void PDFWriter::DrawPixel( const Point& rPos, const Color& rColor ) +{ + xImplementation->drawPixel( rPos, rColor ); +} + +void PDFWriter::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic ) +{ + xImplementation->drawBitmap( rDestPt, rDestSize, rBitmap, rGraphic ); +} + +void PDFWriter::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, const BitmapEx& rBitmap ) +{ + xImplementation->drawBitmap( rDestPt, rDestSize, rBitmap ); +} + +void PDFWriter::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) +{ + xImplementation->drawHatch( rPolyPoly, rHatch ); +} + +void PDFWriter::DrawGradient( const tools::Rectangle& rRect, const Gradient& rGradient ) +{ + xImplementation->drawGradient( rRect, rGradient ); +} + +void PDFWriter::DrawGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient ) +{ + xImplementation->push(PushFlags::CLIPREGION); + xImplementation->setClipRegion( rPolyPoly.getB2DPolyPolygon() ); + xImplementation->drawGradient( rPolyPoly.GetBoundRect(), rGradient ); + xImplementation->pop(); +} + +void PDFWriter::DrawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWallpaper ) +{ + xImplementation->drawWallpaper( rRect, rWallpaper ); +} + +void PDFWriter::DrawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt16 nTransparencePercent ) +{ + xImplementation->drawTransparent( rPolyPoly, nTransparencePercent ); +} + +void PDFWriter::BeginTransparencyGroup() +{ + xImplementation->beginTransparencyGroup(); +} + +void PDFWriter::EndTransparencyGroup( const tools::Rectangle& rRect, sal_uInt16 nTransparentPercent ) +{ + xImplementation->endTransparencyGroup( rRect, nTransparentPercent ); +} + +void PDFWriter::Push( PushFlags nFlags ) +{ + xImplementation->push( nFlags ); +} + +void PDFWriter::Pop() +{ + xImplementation->pop(); +} + +void PDFWriter::SetMapMode( const MapMode& rMapMode ) +{ + xImplementation->setMapMode( rMapMode ); +} + +void PDFWriter::SetLineColor( const Color& rColor ) +{ + xImplementation->setLineColor( rColor ); +} + +void PDFWriter::SetFillColor( const Color& rColor ) +{ + xImplementation->setFillColor( rColor ); +} + +void PDFWriter::SetClipRegion() +{ + xImplementation->clearClipRegion(); +} + +void PDFWriter::SetClipRegion( const basegfx::B2DPolyPolygon& rRegion ) +{ + xImplementation->setClipRegion( rRegion ); +} + +void PDFWriter::MoveClipRegion( tools::Long nHorzMove, tools::Long nVertMove ) +{ + xImplementation->moveClipRegion( nHorzMove, nVertMove ); +} + +void PDFWriter::IntersectClipRegion( const basegfx::B2DPolyPolygon& rRegion ) +{ + xImplementation->intersectClipRegion( rRegion ); +} + +void PDFWriter::IntersectClipRegion( const tools::Rectangle& rRect ) +{ + xImplementation->intersectClipRegion( rRect ); +} + +void PDFWriter::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nMode ) +{ + xImplementation->setLayoutMode( nMode ); +} + +void PDFWriter::SetDigitLanguage( LanguageType eLang ) +{ + xImplementation->setDigitLanguage( eLang ); +} + +void PDFWriter::SetTextColor( const Color& rColor ) +{ + xImplementation->setTextColor( rColor ); +} + +void PDFWriter::SetTextFillColor() +{ + xImplementation->setTextFillColor(); +} + +void PDFWriter::SetTextFillColor( const Color& rColor ) +{ + xImplementation->setTextFillColor( rColor ); +} + +void PDFWriter::SetTextLineColor() +{ + xImplementation->setTextLineColor(); +} + +void PDFWriter::SetTextLineColor( const Color& rColor ) +{ + xImplementation->setTextLineColor( rColor ); +} + +void PDFWriter::SetOverlineColor() +{ + xImplementation->setOverlineColor(); +} + +void PDFWriter::SetOverlineColor( const Color& rColor ) +{ + xImplementation->setOverlineColor( rColor ); +} + +void PDFWriter::SetTextAlign( ::TextAlign eAlign ) +{ + xImplementation->setTextAlign( eAlign ); +} + +void PDFWriter::DrawJPGBitmap( SvStream& rStreamData, bool bIsTrueColor, const Size& rSrcSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic ) +{ + xImplementation->drawJPGBitmap( rStreamData, bIsTrueColor, rSrcSizePixel, rTargetArea, rAlphaMask, rGraphic ); +} + +sal_Int32 PDFWriter::CreateLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText) +{ + return xImplementation->createLink(rRect, nPageNr, rAltText); +} + +sal_Int32 PDFWriter::CreateScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType) +{ + return xImplementation->createScreen(rRect, nPageNr, rAltText, rMimeType); +} + +sal_Int32 PDFWriter::RegisterDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, DestAreaType eType ) +{ + return xImplementation->registerDestReference( nDestId, rRect, nPageNr, eType ); +} +//--->i56629 +sal_Int32 PDFWriter::CreateNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) +{ + return xImplementation->createNamedDest( sDestName, rRect, nPageNr, eType ); +} +sal_Int32 PDFWriter::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) +{ + return xImplementation->createDest( rRect, nPageNr, eType ); +} + +void PDFWriter::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) +{ + xImplementation->setLinkDest( nLinkId, nDestId ); +} + +void PDFWriter::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL ) +{ + xImplementation->setLinkURL( nLinkId, rURL ); +} + +void PDFWriter::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL) +{ + xImplementation->setScreenURL(nScreenId, rURL); +} + +void PDFWriter::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL) +{ + xImplementation->setScreenStream(nScreenId, rURL); +} + +void PDFWriter::SetLinkPropertyID( sal_Int32 nLinkId, sal_Int32 nPropertyId ) +{ + xImplementation->setLinkPropertyId( nLinkId, nPropertyId ); +} + +sal_Int32 PDFWriter::CreateOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID ) +{ + return xImplementation->createOutlineItem( nParent, rText, nDestID ); +} + +void PDFWriter::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr ) +{ + xImplementation->createNote( rRect, rNote, nPageNr ); +} + +sal_Int32 PDFWriter::EnsureStructureElement() +{ + return xImplementation->ensureStructureElement(); +} + +void PDFWriter::InitStructureElement(sal_Int32 const id, + PDFWriter::StructElement const eType, std::u16string_view const rAlias) +{ + return xImplementation->initStructureElement(id, eType, rAlias); +} + +void PDFWriter::BeginStructureElement(sal_Int32 const id) +{ + return xImplementation->beginStructureElement(id); +} + +void PDFWriter::EndStructureElement() +{ + xImplementation->endStructureElement(); +} + +void PDFWriter::SetCurrentStructureElement( sal_Int32 nID ) +{ + xImplementation->setCurrentStructureElement( nID ); +} + +void PDFWriter::SetStructureAttribute( enum StructAttribute eAttr, enum StructAttributeValue eVal ) +{ + xImplementation->setStructureAttribute( eAttr, eVal ); +} + +void PDFWriter::SetStructureAttributeNumerical( enum StructAttribute eAttr, sal_Int32 nValue ) +{ + xImplementation->setStructureAttributeNumerical( eAttr, nValue ); +} + +void PDFWriter::SetStructureBoundingBox( const tools::Rectangle& rRect ) +{ + xImplementation->setStructureBoundingBox( rRect ); +} + +void PDFWriter::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds) +{ + xImplementation->setStructureAnnotIds(rAnnotIds); +} + +void PDFWriter::SetActualText( const OUString& rText ) +{ + xImplementation->setActualText( rText ); +} + +void PDFWriter::SetAlternateText( const OUString& rText ) +{ + xImplementation->setAlternateText( rText ); +} + +void PDFWriter::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr ) +{ + xImplementation->setPageTransition( eType, nMilliSec, nPageNr ); +} + +sal_Int32 PDFWriter::CreateControl( const PDFWriter::AnyWidget& rControl ) +{ + return xImplementation->createControl( rControl ); +} + +PDFOutputStream::~PDFOutputStream() +{ +} + +void PDFWriter::AddAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> pStream) +{ + xImplementation->addDocumentAttachedFile(rFileName, rMimeType, rDescription, std::move(pStream)); +} + +std::set< PDFWriter::ErrorCode > const & PDFWriter::GetErrors() const +{ + return xImplementation->getErrors(); +} + +css::uno::Reference< css::beans::XMaterialHolder > +PDFWriter::InitEncryption( const OUString& i_rOwnerPassword, + const OUString& i_rUserPassword + ) +{ + return PDFWriterImpl::initEncryption( i_rOwnerPassword, i_rUserPassword ); +} + +void PDFWriter::PlayMetafile( const GDIMetaFile& i_rMTF, const vcl::PDFWriter::PlayMetafileContext& i_rPlayContext, PDFExtOutDevData* i_pData ) +{ + xImplementation->playMetafile( i_rMTF, i_pData, i_rPlayContext ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx new file mode 100644 index 0000000000..c7ef07ff49 --- /dev/null +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -0,0 +1,11934 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <config_crypto.h> + +#include <sal/types.h> + +#include <math.h> +#include <algorithm> +#include <string_view> + +#include <lcms2.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <memory> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/util/URL.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <comphelper/xmlencode.hxx> +#include <cppuhelper/implbase.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <o3tl/numeric.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/temporary.hxx> +#include <officecfg/Office/Common.hxx> +#include <osl/file.hxx> +#include <osl/thread.h> +#include <rtl/crc.h> +#include <rtl/digest.h> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <svl/cryptosign.hxx> +#include <sal/log.hxx> +#include <svl/urihelper.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <tools/helpers.hxx> +#include <tools/urlobj.hxx> +#include <tools/UnitConversion.hxx> +#include <tools/zcodec.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/fontcharmap.hxx> +#include <vcl/glyphitemcache.hxx> +#include <vcl/kernarray.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/metric.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/pdfread.hxx> +#include <vcl/settings.hxx> +#include <strhelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/filter/pdfdocument.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <comphelper/hash.hxx> + +#include <svdata.hxx> +#include <vcl/BitmapWriteAccess.hxx> +#include <fontsubset.hxx> +#include <font/EmphasisMark.hxx> +#include <font/PhysicalFontFace.hxx> +#include <salgdi.hxx> +#include <textlayout.hxx> +#include <textlineinfo.hxx> +#include <impglyphitem.hxx> +#include <pdf/XmpMetadata.hxx> +#include <pdf/objectcopier.hxx> +#include <pdf/pdfwriter_impl.hxx> +#include <pdf/PdfConfig.hxx> +#include <o3tl/sorted_vector.hxx> +#include <frozen/bits/defines.h> +#include <frozen/bits/elsa_std.h> +#include <frozen/map.h> + +using namespace::com::sun::star; + +static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION"); + +namespace +{ + +constexpr sal_Int32 nLog10Divisor = 3; +constexpr double fDivisor = 1000.0; + +constexpr double pixelToPoint(double px) +{ + return px / fDivisor; +} + +constexpr sal_Int32 pointToPixel(double pt) +{ + return sal_Int32(pt * fDivisor); +} + +void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine) +{ + aLine.append(nObjectID); + aLine.append(" 0 obj\n"); +} + +void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine) +{ + aLine.append(nObjectID); + aLine.append(" 0 R "); +} + +void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer) +{ + static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] ); + rBuffer.append( pHexDigits[ nInt & 15 ] ); +} + +void appendName( std::u16string_view rStr, OStringBuffer& rBuffer ) +{ +// FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1 +// I guess than when reading the #xx sequence it will count for a single character. + OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) ); + int nLen = aStr.getLength(); + for( int i = 0; i < nLen; i++ ) + { + /* #i16920# PDF recommendation: output UTF8, any byte + * outside the interval [33(=ASCII'!');126(=ASCII'~')] + * should be escaped hexadecimal + * for the sake of ghostscript which also reads PDF + * but has a narrower acceptance rate we only pass + * alphanumerics and '-' literally. + */ + if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) || + (aStr[i] >= 'a' && aStr[i] <= 'z' ) || + (aStr[i] >= '0' && aStr[i] <= '9' ) || + aStr[i] == '-' ) + { + rBuffer.append( aStr[i] ); + } + else + { + rBuffer.append( '#' ); + appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer ); + } + } +} + +void appendName( const char* pStr, OStringBuffer& rBuffer ) +{ + // FIXME i59651 see above + while( pStr && *pStr ) + { + if( (*pStr >= 'A' && *pStr <= 'Z' ) || + (*pStr >= 'a' && *pStr <= 'z' ) || + (*pStr >= '0' && *pStr <= '9' ) || + *pStr == '-' ) + { + rBuffer.append( *pStr ); + } + else + { + rBuffer.append( '#' ); + appendHex( static_cast<sal_Int8>(*pStr), rBuffer ); + } + pStr++; + } +} + +//used only to emit encoded passwords +void appendLiteralString( const char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer ) +{ + while( nLength ) + { + switch( *pStr ) + { + case '\n' : + rBuffer.append( "\\n" ); + break; + case '\r' : + rBuffer.append( "\\r" ); + break; + case '\t' : + rBuffer.append( "\\t" ); + break; + case '\b' : + rBuffer.append( "\\b" ); + break; + case '\f' : + rBuffer.append( "\\f" ); + break; + case '(' : + case ')' : + case '\\' : + rBuffer.append( "\\" ); + rBuffer.append( static_cast<char>(*pStr) ); + break; + default: + rBuffer.append( static_cast<char>(*pStr) ); + break; + } + pStr++; + nLength--; + } +} + +/* + * Convert a string before using it. + * + * This string conversion function is needed because the destination name + * in a PDF file seen through an Internet browser should be + * specially crafted, in order to be used directly by the browser. + * In this way the fragment part of a hyperlink to a PDF file (e.g. something + * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the + * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called + * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf + * and go to named destination thefragment using default zoom'. + * The conversion is needed because in case of a fragment in the form: Slide%201 + * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201 + * using this conversion, in both the generated named destinations, fragment and GoToR + * destination. + * + * The names for destinations are name objects and so they don't need to be encrypted + * even though they expose the content of PDF file (e.g. guessing the PDF content from the + * destination name). + * + * Further limitation: it is advisable to use standard ASCII characters for + * OOo bookmarks. +*/ +void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer ) +{ + const sal_Unicode* pStr = rString.getStr(); + sal_Int32 nLen = rString.getLength(); + for( int i = 0; i < nLen; i++ ) + { + sal_Unicode aChar = pStr[i]; + if( (aChar >= '0' && aChar <= '9' ) || + (aChar >= 'a' && aChar <= 'z' ) || + (aChar >= 'A' && aChar <= 'Z' ) || + aChar == '-' ) + { + rBuffer.append(static_cast<char>(aChar)); + } + else + { + sal_Int8 aValueHigh = sal_Int8(aChar >> 8); + if(aValueHigh > 0) + appendHex( aValueHigh, rBuffer ); + appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer ); + } + } +} + +} // end anonymous namespace + +namespace vcl +{ +const sal_uInt8 PDFWriterImpl::s_nPadString[32] = +{ + 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, + 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A +}; + +namespace +{ + +template < class GEOMETRY > +GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject ) +{ + GEOMETRY aPoint; + if ( MapUnit::MapPixel == _rSource.GetMapUnit() ) + { + aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest ); + } + else + { + aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest ); + } + return aPoint; +} + +void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle); + +} // end anonymous namespace + +void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer) +{ + rBuffer.append( "FEFF" ); + const sal_Unicode* pStr = rString.getStr(); + sal_Int32 nLen = rString.getLength(); + for( int i = 0; i < nLen; i++ ) + { + sal_Unicode aChar = pStr[i]; + appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer ); + appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer ); + } +} + +void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl ) +{ + /* #i80258# previously we use appendName here + however we need a slightly different coding scheme than the normal + name encoding for field names + */ + const OUString& rName = i_rControl.Name; + OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) ); + int nLen = aStr.getLength(); + + OStringBuffer aBuffer( rName.getLength()+64 ); + for( int i = 0; i < nLen; i++ ) + { + /* #i16920# PDF recommendation: output UTF8, any byte + * outside the interval [32(=ASCII' ');126(=ASCII'~')] + * should be escaped hexadecimal + */ + if( aStr[i] >= 32 && aStr[i] <= 126 ) + aBuffer.append( aStr[i] ); + else + { + aBuffer.append( '#' ); + appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer ); + } + } + + OString aFullName( aBuffer.makeStringAndClear() ); + + /* #i82785# create hierarchical fields down to the for each dot in i_rName */ + sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0; + OString aPartialName; + OString aDomain; + do + { + nLastTokenIndex = nTokenIndex; + aPartialName = aFullName.getToken( 0, '.', nTokenIndex ); + if( nTokenIndex != -1 ) + { + // find or create a hierarchical field + // first find the fully qualified name up to this field + aDomain = aFullName.copy( 0, nTokenIndex-1 ); + std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain ); + if( it == m_aFieldNameMap.end() ) + { + // create new hierarchy field + sal_Int32 nNewWidget = m_aWidgets.size(); + m_aWidgets.emplace_back( ); + m_aWidgets[nNewWidget].m_nObject = createObject(); + m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy; + m_aWidgets[nNewWidget].m_aName = aPartialName; + m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; + m_aFieldNameMap[aDomain] = nNewWidget; + m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; + if( nLastTokenIndex > 0 ) + { + // this field is not a root field and + // needs to be inserted to its parent + OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) ); + it = m_aFieldNameMap.find( aParentDomain ); + OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" ); + if( it != m_aFieldNameMap.end() ) + { + OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" ); + if( it->second < sal_Int32(m_aWidgets.size()) ) + { + PDFWidget& rParentField( m_aWidgets[it->second] ); + rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject ); + rParentField.m_aKidsIndex.push_back( nNewWidget ); + m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject; + } + } + } + } + else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy ) + { + // this is invalid, someone tries to have a terminal field as parent + // example: a button with the name foo.bar exists and + // another button is named foo.bar.no + // workaround: put the second terminal field as much up in the hierarchy as + // necessary to have a non-terminal field as parent (or none at all) + // since it->second already is terminal, we just need to use its parent + aDomain.clear(); + aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); + if( nLastTokenIndex > 0 ) + { + aDomain = aFullName.copy( 0, nLastTokenIndex-1 ); + aFullName = aDomain + "." + aPartialName; + } + else + aFullName = aPartialName; + break; + } + } + } while( nTokenIndex != -1 ); + + // insert widget into its hierarchy field + if( !aDomain.isEmpty() ) + { + std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain ); + if( it != m_aFieldNameMap.end() ) + { + OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" ); + if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() ) + { + m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject; + m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject); + m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex ); + } + } + } + + if( aPartialName.isEmpty() ) + { + // how funny, an empty field name + if( i_rControl.getType() == PDFWriter::RadioButton ) + { + aPartialName = "RadioGroup" + + OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup ); + } + else + aPartialName = "Widget"_ostr; + } + + if( ! m_aContext.AllowDuplicateFieldNames ) + { + std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName ); + + if( it != m_aFieldNameMap.end() ) // not unique + { + std::unordered_map< OString, sal_Int32 >::const_iterator check_it; + OString aTry; + sal_Int32 nTry = 2; + do + { + aTry = aFullName + "_" + OString::number(nTry++); + check_it = m_aFieldNameMap.find( aTry ); + } while( check_it != m_aFieldNameMap.end() ); + aFullName = aTry; + m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; + aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); + } + else + m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; + } + + // finally + m_aWidgets[i_nWidgetIndex].m_aName = aPartialName; +} + +namespace +{ + +void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer ) +{ + if( nValue < 0 ) + { + rBuffer.append( '-' ); + nValue = -nValue; + } + sal_Int32 nFactor = 1, nDiv = nLog10Divisor; + while( nDiv-- ) + nFactor *= 10; + + sal_Int32 nInt = nValue / nFactor; + rBuffer.append( nInt ); + if (nFactor > 1 && nValue % nFactor) + { + rBuffer.append( '.' ); + do + { + nFactor /= 10; + rBuffer.append((nValue / nFactor) % 10); + } + while (nFactor > 1 && nValue % nFactor); // omit trailing zeros + } +} + +// appends a double. PDF does not accept exponential format, only fixed point +void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 ) +{ + bool bNeg = false; + if( fValue < 0.0 ) + { + bNeg = true; + fValue=-fValue; + } + + sal_Int64 nInt = static_cast<sal_Int64>(fValue); + fValue -= static_cast<double>(nInt); + // optimizing hardware may lead to a value of 1.0 after the subtraction + if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision ) + { + nInt++; + fValue = 0.0; + } + sal_Int64 nFrac = 0; + if( fValue ) + { + fValue *= pow( 10.0, static_cast<double>(nPrecision) ); + nFrac = static_cast<sal_Int64>(fValue); + } + if( bNeg && ( nInt || nFrac ) ) + rBuffer.append( '-' ); + rBuffer.append( nInt ); + if( !nFrac ) + return; + + int i; + rBuffer.append( '.' ); + sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5); + for ( i = 0; ( i < nPrecision ) && nFrac; i++ ) + { + sal_Int64 nNumb = nFrac / nBound; + nFrac -= nNumb * nBound; + rBuffer.append( nNumb ); + nBound /= 10; + } +} + +void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey ) +{ + + if( rColor == COL_TRANSPARENT ) + return; + + if( bConvertToGrey ) + { + sal_uInt8 cByte = rColor.GetLuminance(); + appendDouble( static_cast<double>(cByte) / 255.0, rBuffer ); + } + else + { + appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer ); + rBuffer.append( ' ' ); + appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer ); + rBuffer.append( ' ' ); + appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer ); + } +} + +} // end anonymous namespace + +void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) +{ + if( rColor != COL_TRANSPARENT ) + { + bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale; + appendColor( rColor, rBuffer, bGrey ); + rBuffer.append( bGrey ? " G" : " RG" ); + } +} + +void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) +{ + if( rColor != COL_TRANSPARENT ) + { + bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale; + appendColor( rColor, rBuffer, bGrey ); + rBuffer.append( bGrey ? " g" : " rg" ); + } +} + +namespace +{ + +void appendPdfTimeDate(OStringBuffer & rBuffer, + sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta) +{ + rBuffer.append("D:"); + rBuffer.append(char('0' + ((year / 1000) % 10))); + rBuffer.append(char('0' + ((year / 100) % 10))); + rBuffer.append(char('0' + ((year / 10) % 10))); + rBuffer.append(char('0' + (year % 10))); + rBuffer.append(char('0' + ((month / 10) % 10))); + rBuffer.append(char('0' + (month % 10))); + rBuffer.append(char('0' + ((day / 10) % 10))); + rBuffer.append(char('0' + (day % 10))); + rBuffer.append(char('0' + ((hours / 10) % 10))); + rBuffer.append(char('0' + (hours % 10))); + rBuffer.append(char('0' + ((minutes / 10) % 10))); + rBuffer.append(char('0' + (minutes % 10))); + rBuffer.append(char('0' + ((seconds / 10) % 10))); + rBuffer.append(char('0' + (seconds % 10))); + + if (tzDelta == 0) + { + rBuffer.append("Z"); + } + else + { + if (tzDelta > 0 ) + rBuffer.append("+"); + else + { + rBuffer.append("-"); + tzDelta = -tzDelta; + } + + rBuffer.append(char('0' + ((tzDelta / 36000) % 10))); + rBuffer.append(char('0' + ((tzDelta / 3600) % 10))); + rBuffer.append("'"); + rBuffer.append(char('0' + ((tzDelta / 600) % 6))); + rBuffer.append(char('0' + ((tzDelta / 60) % 10))); + } +} + +const char* getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion) +{ + switch (ePDFVersion) + { + case PDFWriter::PDFVersion::PDF_A_1: + case PDFWriter::PDFVersion::PDF_1_4: + return "1.4"; + case PDFWriter::PDFVersion::PDF_1_5: + return "1.5"; + case PDFWriter::PDFVersion::PDF_1_6: + return "1.6"; + default: + case PDFWriter::PDFVersion::PDF_A_2: + case PDFWriter::PDFVersion::PDF_A_3: + case PDFWriter::PDFVersion::PDF_1_7: + return "1.7"; + } +} + +} // end namespace + +PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation ) + : + m_pWriter( pWriter ), + m_nPageWidth( nPageWidth ), + m_nPageHeight( nPageHeight ), + m_nUserUnit( 1 ), + m_eOrientation( eOrientation ), + m_nPageObject( 0 ), // invalid object number + m_nStreamLengthObject( 0 ), + m_nBeginStreamPos( 0 ), + m_eTransition( PDFWriter::PageTransition::Regular ), + m_nTransTime( 0 ) +{ + // object ref must be only ever updated in emit() + m_nPageObject = m_pWriter->createObject(); + + switch (m_pWriter->m_aContext.Version) + { + // 1.6 or later + default: + m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0); + break; + case PDFWriter::PDFVersion::PDF_1_4: + case PDFWriter::PDFVersion::PDF_1_5: + case PDFWriter::PDFVersion::PDF_A_1: + break; + } +} + +void PDFPage::beginStream() +{ + if (g_bDebugDisableCompression) + { + m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +"); + } + m_aStreamObjects.push_back(m_pWriter->createObject()); + if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) ) + return; + + m_nStreamLengthObject = m_pWriter->createObject(); + // write content stream header + OStringBuffer aLine( + OString::number(m_aStreamObjects.back()) + + " 0 obj\n<</Length " + + OString::number( m_nStreamLengthObject ) + + " 0 R" ); + if (!g_bDebugDisableCompression) + aLine.append( "/Filter/FlateDecode" ); + aLine.append( ">>\nstream\n" ); + if( ! m_pWriter->writeBuffer( aLine ) ) + return; + if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos)) + { + m_pWriter->m_aFile.close(); + m_pWriter->m_bOpen = false; + } + if (!g_bDebugDisableCompression) + m_pWriter->beginCompression(); + m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() ); +} + +void PDFPage::endStream() +{ + if (!g_bDebugDisableCompression) + m_pWriter->endCompression(); + sal_uInt64 nEndStreamPos; + if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos)) + { + m_pWriter->m_aFile.close(); + m_pWriter->m_bOpen = false; + return; + } + m_pWriter->disableStreamEncryption(); + if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n" ) ) + return; + // emit stream length object + if( ! m_pWriter->updateObject( m_nStreamLengthObject ) ) + return; + OString aLine = + OString::number( m_nStreamLengthObject ) + + " 0 obj\n" + + OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) + + "\nendobj\n\n"; + m_pWriter->writeBuffer( aLine ); +} + +bool PDFPage::emit(sal_Int32 nParentObject ) +{ + m_pWriter->MARK("PDFPage::emit"); + // emit page object + if( ! m_pWriter->updateObject( m_nPageObject ) ) + return false; + OStringBuffer aLine( + OString::number(m_nPageObject) + + " 0 obj\n" + "<</Type/Page/Parent " + + OString::number(nParentObject) + + " 0 R" + "/Resources " + + OString::number(m_pWriter->getResourceDictObj()) + + " 0 R" ); + if( m_nPageWidth && m_nPageHeight ) + { + aLine.append( "/MediaBox[0 0 " + + OString::number(m_nPageWidth / m_nUserUnit) + + " " + + OString::number(m_nPageHeight / m_nUserUnit) + + "]" ); + if (m_nUserUnit > 1) + { + aLine.append("\n/UserUnit " + OString::number(m_nUserUnit)); + } + } + switch( m_eOrientation ) + { + case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break; + case PDFWriter::Orientation::Inherit: break; + } + int nAnnots = m_aAnnotations.size(); + if( nAnnots > 0 ) + { + aLine.append( "/Annots[\n" ); + for( int i = 0; i < nAnnots; i++ ) + { + aLine.append( OString::number(m_aAnnotations[i]) + + " 0 R" ); + aLine.append( ((i+1)%15) ? " " : "\n" ); + } + aLine.append( "]\n" ); + if (PDFWriter::PDFVersion::PDF_1_5 <= m_pWriter->m_aContext.Version) + { + // ISO 14289-1:2014, Clause: 7.18.3 + aLine.append( "/Tabs/S\n" ); + } + } + if( !m_aMCIDParents.empty() ) + { + OStringBuffer aStructParents( 1024 ); + aStructParents.append( "[ " ); + int nParents = m_aMCIDParents.size(); + for( int i = 0; i < nParents; i++ ) + { + aStructParents.append( OString::number(m_aMCIDParents[i]) + + " 0 R" ); + aStructParents.append( ((i%10) == 9) ? "\n" : " " ); + } + aStructParents.append( "]" ); + m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() ); + + aLine.append( "/StructParents " + + OString::number( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) ) + + "\n" ); + } + if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 ) + { + // transition duration + aLine.append( "/Trans<</D " ); + appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 ); + aLine.append( "\n" ); + const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr; + switch( m_eTransition ) + { + case PDFWriter::PageTransition::SplitHorizontalInward: + pStyle = "Split"; pDm = "H"; pM = "I"; break; + case PDFWriter::PageTransition::SplitHorizontalOutward: + pStyle = "Split"; pDm = "H"; pM = "O"; break; + case PDFWriter::PageTransition::SplitVerticalInward: + pStyle = "Split"; pDm = "V"; pM = "I"; break; + case PDFWriter::PageTransition::SplitVerticalOutward: + pStyle = "Split"; pDm = "V"; pM = "O"; break; + case PDFWriter::PageTransition::BlindsHorizontal: + pStyle = "Blinds"; pDm = "H"; break; + case PDFWriter::PageTransition::BlindsVertical: + pStyle = "Blinds"; pDm = "V"; break; + case PDFWriter::PageTransition::BoxInward: + pStyle = "Box"; pM = "I"; break; + case PDFWriter::PageTransition::BoxOutward: + pStyle = "Box"; pM = "O"; break; + case PDFWriter::PageTransition::WipeLeftToRight: + pStyle = "Wipe"; pDi = "0"; break; + case PDFWriter::PageTransition::WipeBottomToTop: + pStyle = "Wipe"; pDi = "90"; break; + case PDFWriter::PageTransition::WipeRightToLeft: + pStyle = "Wipe"; pDi = "180"; break; + case PDFWriter::PageTransition::WipeTopToBottom: + pStyle = "Wipe"; pDi = "270"; break; + case PDFWriter::PageTransition::Dissolve: + pStyle = "Dissolve"; break; + case PDFWriter::PageTransition::Regular: + break; + } + // transition style + if( pStyle ) + { + aLine.append( OString::Concat("/S/") + pStyle + "\n" ); + } + if( pDm ) + { + aLine.append( OString::Concat("/Dm/") + pDm + "\n" ); + } + if( pM ) + { + aLine.append( OString::Concat("/M/") + pM + "\n" ); + } + if( pDi ) + { + aLine.append( OString::Concat("/Di ") + pDi + "\n" ); + } + aLine.append( ">>\n" ); + } + + aLine.append( "/Contents" ); + unsigned int nStreamObjects = m_aStreamObjects.size(); + if( nStreamObjects > 1 ) + aLine.append( '[' ); + for(sal_Int32 i : m_aStreamObjects) + { + aLine.append( " " + OString::number( i ) + " 0 R" ); + } + if( nStreamObjects > 1 ) + aLine.append( ']' ); + aLine.append( ">>\nendobj\n\n" ); + return m_pWriter->writeBuffer( aLine ); +} + +void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const +{ + Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter, + rPoint ) ); + + sal_Int32 nValue = aPoint.X(); + + appendFixedInt( nValue, rBuffer ); + + rBuffer.append( ' ' ); + + nValue = pointToPixel(getHeight()) - aPoint.Y(); + + appendFixedInt( nValue, rBuffer ); +} + +void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const +{ + double fValue = pixelToPoint(rPoint.getX()); + + appendDouble( fValue, rBuffer, nLog10Divisor ); + rBuffer.append( ' ' ); + fValue = getHeight() - pixelToPoint(rPoint.getY()); + appendDouble( fValue, rBuffer, nLog10Divisor ); +} + +void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const +{ + appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer ); + rBuffer.append( " re" ); +} + +void PDFPage::convertRect( tools::Rectangle& rRect ) const +{ + Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter, + rRect.BottomLeft() + Point( 0, 1 ) + ); + Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter, + rRect.GetSize() ); + rRect.SetLeft( aLL.X() ); + rRect.SetRight( aLL.X() + aSize.Width() ); + rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() ); + rRect.SetBottom( rRect.Top() + aSize.Height() ); +} + +void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const +{ + sal_uInt16 nPoints = rPoly.GetSize(); + /* + * #108582# applications do weird things + */ + sal_uInt32 nBufLen = rBuffer.getLength(); + if( nPoints <= 0 ) + return; + + const PolyFlags* pFlagArray = rPoly.GetConstFlagAry(); + appendPoint( rPoly[0], rBuffer ); + rBuffer.append( " m\n" ); + for( sal_uInt16 i = 1; i < nPoints; i++ ) + { + if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 ) + { + // bezier + SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" ); + appendPoint( rPoly[i], rBuffer ); + rBuffer.append( " " ); + appendPoint( rPoly[i+1], rBuffer ); + rBuffer.append( " " ); + appendPoint( rPoly[i+2], rBuffer ); + rBuffer.append( " c" ); + i += 2; // add additionally consumed points + } + else + { + // line + appendPoint( rPoly[i], rBuffer ); + rBuffer.append( " l" ); + } + if( (rBuffer.getLength() - nBufLen) > 65 ) + { + rBuffer.append( "\n" ); + nBufLen = rBuffer.getLength(); + } + else + rBuffer.append( " " ); + } + if( bClose ) + rBuffer.append( "h\n" ); +} + +void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const +{ + basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter, + rPoly ) ); + + if( basegfx::utils::isRectangle( aPoly ) ) + { + basegfx::B2DRange aRange( aPoly.getB2DRange() ); + basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() ); + appendPixelPoint( aBL, rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor ); + rBuffer.append( ' ' ); + appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor ); + rBuffer.append( " re\n" ); + return; + } + sal_uInt32 nPoints = aPoly.count(); + if( nPoints <= 0 ) + return; + + sal_uInt32 nBufLen = rBuffer.getLength(); + basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) ); + appendPixelPoint( aLastPoint, rBuffer ); + rBuffer.append( " m\n" ); + for( sal_uInt32 i = 1; i <= nPoints; i++ ) + { + if( i != nPoints || aPoly.isClosed() ) + { + sal_uInt32 nCurPoint = i % nPoints; + sal_uInt32 nLastPoint = i-1; + basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) ); + if( aPoly.isNextControlPointUsed( nLastPoint ) && + aPoly.isPrevControlPointUsed( nCurPoint ) ) + { + appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer ); + rBuffer.append( ' ' ); + appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer ); + rBuffer.append( ' ' ); + appendPixelPoint( aPoint, rBuffer ); + rBuffer.append( " c" ); + } + else if( aPoly.isNextControlPointUsed( nLastPoint ) ) + { + appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer ); + rBuffer.append( ' ' ); + appendPixelPoint( aPoint, rBuffer ); + rBuffer.append( " y" ); + } + else if( aPoly.isPrevControlPointUsed( nCurPoint ) ) + { + appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer ); + rBuffer.append( ' ' ); + appendPixelPoint( aPoint, rBuffer ); + rBuffer.append( " v" ); + } + else + { + appendPixelPoint( aPoint, rBuffer ); + rBuffer.append( " l" ); + } + if( (rBuffer.getLength() - nBufLen) > 65 ) + { + rBuffer.append( "\n" ); + nBufLen = rBuffer.getLength(); + } + else + rBuffer.append( " " ); + } + } + rBuffer.append( "h\n" ); +} + +void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const +{ + sal_uInt16 nPolygons = rPolyPoly.Count(); + for( sal_uInt16 n = 0; n < nPolygons; n++ ) + appendPolygon( rPolyPoly[n], rBuffer ); +} + +void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const +{ + for(auto const& rPolygon : rPolyPoly) + appendPolygon( rPolygon, rBuffer ); +} + +void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const +{ + sal_Int32 nValue = nLength; + if ( nLength < 0 ) + { + rBuffer.append( '-' ); + nValue = -nLength; + } + Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter, + Size( nValue, nValue ) ) ); + nValue = bVertical ? aSize.Height() : aSize.Width(); + if( pOutLength ) + *pOutLength = ((nLength < 0 ) ? -nValue : nValue); + + appendFixedInt( nValue, rBuffer ); +} + +void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const +{ + Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter, + Size( 1000, 1000 ) ) ); + fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0); + appendDouble( fLength, rBuffer, nPrecision ); +} + +bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const +{ + if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen()) + { + // dashed and non-degraded case, check for implementation limits of dash array + // in PDF reader apps (e.g. acroread) + if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10) + { + return false; + } + } + + if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin()) + { + // LineJoin used, ExtLineInfo required + return false; + } + + if(css::drawing::LineCap_BUTT != rInfo.GetLineCap()) + { + // LineCap used, ExtLineInfo required + return false; + } + + if( rInfo.GetStyle() == LineStyle::Dash ) + { + rBuffer.append( "[ " ); + if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case + { + appendMappedLength( rInfo.GetDashLen(), rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( rInfo.GetDistance(), rBuffer ); + rBuffer.append( ' ' ); + } + else + { + for( int n = 0; n < rInfo.GetDashCount(); n++ ) + { + appendMappedLength( rInfo.GetDashLen(), rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( rInfo.GetDistance(), rBuffer ); + rBuffer.append( ' ' ); + } + for( int m = 0; m < rInfo.GetDotCount(); m++ ) + { + appendMappedLength( rInfo.GetDotLen(), rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( rInfo.GetDistance(), rBuffer ); + rBuffer.append( ' ' ); + } + } + rBuffer.append( "] 0 d\n" ); + } + + if( rInfo.GetWidth() > 1 ) + { + appendMappedLength( rInfo.GetWidth(), rBuffer ); + rBuffer.append( " w\n" ); + } + else if( rInfo.GetWidth() == 0 ) + { + // "pixel" line + appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer ); + rBuffer.append( " w\n" ); + } + + return true; +} + +void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const +{ + if( nWidth <= 0 ) + return; + if( nDelta < 1 ) + nDelta = 1; + + rBuffer.append( "0 " ); + appendMappedLength( nY, rBuffer ); + rBuffer.append( " m\n" ); + for( sal_Int32 n = 0; n < nWidth; ) + { + n += nDelta; + appendMappedLength( n, rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( nDelta+nY, rBuffer ); + rBuffer.append( ' ' ); + n += nDelta; + appendMappedLength( n, rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( nY, rBuffer ); + rBuffer.append( " v " ); + if( n < nWidth ) + { + n += nDelta; + appendMappedLength( n, rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( nY-nDelta, rBuffer ); + rBuffer.append( ' ' ); + n += nDelta; + appendMappedLength( n, rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( nY, rBuffer ); + rBuffer.append( " v\n" ); + } + } + rBuffer.append( "S\n" ); +} + +void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer) +{ + appendDouble(rMatrix.get(0), rBuffer); + rBuffer.append(' '); + appendDouble(rMatrix.get(1), rBuffer); + rBuffer.append(' '); + appendDouble(rMatrix.get(2), rBuffer); + rBuffer.append(' '); + appendDouble(rMatrix.get(3), rBuffer); + rBuffer.append(' '); + appendPoint(Point(tools::Long(rMatrix.get(4)), tools::Long(rMatrix.get(5))), rBuffer); +} + +double PDFPage::getHeight() const +{ + double fRet = m_nPageHeight ? m_nPageHeight : 842; // default A4 height in inch/72, OK to use hardcoded value here? + + if (m_nUserUnit > 1) + { + fRet /= m_nUserUnit; + } + + return fRet; +} + +PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, + const css::uno::Reference< css::beans::XMaterialHolder >& xEnc, + PDFWriter& i_rOuterFace) + : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF), + m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ), + m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()), + m_nCurrentStructElement( 0 ), + m_bEmitStructure( true ), + m_nNextFID( 1 ), + m_aPDFBmpCache(utl::ConfigManager::IsFuzzing() ? 15 : + officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()), + m_nCurrentPage( -1 ), + m_nCatalogObject(0), + m_nSignatureObject( -1 ), + m_nSignatureContentOffset( 0 ), + m_nSignatureLastByteRangeNoOffset( 0 ), + m_nResourceDict( -1 ), + m_nFontDictObject( -1 ), + m_aContext(rContext), + m_aFile(m_aContext.URL), + m_bOpen(false), + m_DocDigest(::comphelper::HashType::MD5), + m_aCipher( nullptr ), + m_nKeyLength(0), + m_nRC4KeyLength(0), + m_bEncryptThisStream( false ), + m_nAccessPermissions(0), + m_bIsPDF_A1( false ), + m_bIsPDF_A2( false ), + m_bIsPDF_UA( false ), + m_bIsPDF_A3( false ), + m_rOuterFace( i_rOuterFace ) +{ + m_aStructure.emplace_back( ); + m_aStructure[0].m_nOwnElement = 0; + m_aStructure[0].m_nParentElement = 0; + //m_StructElementStack.push(0); + + Font aFont; + aFont.SetFamilyName( "Times" ); + aFont.SetFontSize( Size( 0, 12 ) ); + + // tdf#150786 use the same settings for widgets regardless of theme + m_aWidgetStyleSettings.SetStandardStyles(); + + GraphicsState aState; + aState.m_aMapMode = m_aMapMode; + aState.m_aFont = aFont; + m_aGraphicsStack.push_front( aState ); + + osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create); + if (aError != osl::File::E_None) + { + if (aError == osl::File::E_EXIST) + { + aError = m_aFile.open(osl_File_OpenFlag_Write); + if (aError == osl::File::E_None) + aError = m_aFile.setSize(0); + } + } + if (aError != osl::File::E_None) + return; + + m_bOpen = true; + + // setup DocInfo + setupDocInfo(); + + /* prepare the cypher engine, can be done in CTOR, free in DTOR */ + m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); + + if( xEnc.is() ) + prepareEncryption( xEnc ); + + if( m_aContext.Encryption.Encrypt() ) + { + // sanity check + if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE || + m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE || + m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH + ) + { + // the field lengths are invalid ? This was not setup by initEncryption. + // do not encrypt after all + m_aContext.Encryption.OValue.clear(); + m_aContext.Encryption.UValue.clear(); + OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" ); + } + else // setup key lengths + m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength ); + } + + // write header + OStringBuffer aBuffer( 20 ); + aBuffer.append( "%PDF-" ); + aBuffer.append(getPDFVersionStr(m_aContext.Version)); + // append something binary as comment (suggested in PDF Reference) + aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" ); + if( !writeBuffer( aBuffer ) ) + { + m_aFile.close(); + m_bOpen = false; + return; + } + + // insert outline root + m_aOutline.emplace_back( ); + + m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1); + if( m_bIsPDF_A1 ) + m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour + + m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2); + if( m_bIsPDF_A2 ) + m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7; + + m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3); + if( m_bIsPDF_A3 ) + m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7; + + if (m_aContext.UniversalAccessibilityCompliance) + { + m_bIsPDF_UA = true; + m_aContext.Tagged = true; + } + + if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 ) + SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 ); + else + SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy ); + + SetOutputSizePixel( Size( 640, 480 ) ); + SetMapMode(MapMode(MapUnit::MapMM)); +} + +PDFWriterImpl::~PDFWriterImpl() +{ + disposeOnce(); +} + +void PDFWriterImpl::dispose() +{ + if( m_aCipher ) + rtl_cipher_destroyARCFOUR( m_aCipher ); + m_aPages.clear(); + VirtualDevice::dispose(); +} + +bool PDFWriterImpl::ImplNewFont() const +{ + const ImplSVData* pSVData = ImplGetSVData(); + + if( mxFontCollection == pSVData->maGDIData.mxScreenFontList + || mxFontCache == pSVData->maGDIData.mxScreenFontCache ) + { + const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData(); + } + + return OutputDevice::ImplNewFont(); +} + +void PDFWriterImpl::setupDocInfo() +{ + std::vector< sal_uInt8 > aId; + m_aCreationDateString = PDFWriter::GetDateTime(); + computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString ); + if( m_aContext.Encryption.DocumentIdentifier.empty() ) + m_aContext.Encryption.DocumentIdentifier = aId; +} + +OString PDFWriter::GetDateTime() +{ + OStringBuffer aRet; + + TimeValue aTVal, aGMT; + oslDateTime aDT; + osl_getSystemTime(&aGMT); + osl_getLocalTimeFromSystemTime(&aGMT, &aTVal); + osl_getDateTimeFromTimeValue(&aTVal, &aDT); + + sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds; + + appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta); + + aRet.append("'"); + return aRet.makeStringAndClear(); +} + +void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier, + const vcl::PDFWriter::PDFDocInfo& i_rDocInfo, + const OString& i_rCString1, + const css::util::DateTime& rCreationMetaDate, + OString& o_rCString2 + ) +{ + o_rIdentifier.clear(); + + //build the document id + OString aInfoValuesOut; + OStringBuffer aID( 1024 ); + if( !i_rDocInfo.Title.isEmpty() ) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID); + if( !i_rDocInfo.Author.isEmpty() ) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID); + if( !i_rDocInfo.Subject.isEmpty() ) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID); + if( !i_rDocInfo.Keywords.isEmpty() ) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID); + if( !i_rDocInfo.Creator.isEmpty() ) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID); + if( !i_rDocInfo.Producer.isEmpty() ) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID); + + TimeValue aTVal, aGMT; + oslDateTime aDT; + aDT.NanoSeconds = rCreationMetaDate.NanoSeconds; + aDT.Seconds = rCreationMetaDate.Seconds; + aDT.Minutes = rCreationMetaDate.Minutes; + aDT.Hours = rCreationMetaDate.Hours; + aDT.Day = rCreationMetaDate.Day; + aDT.Month = rCreationMetaDate.Month; + aDT.Year = rCreationMetaDate.Year; + + osl_getSystemTime( &aGMT ); + osl_getLocalTimeFromSystemTime( &aGMT, &aTVal ); + OStringBuffer aCreationMetaDateString(64); + + // i59651: we fill the Metadata date string as well, if PDF/A is requested + // according to ISO 19005-1:2005 6.7.3 the date is corrected for + // local time zone offset UTC only, whereas Acrobat 8 seems + // to use the localtime notation only + // according to a recommendation in XMP Specification (Jan 2004, page 75) + // the Acrobat way seems the right approach + aCreationMetaDateString.append( + OStringChar(static_cast<char>('0' + ((aDT.Year/1000)%10)) ) + + OStringChar(static_cast<char>('0' + ((aDT.Year/100)%10)) ) + + OStringChar(static_cast<char>('0' + ((aDT.Year/10)%10)) ) + + OStringChar(static_cast<char>('0' + ((aDT.Year)%10)) ) + + "-" + + OStringChar(static_cast<char>('0' + ((aDT.Month/10)%10)) ) + + OStringChar(static_cast<char>('0' + ((aDT.Month)%10)) ) + + "-" + + OStringChar(static_cast<char>('0' + ((aDT.Day/10)%10)) ) + + OStringChar(static_cast<char>('0' + ((aDT.Day)%10)) ) + + "T" + + OStringChar(static_cast<char>('0' + ((aDT.Hours/10)%10)) ) + + OStringChar(static_cast<char>('0' + ((aDT.Hours)%10)) ) + + ":" + + OStringChar(static_cast<char>('0' + ((aDT.Minutes/10)%10)) ) + + OStringChar(static_cast<char>('0' + ((aDT.Minutes)%10)) ) + + ":" + + OStringChar(static_cast<char>('0' + ((aDT.Seconds/10)%10)) ) + + OStringChar(static_cast<char>('0' + ((aDT.Seconds)%10)) )); + + sal_uInt32 nDelta = 0; + if( aGMT.Seconds > aTVal.Seconds ) + { + nDelta = aGMT.Seconds-aTVal.Seconds; + aCreationMetaDateString.append( "-" ); + } + else if( aGMT.Seconds < aTVal.Seconds ) + { + nDelta = aTVal.Seconds-aGMT.Seconds; + aCreationMetaDateString.append( "+" ); + } + else + { + aCreationMetaDateString.append( "Z" ); + + } + if( nDelta ) + { + aCreationMetaDateString.append( + OStringChar(static_cast<char>('0' + ((nDelta/36000)%10)) ) + + OStringChar(static_cast<char>('0' + ((nDelta/3600)%10)) ) + + ":" + + OStringChar(static_cast<char>('0' + ((nDelta/600)%6)) ) + + OStringChar(static_cast<char>('0' + ((nDelta/60)%10)) )); + } + aID.append( i_rCString1.getStr(), i_rCString1.getLength() ); + + aInfoValuesOut = aID.makeStringAndClear(); + o_rCString2 = aCreationMetaDateString.makeStringAndClear(); + + ::comphelper::Hash aDigest(::comphelper::HashType::MD5); + aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT)); + aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength()); + //the binary form of the doc id is needed for encryption stuff + o_rIdentifier = aDigest.finalize(); +} + +/* i12626 methods */ +/* +check if the Unicode string must be encrypted or not, perform the requested task, +append the string as unicode hex, encrypted if needed + */ +inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer ) +{ + rOutBuffer.append( "<" ); + if( m_aContext.Encryption.Encrypt() ) + { + const sal_Unicode* pStr = rInString.getStr(); + sal_Int32 nLen = rInString.getLength(); + //prepare a unicode string, encrypt it + enableStringEncryption( nInObjectNumber ); + sal_uInt8 *pCopy = m_vEncryptionBuffer.data(); + sal_Int32 nChars = 2 + (nLen * 2); + m_vEncryptionBuffer.resize(nChars); + *pCopy++ = 0xFE; + *pCopy++ = 0xFF; + // we need to prepare a byte stream from the unicode string buffer + for( int i = 0; i < nLen; i++ ) + { + sal_Unicode aUnChar = pStr[i]; + *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 ); + *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 ); + } + //encrypt in place + rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars ); + //now append, hexadecimal (appendHex), the encrypted result + for(int i = 0; i < nChars; i++) + appendHex( m_vEncryptionBuffer[i], rOutBuffer ); + } + else + PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer); + rOutBuffer.append( ">" ); +} + +inline void PDFWriterImpl::appendLiteralStringEncrypt( std::string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer ) +{ + rOutBuffer.append( "(" ); + sal_Int32 nChars = rInString.size(); + //check for encryption, if ok, encrypt the string, then convert with appndLiteralString + if( m_aContext.Encryption.Encrypt() ) + { + m_vEncryptionBuffer.resize(nChars); + //encrypt the string in a buffer, then append it + enableStringEncryption( nInObjectNumber ); + rtl_cipher_encodeARCFOUR( m_aCipher, rInString.data(), nChars, m_vEncryptionBuffer.data(), nChars ); + appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer ); + } + else + appendLiteralString( rInString.data(), nChars , rOutBuffer ); + rOutBuffer.append( ")" ); +} + +void PDFWriterImpl::appendLiteralStringEncrypt( std::u16string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc ) +{ + OString aBufferString( OUStringToOString( rInString, nEnc ) ); + sal_Int32 nLen = aBufferString.getLength(); + OStringBuffer aBuf( nLen ); + const char* pT = aBufferString.getStr(); + + for( sal_Int32 i = 0; i < nLen; i++, pT++ ) + { + if( (*pT & 0x80) == 0 ) + aBuf.append( *pT ); + else + { + aBuf.append( '<' ); + appendHex( *pT, aBuf ); + aBuf.append( '>' ); + } + } + aBufferString = aBuf.makeStringAndClear(); + appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer); +} + +/* end i12626 methods */ + +void PDFWriterImpl::emitComment( const char* pComment ) +{ + OString aLine = OString::Concat("% ") + pComment + "\n"; + writeBuffer( aLine ); +} + +bool PDFWriterImpl::compressStream( SvMemoryStream* pStream ) +{ + if (!g_bDebugDisableCompression) + { + sal_uInt64 nEndPos = pStream->TellEnd(); + pStream->Seek( STREAM_SEEK_TO_BEGIN ); + ZCodec aCodec( 0x4000, 0x4000 ); + SvMemoryStream aStream; + aCodec.BeginCompression(); + aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos ); + aCodec.EndCompression(); + nEndPos = aStream.Tell(); + pStream->Seek( STREAM_SEEK_TO_BEGIN ); + aStream.Seek( STREAM_SEEK_TO_BEGIN ); + pStream->SetStreamSize( nEndPos ); + pStream->WriteBytes( aStream.GetData(), nEndPos ); + return true; + } + else + return false; +} + +void PDFWriterImpl::beginCompression() +{ + if (!g_bDebugDisableCompression) + { + m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 ); + m_pMemStream = std::make_unique<SvMemoryStream>(); + m_pCodec->BeginCompression(); + } +} + +void PDFWriterImpl::endCompression() +{ + if (!g_bDebugDisableCompression && m_pCodec) + { + m_pCodec->EndCompression(); + m_pCodec.reset(); + sal_uInt64 nLen = m_pMemStream->Tell(); + m_pMemStream->Seek( 0 ); + writeBufferBytes( m_pMemStream->GetData(), nLen ); + m_pMemStream.reset(); + } +} + +bool PDFWriterImpl::writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes ) +{ + if( ! m_bOpen ) // we are already down the drain + return false; + + if( ! nBytes ) // huh ? + return true; + + if( !m_aOutputStreams.empty() ) + { + m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END ); + m_aOutputStreams.front().m_pStream->WriteBytes( + pBuffer, sal::static_int_cast<std::size_t>(nBytes)); + return true; + } + + sal_uInt64 nWritten; + if( m_pCodec ) + { + m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) ); + nWritten = nBytes; + } + else + { + bool buffOK = true; + if( m_bEncryptThisStream ) + { + /* implement the encryption part of the PDF spec encryption algorithm 3.1 */ + m_vEncryptionBuffer.resize(nBytes); + if( buffOK ) + rtl_cipher_encodeARCFOUR( m_aCipher, + pBuffer, static_cast<sal_Size>(nBytes), + m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) ); + } + + const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data() : pBuffer; + m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes)); + + if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None) + nWritten = 0; + + if( nWritten != nBytes ) + { + m_aFile.close(); + m_bOpen = false; + } + } + + return nWritten == nBytes; +} + +void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation ) +{ + endPage(); + m_nCurrentPage = m_aPages.size(); + m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation ); + + const Fraction frac(m_aPages.back().m_nUserUnit, pointToPixel(1)); + m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac); + + m_aPages.back().beginStream(); + + // setup global graphics state + // linewidth is "1 pixel" by default + OStringBuffer aBuf( 16 ); + appendDouble( 72.0/double(GetDPIX()), aBuf ); + aBuf.append( " w\n" ); + writeBuffer( aBuf ); +} + +void PDFWriterImpl::endPage() +{ + if( m_aPages.empty() ) + return; + + // close eventual MC sequence + endStructureElementMCSeq(); + + // sanity check + if( !m_aOutputStreams.empty() ) + { + OSL_FAIL( "redirection across pages !!!" ); + m_aOutputStreams.clear(); // leak ! + m_aMapMode.SetOrigin( Point() ); + } + + m_aGraphicsStack.clear(); + m_aGraphicsStack.emplace_back( ); + + // this should pop the PDF graphics stack if necessary + updateGraphicsState(); + + m_aPages.back().endStream(); + + // reset the default font + Font aFont; + aFont.SetFamilyName( "Times" ); + aFont.SetFontSize( Size( 0, 12 ) ); + + m_aCurrentPDFState = m_aGraphicsStack.front(); + m_aGraphicsStack.front().m_aFont = aFont; + + for (auto & bitmap : m_aBitmaps) + { + if( ! bitmap.m_aBitmap.IsEmpty() ) + { + writeBitmapObject(bitmap); + bitmap.m_aBitmap = BitmapEx(); + } + } + for (auto & jpeg : m_aJPGs) + { + if( jpeg.m_pStream ) + { + writeJPG( jpeg ); + jpeg.m_pStream.reset(); + jpeg.m_aAlphaMask = AlphaMask(); + } + } + for (auto & item : m_aTransparentObjects) + { + if( item.m_pContentStream ) + { + writeTransparentObject(item); + item.m_pContentStream.reset(); + } + } + +} + +sal_Int32 PDFWriterImpl::createObject() +{ + m_aObjects.push_back( ~0U ); + return m_aObjects.size(); +} + +bool PDFWriterImpl::updateObject( sal_Int32 n ) +{ + if( ! m_bOpen ) + return false; + + sal_uInt64 nOffset = ~0U; + osl::File::RC aError = m_aFile.getPos(nOffset); + SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" ); + if (aError != osl::File::E_None) + { + m_aFile.close(); + m_bOpen = false; + } + m_aObjects[ n-1 ] = nOffset; + return aError == osl::File::E_None; +} + +#define CHECK_RETURN( x ) if( !(x) ) return 0 +#define CHECK_RETURN2( x ) if( !(x) ) return + +sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject ) +{ + if( nObject > 0 ) + { + OStringBuffer aLine( 1024 ); + + aLine.append( OString::number(nObject) + + " 0 obj\n" + "<</Nums[\n" ); + sal_Int32 nTreeItems = m_aStructParentTree.size(); + for( sal_Int32 n = 0; n < nTreeItems; n++ ) + { + aLine.append( OString::number(n) + " " + + m_aStructParentTree[n] + + "\n" ); + } + aLine.append( "]>>\nendobj\n\n" ); + CHECK_RETURN( updateObject( nObject ) ); + CHECK_RETURN( writeBuffer( aLine ) ); + } + return nObject; +} + +// every structure element already has a unique object id - just use it for ID +static OString GenerateID(sal_Int32 const nObjectId) +{ + return "id" + OString::number(nObjectId); +} + +sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject) +{ + // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15 + if (nObject < 0) + { + return nObject; + } + // the name tree entries must be sorted lexicographically. + std::map<OString, sal_Int32> ids; + for (auto n : m_StructElemObjsWithID) + { + ids.emplace(GenerateID(n), n); + } + OStringBuffer buf; + appendObjectID(nObject, buf); + buf.append("<</Names [\n"); + for (auto const& it : ids) + { + appendLiteralStringEncrypt(it.first, nObject, buf); + buf.append(" "); + appendObjectReference(it.second, buf); + buf.append("\n"); + } + buf.append("] >>\nendobj\n\n"); + + CHECK_RETURN( updateObject(nObject) ); + CHECK_RETURN( writeBuffer(buf) ); + + return nObject; +} + +const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr ) +{ + static constexpr auto aAttributeStrings = frozen::make_map<PDFWriter::StructAttribute, const char*>({ + { PDFWriter::Placement, "Placement" }, + { PDFWriter::WritingMode, "WritingMode" }, + { PDFWriter::SpaceBefore, "SpaceBefore" }, + { PDFWriter::SpaceAfter, "SpaceAfter" }, + { PDFWriter::StartIndent, "StartIndent" }, + { PDFWriter::EndIndent, "EndIndent" }, + { PDFWriter::TextIndent, "TextIndent" }, + { PDFWriter::TextAlign, "TextAlign" }, + { PDFWriter::Width, "Width" }, + { PDFWriter::Height, "Height" }, + { PDFWriter::BlockAlign, "BlockAlign" }, + { PDFWriter::InlineAlign, "InlineAlign" }, + { PDFWriter::LineHeight, "LineHeight" }, + { PDFWriter::BaselineShift, "BaselineShift" }, + { PDFWriter::TextDecorationType,"TextDecorationType" }, + { PDFWriter::ListNumbering, "ListNumbering" }, + { PDFWriter::RowSpan, "RowSpan" }, + { PDFWriter::ColSpan, "ColSpan" }, + { PDFWriter::Scope, "Scope" }, + { PDFWriter::Role, "Role" }, + { PDFWriter::RubyAlign, "RubyAlign" }, + { PDFWriter::RubyPosition, "RubyPosition" }, + { PDFWriter::Type, "Type" }, + { PDFWriter::Subtype, "Subtype" }, + { PDFWriter::LinkAnnotation, "LinkAnnotation" } + }); + + auto it = aAttributeStrings.find( eAttr ); + + if( it == aAttributeStrings.end() ) + SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr); + + return it != aAttributeStrings.end() ? it->second : ""; +} + +const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal ) +{ + static constexpr auto aValueStrings = frozen::make_map<PDFWriter::StructAttributeValue, const char*>({ + { PDFWriter::NONE, "None" }, + { PDFWriter::Block, "Block" }, + { PDFWriter::Inline, "Inline" }, + { PDFWriter::Before, "Before" }, + { PDFWriter::After, "After" }, + { PDFWriter::Start, "Start" }, + { PDFWriter::End, "End" }, + { PDFWriter::LrTb, "LrTb" }, + { PDFWriter::RlTb, "RlTb" }, + { PDFWriter::TbRl, "TbRl" }, + { PDFWriter::Center, "Center" }, + { PDFWriter::Justify, "Justify" }, + { PDFWriter::Auto, "Auto" }, + { PDFWriter::Middle, "Middle" }, + { PDFWriter::Normal, "Normal" }, + { PDFWriter::Underline, "Underline" }, + { PDFWriter::Overline, "Overline" }, + { PDFWriter::LineThrough,"LineThrough" }, + { PDFWriter::Row, "Row" }, + { PDFWriter::Column, "Column" }, + { PDFWriter::Both, "Both" }, + { PDFWriter::Pagination, "Pagination" }, + { PDFWriter::Layout, "Layout" }, + { PDFWriter::Page, "Page" }, + { PDFWriter::Background, "Background" }, + { PDFWriter::Header, "Header" }, + { PDFWriter::Footer, "Footer" }, + { PDFWriter::Watermark, "Watermark" }, + { PDFWriter::Rb, "rb" }, + { PDFWriter::Cb, "cb" }, + { PDFWriter::Pb, "pb" }, + { PDFWriter::Tv, "tv" }, + { PDFWriter::RStart, "Start" }, + { PDFWriter::RCenter, "Center" }, + { PDFWriter::REnd, "End" }, + { PDFWriter::RJustify, "Justify" }, + { PDFWriter::RDistribute,"Distribute" }, + { PDFWriter::RBefore, "Before" }, + { PDFWriter::RAfter, "After" }, + { PDFWriter::RWarichu, "Warichu" }, + { PDFWriter::RInline, "Inline" }, + { PDFWriter::Disc, "Disc" }, + { PDFWriter::Circle, "Circle" }, + { PDFWriter::Square, "Square" }, + { PDFWriter::Decimal, "Decimal" }, + { PDFWriter::UpperRoman, "UpperRoman" }, + { PDFWriter::LowerRoman, "LowerRoman" }, + { PDFWriter::UpperAlpha, "UpperAlpha" }, + { PDFWriter::LowerAlpha, "LowerAlpha" } + }); + + auto it = aValueStrings.find( eVal ); + + if( it == aValueStrings.end() ) + SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal); + + return it != aValueStrings.end() ? it->second : ""; +} + +static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt ) +{ + o_rLine.append( "/" ); + o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) ); + + if( i_rVal.eValue != PDFWriter::Invalid ) + { + o_rLine.append( "/" ); + o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) ); + } + else + { + // numerical value + o_rLine.append( " " ); + if( i_bIsFixedInt ) + appendFixedInt( i_rVal.nValue, o_rLine ); + else + o_rLine.append( i_rVal.nValue ); + } + o_rLine.append( "\n" ); +} + +template<typename T> +void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot) +{ + // update struct parent of link + OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R"); + m_aStructParentTree.push_back( aStructParentEntry ); + rAnnot.m_nStructParent = m_aStructParentTree.size()-1; + sal_Int32 const nAnnotObj(rAnnot.m_nObject); + i_rEle.m_aKids.emplace_back(ObjReferenceObj{nAnnotObj}); +} + +OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) +{ + // create layout, list and table attribute sets + OStringBuffer aLayout(256), aList(64), aTable(64); + OStringBuffer aPrintField; + for (auto const& attribute : i_rEle.m_aAttributes) + { + if( attribute.first == PDFWriter::ListNumbering ) + appendStructureAttributeLine( attribute.first, attribute.second, aList, true ); + else if (attribute.first == PDFWriter::Role) + { + appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true); + } + else if( attribute.first == PDFWriter::RowSpan || + attribute.first == PDFWriter::ColSpan || + attribute.first == PDFWriter::Scope) + { + appendStructureAttributeLine( attribute.first, attribute.second, aTable, false ); + } + else if( attribute.first == PDFWriter::LinkAnnotation ) + { + sal_Int32 nLink = attribute.second.nValue; + std::map< sal_Int32, sal_Int32 >::const_iterator link_it = + m_aLinkPropertyMap.find( nLink ); + if( link_it != m_aLinkPropertyMap.end() ) + nLink = link_it->second; + if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() ) + { + AppendAnnotKid(i_rEle, m_aLinks[nLink]); + } + else + { + OSL_FAIL( "unresolved link id for Link structure" ); + SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure"); + if (g_bDebugDisableCompression) + { + OString aLine = "unresolved link id " + + OString::number( nLink ) + + " for Link structure"; + emitComment( aLine.getStr() ); + } + } + } + else + appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true ); + } + if( ! i_rEle.m_aBBox.IsEmpty() ) + { + aLayout.append( "/BBox[" ); + appendFixedInt( i_rEle.m_aBBox.Left(), aLayout ); + aLayout.append( " " ); + appendFixedInt( i_rEle.m_aBBox.Top(), aLayout ); + aLayout.append( " " ); + appendFixedInt( i_rEle.m_aBBox.Right(), aLayout ); + aLayout.append( " " ); + appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout ); + aLayout.append( "]\n" ); + } + + OStringBuffer aRet(256); + bool isArray(false); + if (1 < (aLayout.isEmpty() ? 0 : 1) + (aList.isEmpty() ? 0 : 1) + + (aPrintField.isEmpty() ? 0 : 1) + (aTable.isEmpty() ? 0 : 1)) + { + isArray = true; + aRet.append(" ["); + } + auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf) + { + aRet.append(" <</O"); + aRet.append(pName); + aRet.append(rBuf); + aRet.append(">>"); + }; + if( !aLayout.isEmpty() ) + { + WriteAttrs("/Layout", aLayout); + } + if( !aList.isEmpty() ) + { + WriteAttrs("/List", aList); + } + if (!aPrintField.isEmpty()) + { + WriteAttrs("/PrintField", aPrintField); + } + if( !aTable.isEmpty() ) + { + WriteAttrs("/Table", aTable); + } + + if (isArray) + { + aRet.append( " ]" ); + } + return aRet.makeStringAndClear(); +} + +sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) +{ + assert(rEle.m_nOwnElement == 0 || rEle.m_oType); + if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root + // do not emit NonStruct and its children + && *rEle.m_oType == PDFWriter::NonStructElement) + { + return 0; + } + + for (auto const& child : rEle.m_aChildren) + { + if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() ) + { + PDFStructureElement& rChild = m_aStructure[ child ]; + if (*rChild.m_oType != PDFWriter::NonStructElement) + { + if( rChild.m_nParentElement == rEle.m_nOwnElement ) + emitStructure( rChild ); + else + { + OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" ); + SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child); + } + } + } + else + { + OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" ); + SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child); + } + } + + OStringBuffer aLine( 512 ); + aLine.append( + OString::number(rEle.m_nObject) + + " 0 obj\n" + "<</Type" ); + sal_Int32 nParentTree = -1; + sal_Int32 nIDTree = -1; + if( rEle.m_nOwnElement == rEle.m_nParentElement ) + { + nParentTree = createObject(); + CHECK_RETURN( nParentTree ); + aLine.append( "/StructTreeRoot\n" + "/ParentTree " + + OString::number(nParentTree) + + " 0 R\n" ); + if( ! m_aRoleMap.empty() ) + { + aLine.append( "/RoleMap<<" ); + for (auto const& role : m_aRoleMap) + { + aLine.append( "/" + role.first + "/" + role.second + "\n" ); + } + aLine.append( ">>\n" ); + } + if (!m_StructElemObjsWithID.empty()) + { + nIDTree = createObject(); + aLine.append("/IDTree "); + appendObjectReference(nIDTree, aLine); + aLine.append("\n"); + } + } + else + { + aLine.append( "/StructElem\n" + "/S/" ); + if( !rEle.m_aAlias.isEmpty() ) + aLine.append( rEle.m_aAlias ); + else + aLine.append( getStructureTag(*rEle.m_oType) ); + if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end()) + { + aLine.append("\n/ID "); + appendLiteralStringEncrypt(GenerateID(rEle.m_nObject), rEle.m_nObject, aLine); + } + aLine.append( + "\n" + "/P " + + OString::number(m_aStructure[ rEle.m_nParentElement ].m_nObject) + + " 0 R\n" + "/Pg " + + OString::number(rEle.m_nFirstPageObject) + + " 0 R\n" ); + if( !rEle.m_aActualText.isEmpty() ) + { + aLine.append( "/ActualText" ); + appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine ); + aLine.append( "\n" ); + } + if( !rEle.m_aAltText.isEmpty() ) + { + aLine.append( "/Alt" ); + appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine ); + aLine.append( "\n" ); + } + } + if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) ) + { + OString aAttribs = emitStructureAttributes( rEle ); + if( !aAttribs.isEmpty() ) + { + aLine.append( "/A" + aAttribs + "\n" ); + } + } + if( !rEle.m_aLocale.Language.isEmpty() ) + { + /* PDF allows only RFC 3066, which is only partly BCP 47 and does not + * include script tags and others. + * http://pdf.editme.com/pdfua-naturalLanguageSpecification + * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886 + * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification + * */ + LanguageTag aLanguageTag( rEle.m_aLocale); + OUString aLanguage, aScript, aCountry; + aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry); + if (!aLanguage.isEmpty()) + { + OUStringBuffer aLocBuf( 16 ); + aLocBuf.append( aLanguage ); + if( !aCountry.isEmpty() ) + { + aLocBuf.append( "-" + aCountry ); + } + aLine.append( "/Lang" ); + appendLiteralStringEncrypt( aLocBuf, rEle.m_nObject, aLine ); + aLine.append( "\n" ); + } + } + if (!rEle.m_AnnotIds.empty()) + { + for (auto const id : rEle.m_AnnotIds) + { + auto const it(m_aLinkPropertyMap.find(id)); + assert(it != m_aLinkPropertyMap.end()); + + if (*rEle.m_oType == PDFWriter::Form) + { + assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size()); + AppendAnnotKid(rEle, m_aWidgets[it->second]); + } + else + { + assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size()); + AppendAnnotKid(rEle, m_aScreens[it->second]); + } + } + } + if( ! rEle.m_aKids.empty() ) + { + unsigned int i = 0; + aLine.append( "/K[" ); + for (auto const& rKid : rEle.m_aKids) + { + if (std::holds_alternative<ObjReference>(rKid)) + { + ObjReference const& rObj(std::get<ObjReference>(rKid)); + appendObjectReference(rObj.nObject, aLine); + aLine.append( ( (i & 15) == 15 ) ? "\n" : " " ); + } + else if (std::holds_alternative<ObjReferenceObj>(rKid)) + { + ObjReferenceObj const& rObj(std::get<ObjReferenceObj>(rKid)); + aLine.append("<</Type/OBJR/Obj "); + appendObjectReference(rObj.nObject, aLine); + aLine.append(">>\n"); + } + else + { + assert(std::holds_alternative<MCIDReference>(rKid)); + MCIDReference const& rMCID(std::get<MCIDReference>(rKid)); + if (rMCID.nPageObj == rEle.m_nFirstPageObject) + { + aLine.append(OString::number(rMCID.nMCID) + " "); + } + else + { + aLine.append("<</Type/MCR/Pg "); + appendObjectReference(rMCID.nPageObj, aLine); + aLine.append(" /MCID " + OString::number(rMCID.nMCID) + ">>\n"); + } + } + ++i; + } + aLine.append( "]\n" ); + } + aLine.append( ">>\nendobj\n\n" ); + + CHECK_RETURN( updateObject( rEle.m_nObject ) ); + CHECK_RETURN( writeBuffer( aLine ) ); + + CHECK_RETURN( emitStructParentTree( nParentTree ) ); + CHECK_RETURN( emitStructIDTree(nIDTree) ); + + return rEle.m_nObject; +} + +bool PDFWriterImpl::emitGradients() +{ + for (auto const& gradient : m_aGradients) + { + if ( !writeGradientFunction( gradient ) ) return false; + } + return true; +} + +bool PDFWriterImpl::emitTilings() +{ + OStringBuffer aTilingObj( 1024 ); + + for (auto & tiling : m_aTilings) + { + SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" ); + if( ! tiling.m_pTilingStream ) + continue; + + aTilingObj.setLength( 0 ); + + if (g_bDebugDisableCompression) + { + emitComment( "PDFWriterImpl::emitTilings" ); + } + + sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left()); + sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top()); + sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth()); + sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight()); + if( tiling.m_aCellSize.Width() == 0 ) + tiling.m_aCellSize.setWidth( nW ); + if( tiling.m_aCellSize.Height() == 0 ) + tiling.m_aCellSize.setHeight( nH ); + + bool bDeflate = compressStream( tiling.m_pTilingStream.get() ); + sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd(); + tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN ); + + // write pattern object + aTilingObj.append( + OString::number(tiling.m_nObject) + + " 0 obj\n" + "<</Type/Pattern/PatternType 1\n" + "/PaintType 1\n" + "/TilingType 2\n" + "/BBox[" ); + appendFixedInt( nX, aTilingObj ); + aTilingObj.append( ' ' ); + appendFixedInt( nY, aTilingObj ); + aTilingObj.append( ' ' ); + appendFixedInt( nX+nW, aTilingObj ); + aTilingObj.append( ' ' ); + appendFixedInt( nY+nH, aTilingObj ); + aTilingObj.append( "]\n" + "/XStep " ); + appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj ); + aTilingObj.append( "\n" + "/YStep " ); + appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj ); + aTilingObj.append( "\n" ); + if( tiling.m_aTransform.matrix[0] != 1.0 || + tiling.m_aTransform.matrix[1] != 0.0 || + tiling.m_aTransform.matrix[3] != 0.0 || + tiling.m_aTransform.matrix[4] != 1.0 || + tiling.m_aTransform.matrix[2] != 0.0 || + tiling.m_aTransform.matrix[5] != 0.0 ) + { + aTilingObj.append( "/Matrix [" ); + // TODO: scaling, mirroring on y, etc + appendDouble( tiling.m_aTransform.matrix[0], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( tiling.m_aTransform.matrix[1], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( tiling.m_aTransform.matrix[3], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( tiling.m_aTransform.matrix[4], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( tiling.m_aTransform.matrix[2], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( tiling.m_aTransform.matrix[5], aTilingObj ); + aTilingObj.append( "]\n" ); + } + aTilingObj.append( "/Resources" ); + tiling.m_aResources.append( aTilingObj, getFontDictObject() ); + if( bDeflate ) + aTilingObj.append( "/Filter/FlateDecode" ); + aTilingObj.append( "/Length " + + OString::number(static_cast<sal_Int32>(nTilingStreamSize)) + + ">>\nstream\n" ); + if ( !updateObject( tiling.m_nObject ) ) return false; + if ( !writeBuffer( aTilingObj ) ) return false; + checkAndEnableStreamEncryption( tiling.m_nObject ); + bool written = writeBufferBytes( tiling.m_pTilingStream->GetData(), nTilingStreamSize ); + tiling.m_pTilingStream.reset(); + if( !written ) + return false; + disableStreamEncryption(); + aTilingObj.setLength( 0 ); + aTilingObj.append( "\nendstream\nendobj\n\n" ); + if ( !writeBuffer( aTilingObj ) ) return false; + } + return true; +} + +sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject) +{ + if( !pFD ) + return 0; + const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont(); + + OStringBuffer aLine( 1024 ); + + if( nFontObject <= 0 ) + nFontObject = createObject(); + CHECK_RETURN( updateObject( nFontObject ) ); + aLine.append( + OString::number(nFontObject) + + " 0 obj\n" + "<</Type/Font/Subtype/Type1/BaseFont/" ); + appendName( rBuildinFont.m_pPSName, aLine ); + aLine.append( "\n" ); + if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 ) + aLine.append( "/Encoding/WinAnsiEncoding\n" ); + aLine.append( ">>\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + return nFontObject; +} + +namespace +{ +// Translate units from TT to PS (standard 1/1000) +int XUnits(int nUPEM, int n) { return (n * 1000) / nUPEM; } +} + +std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed ) +{ + std::map< sal_Int32, sal_Int32 > aRet; + + if (g_bDebugDisableCompression) + emitComment("PDFWriterImpl::emitSystemFont"); + + FontSubsetInfo aInfo; + // fill in dummy values + aInfo.m_nAscent = 1000; + aInfo.m_nDescent = 200; + aInfo.m_nCapHeight = 1000; + aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) ); + aInfo.m_aPSName = pFace->GetFamilyName(); + + sal_Int32 pWidths[256] = { 0 }; + const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance; + auto nUPEM = pFace->UnitsPerEm(); + for( sal_Ucs c = 32; c < 256; c++ ) + { + sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c); + pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false)); + } + + // We are interested only in filling aInfo + sal_GlyphId aGlyphIds[] = { 0 }; + sal_uInt8 pEncoding[] = { 0 }; + std::vector<sal_uInt8> aBuffer; + pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo); + + // write font descriptor + sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0 ); + if( nFontDescriptor ) + { + // write font object + sal_Int32 nObject = createObject(); + if( updateObject( nObject ) ) + { + OStringBuffer aLine( 1024 ); + aLine.append( + OString::number(nObject) + + " 0 obj\n" + "<</Type/Font/Subtype/TrueType" + "/BaseFont/" ); + appendName( aInfo.m_aPSName, aLine ); + aLine.append( "\n" ); + if (!pFace->IsMicrosoftSymbolEncoded()) + aLine.append( "/Encoding/WinAnsiEncoding\n" ); + aLine.append( "/FirstChar 32 /LastChar 255\n" + "/Widths[" ); + for( int i = 32; i < 256; i++ ) + { + aLine.append( pWidths[i] ); + aLine.append( ((i&15) == 15) ? "\n" : " " ); + } + aLine.append( "]\n" + "/FontDescriptor " + + OString::number( nFontDescriptor ) + + " 0 R>>\n" + "endobj\n\n" ); + writeBuffer( aLine ); + + aRet[ rEmbed.m_nNormalFontID ] = nObject; + } + } + + return aRet; +} + +namespace +{ +uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths, + sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex, + sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits, + sal_Int32& nToUnicodeStream) +{ + rCodeUnits.reserve(256); + + // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine + pWidths[0] = 0; + + uint32_t nGlyphs = 1; + for (auto const& item : rSubset.m_aMapping) + { + sal_uInt8 nEnc = item.second.getGlyphId(); + + SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", + "duplicate glyph"); + SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding"); + + pGlyphIds[nEnc] = item.first; + pEncoding[nEnc] = nEnc; + pEncToUnicodeIndex[nEnc] = static_cast<sal_Int32>(rCodeUnits.size()); + pCodeUnitsPerGlyph[nEnc] = item.second.countCodes(); + pWidths[nEnc] = item.second.getGlyphWidth(); + for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++) + rCodeUnits.push_back(item.second.getCode(n)); + if (item.second.getCode(0)) + nToUnicodeStream = 1; + if (nGlyphs < 256) + nGlyphs++; + else + OSL_FAIL("too many glyphs for subset"); + } + + return nGlyphs; +} +} + +bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace, + const FontSubset& rType3Font, + std::map<sal_Int32, sal_Int32>& rFontIDToObject) +{ + if (g_bDebugDisableCompression) + emitComment("PDFWriterImpl::emitType3Font"); + + const auto& rColorPalettes = pFace->GetColorPalettes(); + + FontSubsetInfo aSubsetInfo; + sal_GlyphId pTempGlyphIds[] = { 0 }; + sal_uInt8 pTempEncoding[] = { 0 }; + std::vector<sal_uInt8> aBuffer; + pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo); + + for (auto& rSubset : rType3Font.m_aSubsets) + { + sal_GlyphId pGlyphIds[256] = {}; + sal_Int32 pWidths[256]; + sal_uInt8 pEncoding[256] = {}; + sal_Int32 pEncToUnicodeIndex[256] = {}; + sal_Int32 pCodeUnitsPerGlyph[256] = {}; + std::vector<sal_Ucs> aCodeUnits; + sal_Int32 nToUnicodeStream = 0; + + // fill arrays and prepare encoding index map + auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex, + pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream); + + // write font descriptor + sal_Int32 nFontDescriptor = 0; + if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4) + nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0); + + if (nToUnicodeStream) + nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph, + pEncToUnicodeIndex, nGlyphs); + + // write font object + sal_Int32 nFontObject = createObject(); + if (!updateObject(nFontObject)) + return false; + + OStringBuffer aLine(1024); + aLine.append( + OString::number(nFontObject) + + " 0 obj\n" + "<</Type/Font/Subtype/Type3/Name/"); + appendName(aSubsetInfo.m_aPSName, aLine); + + aLine.append( + "\n/FontBBox[" + // note: Top and Bottom are reversed in VCL and PDF rectangles + + OString::number(aSubsetInfo.m_aFontBBox.Left()) + + " " + + OString::number(aSubsetInfo.m_aFontBBox.Top()) + + " " + + OString::number(aSubsetInfo.m_aFontBBox.Right()) + + " " + + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1) + + "]\n"); + + // tdf#155610 + // Adobe Acrobat does not seem to like certain UPEMs, so instead of + // setting the FontMatrix scale relative to the UPEM, we always set to + // 0.001 (1000 UPEM) and scale everything if the font’s UPEM is + // different. + double fScale = 1000. / pFace->UnitsPerEm(); + + aLine.append("/FontMatrix[0.001 0 0 0.001 0 0]\n"); + + sal_Int32 pGlyphStreams[256] = {}; + aLine.append("/CharProcs<<\n"); + for (auto i = 1u; i < nGlyphs; i++) + { + auto nStream = createObject(); + aLine.append("/" + + pFace->GetGlyphName(pGlyphIds[i], true) + + " " + + OString::number(nStream) + + " 0 R\n"); + pGlyphStreams[i] = nStream; + } + aLine.append(">>\n" + + "/Encoding<</Type/Encoding/Differences[1"); + for (auto i = 1u; i < nGlyphs; i++) + aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true)); + aLine.append("]>>\n" + + "/FirstChar 0\n" + "/LastChar " + + OString::number(nGlyphs - 1) + + "\n" + + "/Widths["); + for (auto i = 0u; i < nGlyphs; i++) + { + appendDouble(pWidths[i] * fScale, aLine); + aLine.append(" "); + } + aLine.append("]\n"); + + if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4) + { + aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n"); + } + + auto nResources = createObject(); + aLine.append("/Resources " + OString::number(nResources) + " 0 R\n"); + + if (nToUnicodeStream) + { + aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n"); + } + + aLine.append(">>\n" + "endobj\n\n"); + + if (!writeBuffer(aLine)) + return false; + + std::set<sal_Int32> aUsedFonts; + std::list<BitmapEmit> aUsedBitmaps; + std::map<sal_uInt8, sal_Int32> aUsedAlpha; + ResourceDict aResourceDict; + std::list<StreamRedirect> aOutputStreams; + + // Scale for glyph outlines. + double fScaleX = (GetDPIX() / 72.) * fScale; + double fScaleY = (GetDPIY() / 72.) * fScale; + + for (auto i = 1u; i < nGlyphs; i++) + { + auto nStream = pGlyphStreams[i]; + if (!updateObject(nStream)) + return false; + OStringBuffer aContents(1024); + appendDouble(pWidths[i] * fScale, aContents); + aContents.append(" 0 d0\n"); + + const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second; + const auto& rLayers = rGlyph.getColorLayers(); + for (const auto& rLayer : rLayers) + { + aUsedFonts.insert(rLayer.m_nFontID); + + aContents.append("q "); + // 0xFFFF is a special value means foreground color. + if (rLayer.m_nColorIndex != 0xFFFF) + { + auto& rPalette = rColorPalettes[0]; + auto aColor(rPalette[rLayer.m_nColorIndex]); + appendNonStrokingColor(aColor, aContents); + aContents.append(" "); + if (aColor.GetAlpha() != 0xFF + && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4) + { + auto nAlpha = aColor.GetAlpha(); + OStringBuffer aName(16); + aName.append("GS"); + appendHex(nAlpha, aName); + + aContents.append("/" + aName + " gs "); + + if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end()) + { + auto nObject = createObject(); + aUsedAlpha[nAlpha] = nObject; + pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(), + nObject, aResourceDict, aOutputStreams); + } + } + } + aContents.append( + "BT " + "/F" + OString::number(rLayer.m_nFontID) + " "); + appendDouble(pFace->UnitsPerEm() * fScale, aContents); + aContents.append( + " Tf " + "<"); + appendHex(rLayer.m_nSubsetGlyphID, aContents); + aContents.append( + ">Tj " + "ET " + "Q\n"); + } + + tools::Rectangle aRect; + const auto& rBitmapData = rGlyph.getColorBitmap(aRect); + if (!rBitmapData.empty()) + { + SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(), + StreamMode::READ); + vcl::PngImageReader aReader(aStream); + + // When rendering an image with an alpha mask during PDF + // export, the alpha mask needs to be inverted + BitmapEx aBitmapEx = aReader.read(); + if ( aBitmapEx.IsAlpha()) + { + AlphaMask aAlpha = aBitmapEx.GetAlphaMask(); + aAlpha.Invert(); + aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha); + } + + auto aBitmapEmit = createBitmapEmit(aBitmapEx, Graphic(), + aUsedBitmaps, aResourceDict, aOutputStreams); + + auto nObject = aBitmapEmit.m_aReferenceXObject.getObject(); + aContents.append("q "); + appendDouble(aRect.GetWidth() * fScale, aContents); + aContents.append(" 0 0 "); + appendDouble(aRect.GetHeight() * fScale, aContents); + aContents.append( + + " " + + OString::number(aRect.getX()) + + " " + + OString::number(aRect.getY()) + + " cm " + "/Im" + + OString::number(nObject) + + " Do Q\n"); + } + + const auto& rOutline = rGlyph.getOutline(); + if (rOutline.count()) + { + aContents.append("q "); + appendDouble(fScaleX, aContents); + aContents.append(" 0 0 "); + appendDouble(fScaleY, aContents); + aContents.append(" 0 "); + appendDouble(m_aPages.back().getHeight() * -fScaleY, aContents, 3); + aContents.append(" cm\n"); + m_aPages.back().appendPolyPolygon(rOutline, aContents); + aContents.append("f\n" + "Q\n"); + } + + aLine.setLength(0); + aLine.append(OString::number(nStream) + + " 0 obj\n<</Length " + + OString::number(aContents.getLength()) + + ">>\nstream\n"); + if (!writeBuffer(aLine)) + return false; + if (!writeBuffer(aContents)) + return false; + aLine.setLength(0); + aLine.append("endstream\nendobj\n\n"); + if (!writeBuffer(aLine)) + return false; + } + + // write font dict + sal_Int32 nFontDict = 0; + if (!aUsedFonts.empty()) + { + nFontDict = createObject(); + aLine.setLength(0); + aLine.append(OString::number(nFontDict) + " 0 obj\n<<"); + for (auto nFontID : aUsedFonts) + { + aLine.append("/F" + + OString::number(nFontID) + + " " + + OString::number(rFontIDToObject[nFontID]) + + " 0 R"); + } + aLine.append(">>\nendobj\n\n"); + if (!updateObject(nFontDict)) + return false; + if (!writeBuffer(aLine)) + return false; + } + + // write ExtGState objects + if (!aUsedAlpha.empty()) + { + for (const auto & [ nAlpha, nObject ] : aUsedAlpha) + { + aLine.setLength(0); + aLine.append(OString::number(nObject) + " 0 obj\n<<"); + if (m_bIsPDF_A1) + { + aLine.append("/CA 1.0/ca 1.0"); + m_aErrors.insert(PDFWriter::Warning_Transparency_Omitted_PDFA); + } + else + { + aLine.append("/CA "); + appendDouble(nAlpha / 255., aLine); + aLine.append("/ca "); + appendDouble(nAlpha / 255., aLine); + } + aLine.append(">>\nendobj\n\n"); + if (!updateObject(nObject)) + return false; + if (!writeBuffer(aLine)) + return false; + } + } + + // write bitmap objects + for (auto& aBitmap : aUsedBitmaps) + writeBitmapObject(aBitmap); + + // write resources dict + aLine.setLength(0); + aLine.append(OString::number(nResources) + " 0 obj\n"); + aResourceDict.append(aLine, nFontDict); + aLine.append("endobj\n\n"); + if (!updateObject(nResources)) + return false; + if (!writeBuffer(aLine)) + return false; + + rFontIDToObject[rSubset.m_nFontID] = nFontObject; + } + + return true; +} + +typedef int ThreeInts[3]; +static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen, + ThreeInts& rSegmentLengths ) +{ + if( !pFontBytes || (nByteLen < 0) ) + return false; + const unsigned char* pPtr = pFontBytes; + const unsigned char* pEnd = pFontBytes + nByteLen; + + for(int & rSegmentLength : rSegmentLengths) { + // read segment1 header + if( pPtr+6 >= pEnd ) + return false; + if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) ) + return false; + const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2]; + if( nLen <= 0) + return false; + rSegmentLength = nLen; + pPtr += nLen + 6; + } + + // read segment-end header + if( pPtr+2 >= pEnd ) + return false; + if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) ) + return false; + + return true; +} + +static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer ) +{ + if( nSubsetID ) + { + for( int i = 0; i < 6; i++ ) + { + int nOffset = nSubsetID % 26; + nSubsetID /= 26; + rBuffer.append( static_cast<char>('A'+nOffset) ); + } + rBuffer.append( '+' ); + } + appendName( rPSName, rBuffer ); +} + +sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding, + const std::vector<sal_Ucs>& rCodeUnits, + const sal_Int32* pCodeUnitsPerGlyph, + const sal_Int32* pEncToUnicodeIndex, + uint32_t nGlyphs ) +{ + int nMapped = 0; + for (auto n = 0u; n < nGlyphs; ++n) + if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]]) + nMapped++; + + if( nMapped == 0 ) + return 0; + + sal_Int32 nStream = createObject(); + CHECK_RETURN( updateObject( nStream ) ); + + OStringBuffer aContents( 1024 ); + aContents.append( + "/CIDInit/ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo<<\n" + "/Registry (Adobe)\n" + "/Ordering (UCS)\n" + "/Supplement 0\n" + ">> def\n" + "/CMapName/Adobe-Identity-UCS def\n" + "/CMapType 2 def\n" + "1 begincodespacerange\n" + "<00> <FF>\n" + "endcodespacerange\n" + ); + int nCount = 0; + for (auto n = 0u; n < nGlyphs; ++n) + { + if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]]) + { + if( (nCount % 100) == 0 ) + { + if( nCount ) + aContents.append( "endbfchar\n" ); + aContents.append( OString::number(static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) ) + + " beginbfchar\n" ); + } + aContents.append( '<' ); + appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents ); + aContents.append( "> <" ); + // TODO: handle code points>U+FFFF + sal_Int32 nIndex = pEncToUnicodeIndex[n]; + for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ ) + { + appendHex( static_cast<sal_Int8>(rCodeUnits[nIndex + j] / 256), aContents ); + appendHex( static_cast<sal_Int8>(rCodeUnits[nIndex + j] & 255), aContents ); + } + aContents.append( ">\n" ); + nCount++; + } + } + aContents.append( "endbfchar\n" + "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end\n" ); + SvMemoryStream aStream; + if (!g_bDebugDisableCompression) + { + ZCodec aCodec( 0x4000, 0x4000 ); + aCodec.BeginCompression(); + aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() ); + aCodec.EndCompression(); + } + + if (g_bDebugDisableCompression) + { + emitComment( "PDFWriterImpl::createToUnicodeCMap" ); + } + OStringBuffer aLine( 40 ); + + aLine.append( OString::number(nStream ) + " 0 obj\n<</Length " ); + sal_uInt64 nLen = 0; + if (!g_bDebugDisableCompression) + { + nLen = aStream.Tell(); + aStream.Seek( 0 ); + aLine.append( OString::number(nLen) + "/Filter/FlateDecode" ); + } + else + aLine.append( aContents.getLength() ); + aLine.append( ">>\nstream\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + checkAndEnableStreamEncryption( nStream ); + if (!g_bDebugDisableCompression) + { + CHECK_RETURN( writeBufferBytes( aStream.GetData(), nLen ) ); + } + else + { + CHECK_RETURN( writeBuffer( aContents ) ); + } + disableStreamEncryption(); + aLine.setLength( 0 ); + aLine.append( "\nendstream\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + return nStream; +} + +sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream ) +{ + OStringBuffer aLine( 1024 ); + // get font flags, see PDF reference 1.4 p. 358 + // possibly characters outside Adobe standard encoding + // so set Symbolic flag + sal_Int32 nFontFlags = (1<<2); + if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE ) + nFontFlags |= (1 << 6); + if( pFace->GetPitch() == PITCH_FIXED ) + nFontFlags |= 1; + if( pFace->GetFamilyType() == FAMILY_SCRIPT ) + nFontFlags |= (1 << 3); + else if( pFace->GetFamilyType() == FAMILY_ROMAN ) + nFontFlags |= (1 << 1); + + sal_Int32 nFontDescriptor = createObject(); + CHECK_RETURN( updateObject( nFontDescriptor ) ); + aLine.setLength( 0 ); + aLine.append( + OString::number(nFontDescriptor) + + " 0 obj\n" + "<</Type/FontDescriptor/FontName/" ); + appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine ); + aLine.append( "\n" + "/Flags " + + OString::number( nFontFlags ) + + "\n" + "/FontBBox[" + // note: Top and Bottom are reversed in VCL and PDF rectangles + + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) ) + + " " + + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) ) + + " " + + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) ) + + " " + + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) ) + + "]/ItalicAngle " ); + if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL ) + aLine.append( "-30" ); + else + aLine.append( "0" ); + aLine.append( "\n" + "/Ascent " + + OString::number( static_cast<sal_Int32>(rInfo.m_nAscent) ) + + "\n" + "/Descent " + + OString::number( static_cast<sal_Int32>(-rInfo.m_nDescent) ) + + "\n" + "/CapHeight " + + OString::number( static_cast<sal_Int32>(rInfo.m_nCapHeight) ) + // According to PDF reference 1.4 StemV is required + // seems a tad strange to me, but well ... + + "\n" + "/StemV 80\n" ); + if( nFontStream ) + { + aLine.append( "/FontFile" ); + switch( rInfo.m_nFontType ) + { + case FontType::SFNT_TTF: + aLine.append( '2' ); + break; + case FontType::TYPE1_PFA: + case FontType::TYPE1_PFB: + case FontType::ANY_TYPE1: + break; + default: + OSL_FAIL( "unknown fonttype in PDF font descriptor" ); + return 0; + } + aLine.append( " " + OString::number(nFontStream) + " 0 R\n" ); + } + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + + return nFontDescriptor; +} + +void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const +{ + for (auto const& item : m_aBuildinFontToObjectMap) + { + rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() ); + rDict.append( ' ' ); + rDict.append( item.second ); + rDict.append( " 0 R" ); + } +} + +bool PDFWriterImpl::emitFonts() +{ + OStringBuffer aLine( 1024 ); + + std::map< sal_Int32, sal_Int32 > aFontIDToObject; + + for (const auto & subset : m_aSubsets) + { + for (auto & s_subset :subset.second.m_aSubsets) + { + sal_GlyphId pGlyphIds[ 256 ] = {}; + sal_Int32 pWidths[ 256 ]; + sal_uInt8 pEncoding[ 256 ] = {}; + sal_Int32 pEncToUnicodeIndex[ 256 ] = {}; + sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {}; + std::vector<sal_Ucs> aCodeUnits; + sal_Int32 nToUnicodeStream = 0; + + // fill arrays and prepare encoding index map + auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex, + pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream); + + std::vector<sal_uInt8> aBuffer; + FontSubsetInfo aSubsetInfo; + const auto* pFace = subset.first; + if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo)) + { + // create font stream + if (g_bDebugDisableCompression) + { + emitComment( "PDFWriterImpl::emitFonts" ); + } + sal_Int32 nFontStream = createObject(); + sal_Int32 nStreamLengthObject = createObject(); + if ( !updateObject( nFontStream ) ) return false; + aLine.setLength( 0 ); + aLine.append( OString::number(nFontStream) + + " 0 obj\n" + "<</Length " + + OString::number( nStreamLengthObject ) ); + if (!g_bDebugDisableCompression) + aLine.append( " 0 R" + "/Filter/FlateDecode" + "/Length1 " ); + else + aLine.append( " 0 R" + "/Length1 " ); + + sal_uInt64 nStartPos = 0; + if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF ) + { + aLine.append( OString::number(static_cast<sal_Int32>(aBuffer.size())) + + ">>\n" + "stream\n" ); + if ( !writeBuffer( aLine ) ) return false; + if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false; + + // copy font file + beginCompression(); + checkAndEnableStreamEncryption( nFontStream ); + if (!writeBufferBytes(aBuffer.data(), aBuffer.size())) + return false; + } + else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT) + { + // TODO: implement + OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" ); + } + else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA? + { + // get the PFB-segment lengths + ThreeInts aSegmentLengths = {0,0,0}; + getPfbSegmentLengths(aBuffer.data(), static_cast<int>(aBuffer.size()), aSegmentLengths); + // the lengths below are mandatory for PDF-exported Type1 fonts + // because the PFB segment headers get stripped! WhyOhWhy. + aLine.append( OString::number(static_cast<sal_Int32>(aSegmentLengths[0]) ) + + "/Length2 " + + OString::number( static_cast<sal_Int32>(aSegmentLengths[1]) ) + + "/Length3 " + + OString::number( static_cast<sal_Int32>(aSegmentLengths[2]) ) + + ">>\n" + "stream\n" ); + if ( !writeBuffer( aLine ) ) return false; + if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false; + + // emit PFB-sections without section headers + beginCompression(); + checkAndEnableStreamEncryption( nFontStream ); + if ( !writeBufferBytes( &aBuffer[6], aSegmentLengths[0] ) ) return false; + if ( !writeBufferBytes( &aBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false; + if ( !writeBufferBytes( &aBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false; + } + else + { + SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType)); + aLine.append( "0 >>\nstream\n" ); + } + + endCompression(); + disableStreamEncryption(); + + sal_uInt64 nEndPos = 0; + if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false; + // end the stream + aLine.setLength( 0 ); + aLine.append( "\nendstream\nendobj\n\n" ); + if ( !writeBuffer( aLine ) ) return false; + + // emit stream length object + if ( !updateObject( nStreamLengthObject ) ) return false; + aLine.setLength( 0 ); + aLine.append( OString::number(nStreamLengthObject) + + " 0 obj\n" + + OString::number( static_cast<sal_Int64>(nEndPos-nStartPos) ) + + "\nendobj\n\n" ); + if ( !writeBuffer( aLine ) ) return false; + + // write font descriptor + sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream ); + + if( nToUnicodeStream ) + nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs ); + + sal_Int32 nFontObject = createObject(); + if ( !updateObject( nFontObject ) ) return false; + aLine.setLength( 0 ); + aLine.append( OString::number(nFontObject) + " 0 obj\n" ); + aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ? + "<</Type/Font/Subtype/Type1/BaseFont/" : + "<</Type/Font/Subtype/TrueType/BaseFont/" ); + appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine ); + aLine.append( "\n" + "/FirstChar 0\n" + "/LastChar " + + OString::number( static_cast<sal_Int32>(nGlyphs-1) ) + + "\n" + "/Widths[" ); + for (auto i = 0u; i < nGlyphs; i++) + { + aLine.append( pWidths[ i ] ); + aLine.append( ((i & 15) == 15) ? "\n" : " " ); + } + aLine.append( "]\n" + "/FontDescriptor " + + OString::number( nFontDescriptor ) + + " 0 R\n" ); + if( nToUnicodeStream ) + { + aLine.append( "/ToUnicode " + + OString::number( nToUnicodeStream ) + + " 0 R\n" ); + } + aLine.append( ">>\n" + "endobj\n\n" ); + if ( !writeBuffer( aLine ) ) return false; + + aFontIDToObject[ s_subset.m_nFontID ] = nFontObject; + } + else + { + OStringBuffer aErrorComment( 256 ); + aErrorComment.append( "CreateFontSubset failed for font \"" + + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) + + "\"" ); + if( pFace->GetItalic() == ITALIC_NORMAL ) + aErrorComment.append( " italic" ); + else if( pFace->GetItalic() == ITALIC_OBLIQUE ) + aErrorComment.append( " oblique" ); + aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) ); + emitComment( aErrorComment.getStr() ); + } + } + } + + // emit system fonts + for (auto const& systemFont : m_aSystemFonts) + { + std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second ); + for (auto const& item : aObjects) + { + if ( !item.second ) return false; + aFontIDToObject[ item.first ] = item.second; + } + } + + // emit Type3 fonts + for (auto const& it : m_aType3Fonts) + { + if (!emitType3Font(it.first, it.second, aFontIDToObject)) + return false; + } + + OStringBuffer aFontDict( 1024 ); + aFontDict.append( OString::number(getFontDictObject()) + + " 0 obj\n" + "<<" ); + int ni = 0; + for (auto const& itemMap : aFontIDToObject) + { + aFontDict.append( "/F" + + OString::number( itemMap.first ) + + " " + + OString::number( itemMap.second ) + + " 0 R" ); + if( ((++ni) & 7) == 0 ) + aFontDict.append( '\n' ); + } + // emit builtin font for widget appearances / variable text + for (auto & item : m_aBuildinFontToObjectMap) + { + rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first)); + item.second = emitBuildinFont( aData.get(), item.second ); + } + + appendBuildinFontsToDict(aFontDict); + aFontDict.append( "\n>>\nendobj\n\n" ); + + if ( !updateObject( getFontDictObject() ) ) return false; + if ( !writeBuffer( aFontDict ) ) return false; + return true; +} + +sal_Int32 PDFWriterImpl::emitResources() +{ + // emit shadings + if( ! m_aGradients.empty() ) + CHECK_RETURN( emitGradients() ); + // emit tilings + if( ! m_aTilings.empty() ) + CHECK_RETURN( emitTilings() ); + + // emit font dict + CHECK_RETURN( emitFonts() ); + + // emit Resource dict + OStringBuffer aLine( 512 ); + sal_Int32 nResourceDict = getResourceDictObj(); + CHECK_RETURN( updateObject( nResourceDict ) ); + aLine.setLength( 0 ); + aLine.append( OString::number(nResourceDict) + + " 0 obj\n" ); + m_aGlobalResourceDict.append( aLine, getFontDictObject() ); + aLine.append( "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + return nResourceDict; +} + +sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts, + sal_Int32 nItemLevel, + sal_Int32 nCurrentItemId ) +{ + /* The /Count number of an item is + positive: the number of visible subitems + negative: the negative number of subitems that will become visible if + the item gets opened + see PDF ref 1.4 p 478 + */ + + sal_Int32 nCount = 0; + + if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible + m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible + ) + { + PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; + sal_Int32 nChildren = rItem.m_aChildren.size(); + for( sal_Int32 i = 0; i < nChildren; i++ ) + nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); + rCounts[nCurrentItemId] = nCount; + // return 1 (this item) + visible sub items + if( nCount < 0 ) + nCount = 0; + nCount++; + } + else + { + // this bookmark level is invisible + PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; + sal_Int32 nChildren = rItem.m_aChildren.size(); + rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size()); + for( sal_Int32 i = 0; i < nChildren; i++ ) + updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); + nCount = -1; + } + + return nCount; +} + +sal_Int32 PDFWriterImpl::emitOutline() +{ + int i, nItems = m_aOutline.size(); + + // do we have an outline at all ? + if( nItems < 2 ) + return 0; + + // reserve object numbers for all outline items + for( i = 0; i < nItems; ++i ) + m_aOutline[i].m_nObject = createObject(); + + // update all parent, next and prev object ids + for( i = 0; i < nItems; ++i ) + { + PDFOutlineEntry& rItem = m_aOutline[i]; + int nChildren = rItem.m_aChildren.size(); + + if( nChildren ) + { + for( int n = 0; n < nChildren; ++n ) + { + PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ]; + + rChild.m_nParentObject = rItem.m_nObject; + rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0; + rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0; + } + + } + } + + // calculate Count entries for all items + std::vector< sal_Int32 > aCounts( nItems ); + updateOutlineItemCount( aCounts, 0, 0 ); + + // emit hierarchy + for( i = 0; i < nItems; ++i ) + { + PDFOutlineEntry& rItem = m_aOutline[i]; + OStringBuffer aLine( 1024 ); + + CHECK_RETURN( updateObject( rItem.m_nObject ) ); + aLine.append( OString::number(rItem.m_nObject) + + " 0 obj\n" + "<<" ); + // number of visible children (all levels) + if( i > 0 || aCounts[0] > 0 ) + { + aLine.append( "/Count " + OString::number( aCounts[i] ) ); + } + if( ! rItem.m_aChildren.empty() ) + { + // children list: First, Last + aLine.append( "/First " + + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject ) + + " 0 R/Last " + + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject ) + + " 0 R\n" ); + } + if( i > 0 ) + { + // Title, Dest, Parent, Prev, Next + aLine.append( "/Title" ); + appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine ); + aLine.append( "\n" ); + // Dest is not required + if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() ) + { + aLine.append( "/Dest" ); + appendDest( rItem.m_nDestID, aLine ); + } + aLine.append( "/Parent " + + OString::number( rItem.m_nParentObject ) + + " 0 R" ); + if( rItem.m_nPrevObject ) + { + aLine.append( "/Prev " + + OString::number( rItem.m_nPrevObject ) + + " 0 R" ); + } + if( rItem.m_nNextObject ) + { + aLine.append( "/Next " + + OString::number( rItem.m_nNextObject ) + + " 0 R" ); + } + } + aLine.append( ">>\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + } + + return m_aOutline[0].m_nObject; +} + +#undef CHECK_RETURN +#define CHECK_RETURN( x ) if( !x ) return false + +bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer ) +{ + if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) + { + SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested"); + return false; + } + + const PDFDest& rDest = m_aDests[ nDestID ]; + const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; + + rBuffer.append( '[' ); + rBuffer.append( rDestPage.m_nPageObject ); + rBuffer.append( " 0 R" ); + + switch( rDest.m_eType ) + { + case PDFWriter::DestAreaType::XYZ: + default: + rBuffer.append( "/XYZ " ); + appendFixedInt( rDest.m_aRect.Left(), rBuffer ); + rBuffer.append( ' ' ); + appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); + rBuffer.append( " 0" ); + break; + case PDFWriter::DestAreaType::FitRectangle: + rBuffer.append( "/FitR " ); + appendFixedInt( rDest.m_aRect.Left(), rBuffer ); + rBuffer.append( ' ' ); + appendFixedInt( rDest.m_aRect.Top(), rBuffer ); + rBuffer.append( ' ' ); + appendFixedInt( rDest.m_aRect.Right(), rBuffer ); + rBuffer.append( ' ' ); + appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); + break; + } + rBuffer.append( ']' ); + + return true; +} + +void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream) +{ + sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType); + auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back(); + rAttachedFile.maFilename = rFileName; + rAttachedFile.maMimeType = rMimeType; + rAttachedFile.maDescription = rDescription; + rAttachedFile.mnEmbeddedFileObjectId = nObjectID; + rAttachedFile.mnObjectId = createObject(); +} + +sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType) +{ + sal_Int32 aObjectID = createObject(); + auto& rEmbedded = m_aEmbeddedFiles.emplace_back(); + rEmbedded.m_nObject = aObjectID; + rEmbedded.m_aSubType = rMimeType; + rEmbedded.m_pStream = std::move(rStream); + return aObjectID; +} + +sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer) +{ + sal_Int32 aObjectID = createObject(); + m_aEmbeddedFiles.emplace_back(); + m_aEmbeddedFiles.back().m_nObject = aObjectID; + m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer; + return aObjectID; +} + +bool PDFWriterImpl::emitScreenAnnotations() +{ + int nAnnots = m_aScreens.size(); + for (int i = 0; i < nAnnots; i++) + { + const PDFScreen& rScreen = m_aScreens[i]; + + OStringBuffer aLine; + bool bEmbed = false; + if (!rScreen.m_aTempFileURL.isEmpty()) + { + bEmbed = true; + if (!updateObject(rScreen.m_nTempFileObject)) + continue; + + SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ); + SvMemoryStream aMemoryStream; + aMemoryStream.WriteStream(aFileStream); + + aLine.append(rScreen.m_nTempFileObject); + aLine.append(" 0 obj\n"); + aLine.append("<< /Type /EmbeddedFile /Length "); + aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize())); + aLine.append(" >>\nstream\n"); + CHECK_RETURN(writeBuffer(aLine)); + aLine.setLength(0); + + CHECK_RETURN(writeBufferBytes(aMemoryStream.GetData(), aMemoryStream.GetSize())); + + aLine.append("\nendstream\nendobj\n\n"); + CHECK_RETURN(writeBuffer(aLine)); + aLine.setLength(0); + } + + if (!updateObject(rScreen.m_nObject)) + continue; + + // Annot dictionary. + aLine.append(OString::number(rScreen.m_nObject) + + " 0 obj\n" + "<</Type/Annot" + "/Subtype/Screen/Rect["); + appendFixedInt(rScreen.m_aRect.Left(), aLine); + aLine.append(' '); + appendFixedInt(rScreen.m_aRect.Top(), aLine); + aLine.append(' '); + appendFixedInt(rScreen.m_aRect.Right(), aLine); + aLine.append(' '); + appendFixedInt(rScreen.m_aRect.Bottom(), aLine); + aLine.append("]"); + + // Action dictionary. + aLine.append("/A<</Type/Action /S/Rendition /AN " + + OString::number(rScreen.m_nObject) + + " 0 R "); + + // Rendition dictionary. + aLine.append("/R<</Type/Rendition /S/MR "); + + // MediaClip dictionary. + aLine.append("/C<</Type/MediaClip /S/MCD "); + if (bEmbed) + { + aLine.append("\n/D << /Type /Filespec /F (<embedded file>) "); + if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) + { // ISO 14289-1:2014, Clause: 7.11 + aLine.append("/UF (<embedded file>) "); + } + aLine.append("/EF << /F "); + aLine.append(rScreen.m_nTempFileObject); + aLine.append(" 0 R >>"); + } + else + { + // Linked. + aLine.append("\n/D << /Type /Filespec /FS /URL /F "); + appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding()); + if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) + { // ISO 14289-1:2014, Clause: 7.11 + aLine.append("/UF "); + appendUnicodeTextStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine); + } + } + if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version + && !rScreen.m_AltText.isEmpty()) + { // ISO 14289-1:2014, Clause: 7.11 + aLine.append("/Desc "); + appendUnicodeTextStringEncrypt(rScreen.m_AltText, rScreen.m_nObject, aLine); + } + aLine.append(" >>\n"); // end of /D + // Allow playing the video via a tempfile. + aLine.append("/P <</TF (TEMPACCESS)>>"); + // ISO 14289-1:2014, Clause: 7.18.6.2 + aLine.append("/CT "); + appendLiteralStringEncrypt(rScreen.m_MimeType, rScreen.m_nObject, aLine); + // ISO 14289-1:2014, Clause: 7.18.6.2 + // Alt text is a "Multi-language Text Array" + aLine.append(" /Alt [ () "); + appendUnicodeTextStringEncrypt(rScreen.m_AltText, rScreen.m_nObject, aLine); + aLine.append(" ] " + ">>"); + + // End Rendition dictionary by requesting play/pause/stop controls. + aLine.append("/P<</BE<</C true >>>>" + ">>"); + + // End Action dictionary. + aLine.append("/OP 0 >>"); + + if (-1 != rScreen.m_nStructParent) + { + aLine.append("\n/StructParent " + + OString::number(rScreen.m_nStructParent) + + "\n"); + } + + // End Annot dictionary. + aLine.append("/P " + + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject) + + " 0 R\n>>\nendobj\n\n"); + CHECK_RETURN(writeBuffer(aLine)); + } + + return true; +} + +bool PDFWriterImpl::emitLinkAnnotations() +{ + MARK("PDFWriterImpl::emitLinkAnnotations"); + int nAnnots = m_aLinks.size(); + for( int i = 0; i < nAnnots; i++ ) + { + const PDFLink& rLink = m_aLinks[i]; + if( ! updateObject( rLink.m_nObject ) ) + continue; + + OStringBuffer aLine( 1024 ); + aLine.append( rLink.m_nObject ); + aLine.append( " 0 obj\n" ); +// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' +// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 + aLine.append( "<</Type/Annot" ); + if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3) + aLine.append( "/F 4" ); + aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" ); + + appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Top(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Bottom(), aLine ); + aLine.append( "]" ); + // ISO 14289-1:2014, Clause: 7.18.5 + aLine.append("/Contents"); + appendUnicodeTextStringEncrypt(rLink.m_AltText, rLink.m_nObject, aLine); + if( rLink.m_nDest >= 0 ) + { + aLine.append( "/Dest" ); + appendDest( rLink.m_nDest, aLine ); + } + else + { +/* +destination is external to the document, so +we check in the following sequence: + + if target type is neither .pdf, nor .od[tpgs], then + check if relative or absolute and act accordingly (use URI or 'launch application' as requested) + end processing + else if target is .od[tpgs]: then + if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file + processing continue + + if (new)target is .pdf : then + if GotToR is requested, then + convert the target in GoToR where the fragment of the URI is + considered the named destination in the target file, set relative or absolute as requested + else strip the fragment from URL and then set URI or 'launch application' as requested +*/ + +// FIXME: check if the decode mechanisms for URL processing throughout this implementation +// are the correct one!! + +// extract target file type + auto url(URIHelper::resolveIdnaHost(rLink.m_aURL)); + + INetURLObject aDocumentURL( m_aContext.BaseURL ); + INetURLObject aTargetURL( url ); + bool bSetGoToRMode = false; + bool bTargetHasPDFExtension = false; + INetProtocol eTargetProtocol = aTargetURL.GetProtocol(); + bool bIsUNCPath = false; + bool bUnparsedURI = false; + + // check if the protocol is a known one, or if there is no protocol at all (on target only) + // if there is no protocol, make the target relative to the current document directory + // getting the needed URL information from the current document path + if( eTargetProtocol == INetProtocol::NotValid ) + { + if( url.getLength() > 4 && url.startsWith("\\\\\\\\")) + { + bIsUNCPath = true; + } + else + { + INetURLObject aNewURL(rtl::Uri::convertRelToAbs( + (m_aContext.BaseURL.getLength() > 0 ? + m_aContext.BaseURL : + //use dummy location if empty + u"http://ahost.ax"_ustr), + url)); + aTargetURL = aNewURL; //reassign the new target URL + + //recompute the target protocol, with the new URL + //normal URL processing resumes + eTargetProtocol = aTargetURL.GetProtocol(); + + bUnparsedURI = eTargetProtocol == INetProtocol::NotValid; + } + } + + OUString aFileExtension = aTargetURL.GetFileExtension(); + + // Check if the URL ends in '/': if yes it's a directory, + // it will be forced to a URI link. + // possibly a malformed URI, leave it as it is, force as URI + if( aTargetURL.hasFinalSlash() ) + m_aContext.DefaultLinkAction = PDFWriter::URIAction; + + if( !aFileExtension.isEmpty() ) + { + if( m_aContext.ConvertOOoTargetToPDFTarget ) + { + bool bChangeFileExtensionToPDF = false; + //examine the file type (.odm .odt. .odp, odg, ods) + if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) ) + bChangeFileExtensionToPDF = true; + if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) ) + bChangeFileExtensionToPDF = true; + else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) ) + bChangeFileExtensionToPDF = true; + else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) ) + bChangeFileExtensionToPDF = true; + else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) ) + bChangeFileExtensionToPDF = true; + if( bChangeFileExtensionToPDF ) + aTargetURL.setExtension(u"pdf" ); + } + //check if extension is pdf, see if GoToR should be forced + bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" ); + if( m_aContext.ForcePDFAction && bTargetHasPDFExtension ) + bSetGoToRMode = true; + } + //prepare the URL, if relative or not + INetProtocol eBaseProtocol = aDocumentURL.GetProtocol(); + //queue the string common to all types of actions + aLine.append( "/A<</Type/Action/S"); + if( bIsUNCPath ) // handle Win UNC paths + { + aLine.append( "/Launch/Win<</F" ); + // INetURLObject is not good with UNC paths, use original path + appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); + aLine.append( ">>" ); + } + else + { + bool bSetRelative = false; + bool bFileSpec = false; + //check if relative file link is requested and if the protocol is 'file://' + if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File ) + bSetRelative = true; + + OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is, + if( !bSetGoToRMode ) + { + switch( m_aContext.DefaultLinkAction ) + { + default: + case PDFWriter::URIAction : + case PDFWriter::URIActionDestination : + aLine.append( "/URI/URI" ); + break; + case PDFWriter::LaunchAction: + // now: + // if a launch action is requested and the hyperlink target has a fragment + // and the target file does not have a pdf extension, or it's not a 'file:://' + // protocol then force the uri action on it + // This code will permit the correct opening of application on web pages, + // the one that normally have fragments (but I may be wrong...) + // and will force the use of URI when the protocol is not file: + if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) || + eTargetProtocol != INetProtocol::File ) + { + aLine.append( "/URI/URI" ); + } + else + { + aLine.append( "/Launch/F" ); + bFileSpec = true; + } + break; + } + } + + //fragment are encoded in the same way as in the named destination processing + if( bSetGoToRMode ) + { + //add the fragment + OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset ); + aLine.append("/GoToR"); + aLine.append("/F"); + appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark, + INetURLObject::EncodeMechanism::WasEncoded, + INetURLObject::DecodeMechanism::WithCharset ) : + aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); + if( !aFragment.isEmpty() ) + { + aLine.append("/D/"); + appendDestinationName( aFragment , aLine ); + } + } + else + { + // change the fragment to accommodate the bookmark (only if the file extension + // is PDF and the requested action is of the correct type) + if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination && + bTargetHasPDFExtension && !aFragment.isEmpty() ) + { + OStringBuffer aLineLoc( 1024 ); + appendDestinationName( aFragment , aLineLoc ); + //substitute the fragment + aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) ); + } + OUString aURL = bUnparsedURI ? url : + aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : + INetURLObject::DecodeMechanism::NONE ); + appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL, + INetURLObject::EncodeMechanism::WasEncoded, + bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE + ) : + aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); + } + } + aLine.append( ">>\n" ); + } + if (rLink.m_nStructParent != -1) + { + aLine.append( "/StructParent " ); + aLine.append( rLink.m_nStructParent ); + } + aLine.append( ">>\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + } + + return true; +} + +namespace +{ + +void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine) +{ + aLine.append("/Rect["); + appendFixedInt(rRectangle.Left(), aLine); + aLine.append(' '); + appendFixedInt(rRectangle.Top(), aLine); + aLine.append(' '); + appendFixedInt(rRectangle.Right(), aLine); + aLine.append(' '); + appendFixedInt(rRectangle.Bottom(), aLine); + aLine.append("] "); +} + +} // end anonymous namespace + +void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote) +{ + appendObjectID(rNote.m_nObject, aLine); + + aLine.append("<</Type /Annot /Subtype "); + if (rNote.m_aContents.maPolygons.size() == 1) + { + auto const& rPolygon = rNote.m_aContents.maPolygons[0]; + aLine.append(rPolygon.isClosed() ? "/Polygon " : "/Polyline "); + aLine.append("/Vertices ["); + for (sal_uInt32 i = 0; i < rPolygon.count(); ++i) + { + appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor); + aLine.append(" "); + appendDouble(m_aPages[rNote.m_nPage].getHeight() + - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()), + aLine, nLog10Divisor); + aLine.append(" "); + } + aLine.append("] "); + aLine.append("/C ["); + appendColor(rNote.m_aContents.annotColor, aLine, false); + aLine.append("] "); + if (rPolygon.isClosed()) + { + aLine.append("/IC ["); + appendColor(rNote.m_aContents.interiorColor, aLine, false); + aLine.append("] "); + } + } + else if (rNote.m_aContents.maPolygons.size() > 1) + { + aLine.append("/Ink /InkList ["); + for (auto const& rPolygon : rNote.m_aContents.maPolygons) + { + aLine.append("["); + for (sal_uInt32 i = 0; i < rPolygon.count(); ++i) + { + appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, + nLog10Divisor); + aLine.append(" "); + appendDouble(m_aPages[rNote.m_nPage].getHeight() + - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()), + aLine, nLog10Divisor); + aLine.append(" "); + } + aLine.append("]"); + aLine.append("/C ["); + appendColor(rNote.m_aContents.annotColor, aLine, false); + aLine.append("] "); + } + aLine.append("] "); + } + else if (rNote.m_aContents.isFreeText) + aLine.append("/FreeText "); + else + aLine.append("/Text "); + + aLine.append("/BS<</W 0>>"); + + // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' + // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 + if (m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3) + aLine.append("/F 4 "); + + appendAnnotationRect(rNote.m_aRect, aLine); + + aLine.append("/Popup "); + appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine); + + auto & rDateTime = rNote.m_aContents.maModificationDate; + + aLine.append("/M ("); + appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0); + aLine.append(") "); + + // contents of the note (type text string) + aLine.append("/Contents "); + appendUnicodeTextStringEncrypt(rNote.m_aContents.Contents, rNote.m_nObject, aLine); + aLine.append("\n"); + + // optional title + if (!rNote.m_aContents.Title.isEmpty()) + { + aLine.append("/T "); + appendUnicodeTextStringEncrypt(rNote.m_aContents.Title, rNote.m_nObject, aLine); + aLine.append("\n"); + } + aLine.append(">>\n"); + aLine.append("endobj\n\n"); +} + +void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp) +{ + appendObjectID(rPopUp.m_nObject, aLine); + aLine.append("<</Type /Annot /Subtype /Popup "); + aLine.append("/Parent "); + appendObjectReference(rPopUp.m_nParentObject, aLine); + aLine.append(">>\n"); + aLine.append("endobj\n\n"); +} + +bool PDFWriterImpl::emitNoteAnnotations() +{ + // emit note annotations + int nAnnots = m_aNotes.size(); + for( int i = 0; i < nAnnots; i++ ) + { + const PDFNoteEntry& rNote = m_aNotes[i]; + const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation; + + { + if (!updateObject(rNote.m_nObject)) + return false; + + OStringBuffer aLine(1024); + + emitTextAnnotationLine(aLine, rNote); + + if (!writeBuffer(aLine)) + return false; + } + + { + + if (!updateObject(rPopUp.m_nObject)) + return false; + + OStringBuffer aLine(1024); + + emitPopupAnnotationLine(aLine, rPopUp); + + if (!writeBuffer(aLine)) + return false; + } + } + return true; +} + +Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont ) +{ + bool bAdjustSize = false; + + Font aFont( rControlFont ); + if( aFont.GetFamilyName().isEmpty() ) + { + aFont = rAppSetFont; + if( rControlFont.GetFontHeight() ) + aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) ); + else + bAdjustSize = true; + if( rControlFont.GetItalic() != ITALIC_DONTKNOW ) + aFont.SetItalic( rControlFont.GetItalic() ); + if( rControlFont.GetWeight() != WEIGHT_DONTKNOW ) + aFont.SetWeight( rControlFont.GetWeight() ); + } + else if( ! aFont.GetFontHeight() ) + { + aFont.SetFontSize( rAppSetFont.GetFontSize() ); + bAdjustSize = true; + } + if( bAdjustSize ) + { + Size aFontSize = aFont.GetFontSize(); + OutputDevice* pDefDev = Application::GetDefaultDevice(); + aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() ); + aFont.SetFontSize( aFontSize ); + } + return aFont; +} + +sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont ) +{ + sal_Int32 nBest = 4; // default to Helvetica + + if (rFont.GetFamilyType() == FAMILY_ROMAN) + { + // Serif: default to Times-Roman. + nBest = 8; + } + + OUString aFontName( rFont.GetFamilyName() ); + aFontName = aFontName.toAsciiLowerCase(); + + if( aFontName.indexOf( "times" ) != -1 ) + nBest = 8; + else if( aFontName.indexOf( "courier" ) != -1 ) + nBest = 0; + else if( aFontName.indexOf( "dingbats" ) != -1 ) + nBest = 13; + else if( aFontName.indexOf( "symbol" ) != -1 ) + nBest = 12; + if( nBest < 12 ) + { + if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL ) + nBest += 1; + if( rFont.GetWeight() > WEIGHT_MEDIUM ) + nBest += 2; + } + + if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() ) + m_aBuildinFontToObjectMap[ nBest ] = createObject(); + + return nBest; +} + +static const Color& replaceColor( const Color& rCol1, const Color& rCol2 ) +{ + return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1; +} + +void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget ) +{ + const StyleSettings& rSettings = m_aWidgetStyleSettings; + + // save graphics state + push( PushFlags::ALL ); + + // transform relative to control's coordinates since an + // appearance stream is a form XObject + // this relies on the m_aRect member of rButton NOT already being transformed + // to default user space + if( rWidget.Background || rWidget.Border ) + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT ); + drawRectangle( rWidget.Location ); + } + // prepare font to use + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() ); + setFont( aFont ); + setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) ); + + drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle ); + + // create DA string while local mapmode is still in place + // (that is before endRedirect()) + OStringBuffer aDA( 256 ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA ); + Font aDummyFont( "Helvetica", aFont.GetFontSize() ); + sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont ); + aDA.append( ' ' ); + aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject()); + aDA.append( ' ' ); + m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); + aDA.append( " Tf" ); + rButton.m_aDAString = aDA.makeStringAndClear(); + + pop(); + + rButton.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream(); + + /* seems like a bad hack but at least works in both AR5 and 6: + we draw the button ourselves and tell AR + the button would be totally transparent with no text + + One would expect that simply setting a normal appearance + should suffice, but no, as soon as the user actually presses + the button and an action is tied to it (gasp! a button that + does something) the appearance gets replaced by some crap that AR + creates on the fly even if no DA or MK is given. On AR6 at least + the DA and MK work as expected, but on AR5 this creates a region + filled with the background color but nor text. Urgh. + */ + rButton.m_aMKDict = "/BC [] /BG [] /CA"_ostr; + rButton.m_aMKDictCAString = ""_ostr; +} + +Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern, + const PDFWriter::AnyWidget& rWidget, + const StyleSettings& rSettings ) +{ + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() ); + + if( rWidget.Background || rWidget.Border ) + { + if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT ) + { + sal_Int32 nDelta = GetDPIX() / 500; + if( nDelta < 1 ) + nDelta = 1; + setLineColor( COL_TRANSPARENT ); + tools::Rectangle aRect = rIntern.m_aRect; + setFillColor( rSettings.GetLightBorderColor() ); + drawRectangle( aRect ); + aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta ); + aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta ); + setFillColor( rSettings.GetFieldColor() ); + drawRectangle( aRect ); + setFillColor( rSettings.GetLightColor() ); + drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) ); + drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) ); + setFillColor( rSettings.GetDarkShadowColor() ); + drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) ); + drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) ); + } + else + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); + drawRectangle( rIntern.m_aRect ); + } + + if( rWidget.Border ) + { + // adjust edit area accounting for border + sal_Int32 nDelta = aFont.GetFontHeight()/4; + if( nDelta < 1 ) + nDelta = 1; + rIntern.m_aRect.AdjustLeft(nDelta ); + rIntern.m_aRect.AdjustTop(nDelta ); + rIntern.m_aRect.AdjustRight( -nDelta ); + rIntern.m_aRect.AdjustBottom( -nDelta ); + } + } + return aFont; +} + +void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget ) +{ + const StyleSettings& rSettings = m_aWidgetStyleSettings; + SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 ); + + push( PushFlags::ALL ); + + // prepare font to use, draw field border + Font aFont = drawFieldBorder( rEdit, rWidget, rSettings ); + // Get the built-in font which is closest to aFont. + sal_Int32 nBest = getBestBuildinFont(aFont); + + // prepare DA string + OStringBuffer aDA( 32 ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); + aDA.append( ' ' ); + aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject()); + + OStringBuffer aDR( 32 ); + aDR.append( "/Font " ); + aDR.append( getFontDictObject() ); + aDR.append( " 0 R" ); + rEdit.m_aDRDict = aDR.makeStringAndClear(); + aDA.append( ' ' ); + m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); + aDA.append( " Tf" ); + + /* create an empty appearance stream, let the viewer create + the appearance at runtime. This is because AR5 seems to + paint the widget appearance always, and a dynamically created + appearance on top of it. AR6 is well behaved in that regard, so + that behaviour seems to be a bug. Anyway this empty appearance + relies on /NeedAppearances in the AcroForm dictionary set to "true" + */ + beginRedirect( pEditStream, rEdit.m_aRect ); + writeBuffer( "/Tx BMC\nEMC\n" ); + + endRedirect(); + pop(); + + rEdit.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pEditStream; + + rEdit.m_aDAString = aDA.makeStringAndClear(); +} + +void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget ) +{ + const StyleSettings& rSettings = m_aWidgetStyleSettings; + SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 ); + + push( PushFlags::ALL ); + + // prepare font to use, draw field border + Font aFont = drawFieldBorder( rBox, rWidget, rSettings ); + sal_Int32 nBest = getSystemFont( aFont ); + + beginRedirect( pListBoxStream, rBox.m_aRect ); + + setLineColor( COL_TRANSPARENT ); + setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) ); + drawRectangle( rBox.m_aRect ); + + // empty appearance, see createDefaultEditAppearance for reference + writeBuffer( "/Tx BMC\nEMC\n" ); + + endRedirect(); + pop(); + + rBox.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pListBoxStream; + + // prepare DA string + OStringBuffer aDA( 256 ); + // prepare DA string + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); + aDA.append( ' ' ); + aDA.append( "/F" ); + aDA.append( nBest ); + + OStringBuffer aDR( 32 ); + aDR.append( "/Font " ); + aDR.append( getFontDictObject() ); + aDR.append( " 0 R" ); + rBox.m_aDRDict = aDR.makeStringAndClear(); + aDA.append( ' ' ); + m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); + aDA.append( " Tf" ); + rBox.m_aDAString = aDA.makeStringAndClear(); +} + +void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget ) +{ + const StyleSettings& rSettings = m_aWidgetStyleSettings; + + // save graphics state + push( PushFlags::ALL ); + + if( rWidget.Background || rWidget.Border ) + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); + drawRectangle( rBox.m_aRect ); + } + + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); + setFont( aFont ); + Size aFontSize = aFont.GetFontSize(); + if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) + aFontSize.setHeight( rBox.m_aRect.GetHeight() ); + sal_Int32 nDelta = aFontSize.Height()/10; + if( nDelta < 1 ) + nDelta = 1; + + tools::Rectangle aCheckRect, aTextRect; + { + aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta ); + aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 ); + aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() ); + aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() ); + + // #i74206# handle small controls without text area + while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) + { + aCheckRect.AdjustRight( -nDelta ); + aCheckRect.AdjustTop(nDelta/2 ); + aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) ); + } + + aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta ); + aTextRect.SetTop( rBox.m_aRect.Top() ); + aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta ); + aTextRect.SetBottom( rBox.m_aRect.Bottom() ); + } + setLineColor( COL_BLACK ); + setFillColor( COL_TRANSPARENT ); + OStringBuffer aLW( 32 ); + aLW.append( "q " ); + m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW ); + aLW.append( " w " ); + writeBuffer( aLW ); + drawRectangle( aCheckRect ); + writeBuffer( " Q\n" ); + setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); + drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); + + pop(); + + OStringBuffer aDA( 256 ); + + // tdf#93853 don't rely on Zapf (or any other 'standard' font) + // being present, but our own OpenSymbol - N.B. PDF/A for good + // reasons require even the standard PS fonts to be embedded! + Push(); + SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) ); + const LogicalFontInstance* pFontInstance = GetFontInstance(); + const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace(); + Pop(); + + // make sure OpenSymbol is embedded, and includes our checkmark + const sal_Unicode cMark=0x2713; + const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark); + const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false); + + sal_uInt8 nMappedGlyph; + sal_Int32 nMappedFontObject; + registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject); + + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + aDA.append( ' ' ); + aDA.append( "/F" ); + aDA.append( nMappedFontObject ); + aDA.append( " 0 Tf" ); + + OStringBuffer aDR( 32 ); + aDR.append( "/Font " ); + aDR.append( getFontDictObject() ); + aDR.append( " 0 R" ); + rBox.m_aDRDict = aDR.makeStringAndClear(); + rBox.m_aDAString = aDA.makeStringAndClear(); + rBox.m_aMKDict = "/CA"_ostr; + rBox.m_aMKDictCAString = "8"_ostr; + rBox.m_aRect = aCheckRect; + + // create appearance streams + sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol + nCharXOffset *= aCheckRect.GetHeight(); + nCharXOffset /= 2000; + sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf + nCharYOffset *= aCheckRect.GetHeight(); + nCharYOffset /= 2000; + + // write 'checked' appearance stream + SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); + beginRedirect( pCheckStream, aCheckRect ); + aDA.append( "/Tx BMC\nq BT\n" ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + aDA.append( ' ' ); + aDA.append( "/F" ); + aDA.append( nMappedFontObject ); + aDA.append( ' ' ); + m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); + aDA.append( " Tf\n" ); + m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA ); + aDA.append( " " ); + m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA ); + aDA.append( " Td <" ); + appendHex( nMappedGlyph, aDA ); + aDA.append( "> Tj\nET\nQ\nEMC\n" ); + writeBuffer( aDA ); + endRedirect(); + rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream; + + // write 'unchecked' appearance stream + SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); + beginRedirect( pUncheckStream, aCheckRect ); + writeBuffer( "/Tx BMC\nEMC\n" ); + endRedirect(); + rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream; +} + +void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget ) +{ + const StyleSettings& rSettings = m_aWidgetStyleSettings; + + // save graphics state + push( PushFlags::ALL ); + + if( rWidget.Background || rWidget.Border ) + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); + drawRectangle( rBox.m_aRect ); + } + + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); + setFont( aFont ); + Size aFontSize = aFont.GetFontSize(); + if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) + aFontSize.setHeight( rBox.m_aRect.GetHeight() ); + sal_Int32 nDelta = aFontSize.Height()/10; + if( nDelta < 1 ) + nDelta = 1; + + tools::Rectangle aCheckRect, aTextRect; + { + aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta ); + aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 ); + aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() ); + aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() ); + + // #i74206# handle small controls without text area + while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) + { + aCheckRect.AdjustRight( -nDelta ); + aCheckRect.AdjustTop(nDelta/2 ); + aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) ); + } + + aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta ); + aTextRect.SetTop( rBox.m_aRect.Top() ); + aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta ); + aTextRect.SetBottom( rBox.m_aRect.Bottom() ); + } + setLineColor( COL_BLACK ); + setFillColor( COL_TRANSPARENT ); + OStringBuffer aLW( 32 ); + aLW.append( "q " ); + m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW ); + aLW.append( " w " ); + writeBuffer( aLW ); + drawEllipse( aCheckRect ); + writeBuffer( " Q\n" ); + setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); + drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); + + pop(); + + //to encrypt this (el) + rBox.m_aMKDict = "/CA"_ostr; + //after this assignment, to m_aMKDic cannot be added anything + rBox.m_aMKDictCAString = "l"_ostr; + + rBox.m_aRect = aCheckRect; + + // create appearance streams + push( PushFlags::ALL); + SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); + + beginRedirect( pCheckStream, aCheckRect ); + OStringBuffer aDA( 256 ); + aDA.append( "/Tx BMC\nq BT\n" ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + aDA.append( ' ' ); + m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); + aDA.append( " 0 0 Td\nET\nQ\n" ); + writeBuffer( aDA ); + setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); + setLineColor( COL_TRANSPARENT ); + aCheckRect.AdjustLeft(3*nDelta ); + aCheckRect.AdjustTop(3*nDelta ); + aCheckRect.AdjustBottom( -(3*nDelta) ); + aCheckRect.AdjustRight( -(3*nDelta) ); + drawEllipse( aCheckRect ); + writeBuffer( "\nEMC\n" ); + endRedirect(); + + pop(); + rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream; + + SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); + beginRedirect( pUncheckStream, aCheckRect ); + writeBuffer( "/Tx BMC\nEMC\n" ); + endRedirect(); + rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream; +} + +bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict ) +{ + // TODO: check and insert default streams + OString aStandardAppearance; + switch( rWidget.m_eType ) + { + case PDFWriter::CheckBox: + aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US ); + break; + default: + break; + } + + if( !rWidget.m_aAppearances.empty() ) + { + rAnnotDict.append( "/AP<<\n" ); + for (auto & dict_item : rWidget.m_aAppearances) + { + rAnnotDict.append( "/" ); + rAnnotDict.append( dict_item.first ); + bool bUseSubDict = (dict_item.second.size() > 1); + + // PDF/A requires sub-dicts for /FT/Btn objects (clause + // 6.3.3) + if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3) + { + if( rWidget.m_eType == PDFWriter::RadioButton || + rWidget.m_eType == PDFWriter::CheckBox || + rWidget.m_eType == PDFWriter::PushButton ) + { + bUseSubDict = true; + } + } + + rAnnotDict.append( bUseSubDict ? "<<" : " " ); + + for (auto const& stream_item : dict_item.second) + { + SvMemoryStream* pAppearanceStream = stream_item.second; + dict_item.second[ stream_item.first ] = nullptr; + + bool bDeflate = compressStream( pAppearanceStream ); + + sal_Int64 nStreamLen = pAppearanceStream->TellEnd(); + pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN ); + sal_Int32 nObject = createObject(); + CHECK_RETURN( updateObject( nObject ) ); + if (g_bDebugDisableCompression) + { + emitComment( "PDFWriterImpl::emitAppearances" ); + } + OStringBuffer aLine; + aLine.append( nObject ); + + aLine.append( " 0 obj\n" + "<</Type/XObject\n" + "/Subtype/Form\n" + "/BBox[0 0 " ); + appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine ); + aLine.append( " " ); + appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine ); + aLine.append( "]\n" + "/Resources " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R\n" + "/Length " ); + aLine.append( nStreamLen ); + aLine.append( "\n" ); + if( bDeflate ) + aLine.append( "/Filter/FlateDecode\n" ); + aLine.append( ">>\nstream\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + checkAndEnableStreamEncryption( nObject ); + CHECK_RETURN( writeBufferBytes( pAppearanceStream->GetData(), nStreamLen ) ); + disableStreamEncryption(); + CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n" ) ); + + if( bUseSubDict ) + { + rAnnotDict.append( " /" ); + rAnnotDict.append( stream_item.first ); + rAnnotDict.append( " " ); + } + rAnnotDict.append( nObject ); + rAnnotDict.append( " 0 R" ); + + delete pAppearanceStream; + } + + rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" ); + } + rAnnotDict.append( ">>\n" ); + if( !aStandardAppearance.isEmpty() ) + { + rAnnotDict.append( "/AS /" ); + rAnnotDict.append( aStandardAppearance ); + rAnnotDict.append( "\n" ); + } + } + + return true; +} + +bool PDFWriterImpl::emitWidgetAnnotations() +{ + ensureUniqueRadioOnValues(); + + int nAnnots = m_aWidgets.size(); + for( int a = 0; a < nAnnots; a++ ) + { + PDFWidget& rWidget = m_aWidgets[a]; + + if( rWidget.m_eType == PDFWriter::CheckBox ) + { + if ( !rWidget.m_aOnValue.isEmpty() ) + { + auto app_it = rWidget.m_aAppearances.find( "N"_ostr ); + if( app_it != rWidget.m_aAppearances.end() ) + { + auto stream_it = app_it->second.find( "Yes"_ostr ); + if( stream_it != app_it->second.end() ) + { + SvMemoryStream* pStream = stream_it->second; + app_it->second.erase( stream_it ); + OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 ); + appendName( rWidget.m_aOnValue, aBuf ); + (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; + } + else + SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" ); + } + } + + if ( !rWidget.m_aOffValue.isEmpty() ) + { + auto app_it = rWidget.m_aAppearances.find( "N"_ostr ); + if( app_it != rWidget.m_aAppearances.end() ) + { + auto stream_it = app_it->second.find( "Off"_ostr ); + if( stream_it != app_it->second.end() ) + { + SvMemoryStream* pStream = stream_it->second; + app_it->second.erase( stream_it ); + OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 ); + appendName( rWidget.m_aOffValue, aBuf ); + (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; + } + else + SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" ); + } + } + } + + OStringBuffer aLine( 1024 ); + OStringBuffer aValue( 256 ); + aLine.append( rWidget.m_nObject ); + aLine.append( " 0 obj\n" + "<<" ); + if( rWidget.m_eType != PDFWriter::Hierarchy ) + { + // emit widget annotation only for terminal fields + if( rWidget.m_aKids.empty() ) + { + int iRectMargin; + + aLine.append( "/Type/Annot/Subtype/Widget/F " ); + + if (rWidget.m_eType == PDFWriter::Signature) + { + aLine.append( "132\n" ); // Print & Locked + iRectMargin = 0; + } + else + { + aLine.append( "4\n" ); + iRectMargin = 1; + } + + if (-1 != rWidget.m_nStructParent) + { + aLine.append("/StructParent "); + aLine.append(rWidget.m_nStructParent); + aLine.append("\n"); + } + + aLine.append("/Rect[" ); + appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine ); + aLine.append( ' ' ); + appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine ); + aLine.append( ' ' ); + appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine ); + aLine.append( ' ' ); + appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine ); + aLine.append( "]\n" ); + } + aLine.append( "/FT/" ); + switch( rWidget.m_eType ) + { + case PDFWriter::RadioButton: + case PDFWriter::CheckBox: + // for radio buttons only the RadioButton field, not the + // CheckBox children should have a value, else acrobat reader + // does not always check the right button + // of course real check boxes (not belonging to a radio group) + // need their values, too + if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 ) + { + aValue.append( "/" ); + // check for radio group with all buttons unpressed + if( rWidget.m_aValue.isEmpty() ) + aValue.append( "Off" ); + else + appendName( rWidget.m_aValue, aValue ); + } + [[fallthrough]]; + case PDFWriter::PushButton: + aLine.append( "Btn" ); + break; + case PDFWriter::ListBox: + if( rWidget.m_nFlags & 0x200000 ) // multiselect + { + aValue.append( "[" ); + for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ ) + { + sal_Int32 nEntry = rWidget.m_aSelectedEntries[i]; + if( nEntry >= 0 + && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() ) + appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue ); + } + aValue.append( "]" ); + } + else if( !rWidget.m_aSelectedEntries.empty() && + rWidget.m_aSelectedEntries[0] >= 0 && + o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() ) + { + appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue ); + } + else + appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue ); + aLine.append( "Ch" ); + break; + case PDFWriter::ComboBox: + appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue ); + aLine.append( "Ch" ); + break; + case PDFWriter::Edit: + aLine.append( "Tx" ); + appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue ); + break; + case PDFWriter::Signature: + aLine.append( "Sig" ); + aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US)); + break; + case PDFWriter::Hierarchy: // make the compiler happy + break; + } + aLine.append( "\n" ); + aLine.append( "/P " ); + aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject ); + aLine.append( " 0 R\n" ); + } + if( rWidget.m_nParent ) + { + aLine.append( "/Parent " ); + aLine.append( rWidget.m_nParent ); + aLine.append( " 0 R\n" ); + } + if( !rWidget.m_aKids.empty() ) + { + aLine.append( "/Kids[" ); + for( size_t i = 0; i < rWidget.m_aKids.size(); i++ ) + { + aLine.append( rWidget.m_aKids[i] ); + aLine.append( " 0 R" ); + aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); + } + aLine.append( "]\n" ); + } + if( !rWidget.m_aName.isEmpty() ) + { + aLine.append( "/T" ); + appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine ); + aLine.append( "\n" ); + } + if (!rWidget.m_aDescription.isEmpty()) + { + // the alternate field name should be unicode able since it is + // supposed to be used in UI + aLine.append( "/TU" ); + appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine ); + aLine.append( "\n" ); + } + + if( rWidget.m_nFlags ) + { + aLine.append( "/Ff " ); + aLine.append( rWidget.m_nFlags ); + aLine.append( "\n" ); + } + if( !aValue.isEmpty() ) + { + OString aVal = aValue.makeStringAndClear(); + aLine.append( "/V " ); + aLine.append( aVal ); + aLine.append( "\n" + "/DV " ); + aLine.append( aVal ); + aLine.append( "\n" ); + } + if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox ) + { + sal_Int32 nTI = -1; + aLine.append( "/Opt[\n" ); + sal_Int32 i = 0; + for (auto const& entry : rWidget.m_aListEntries) + { + appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine ); + aLine.append( "\n" ); + if( entry == rWidget.m_aValue ) + nTI = i; + ++i; + } + aLine.append( "]\n" ); + if( nTI > 0 ) + { + aLine.append( "/TI " ); + aLine.append( nTI ); + aLine.append( "\n" ); + if( rWidget.m_nFlags & 0x200000 ) // Multiselect + { + aLine.append( "/I [" ); + aLine.append( nTI ); + aLine.append( "]\n" ); + } + } + } + if( rWidget.m_eType == PDFWriter::Edit ) + { + if ( rWidget.m_nMaxLen > 0 ) + { + aLine.append( "/MaxLen " ); + aLine.append( rWidget.m_nMaxLen ); + aLine.append( "\n" ); + } + + if ( rWidget.m_nFormat == PDFWriter::Number ) + { + OString aHexText; + + if ( !rWidget.m_aCurrencySymbol.isEmpty() ) + { + // Get the hexadecimal code + sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1); + aHexText = "\\\\u" + OString::number(cChar, 16); + } + + aLine.append("/AA<<\n"); + aLine.append("/F<</JS(AFNumber_Format\\("); + aLine.append(OString::number(rWidget.m_nDecimalAccuracy)); + aLine.append(", 0, 0, 0, \""); + aLine.append( aHexText ); + aLine.append("\","); + aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol)); + aLine.append("\\);)"); + aLine.append("/S/JavaScript>>\n"); + aLine.append("/K<</JS(AFNumber_Keystroke\\("); + aLine.append(OString::number(rWidget.m_nDecimalAccuracy)); + aLine.append(", 0, 0, 0, \""); + aLine.append( aHexText ); + aLine.append("\","); + aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol)); + aLine.append("\\);)"); + aLine.append("/S/JavaScript>>\n"); + aLine.append(">>\n"); + } + else if ( rWidget.m_nFormat == PDFWriter::Time ) + { + aLine.append("/AA<<\n"); + aLine.append("/F<</JS(AFTime_FormatEx\\(\""); + aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US)); + aLine.append("\"\\);)"); + aLine.append("/S/JavaScript>>\n"); + aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\""); + aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US)); + aLine.append("\"\\);)"); + aLine.append("/S/JavaScript>>\n"); + aLine.append(">>\n"); + } + else if ( rWidget.m_nFormat == PDFWriter::Date ) + { + aLine.append("/AA<<\n"); + aLine.append("/F<</JS(AFDate_FormatEx\\(\""); + aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US)); + aLine.append("\"\\);)"); + aLine.append("/S/JavaScript>>\n"); + aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\""); + aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US)); + aLine.append("\"\\);)"); + aLine.append("/S/JavaScript>>\n"); + aLine.append(">>\n"); + } + } + if( rWidget.m_eType == PDFWriter::PushButton ) + { + if(!m_bIsPDF_A1) + { + OStringBuffer aDest; + if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) ) + { + aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " ); + aLine.append( aDest ); + aLine.append( ">>>>\n" ); + } + else if( rWidget.m_aListEntries.empty() ) + { + if( !m_bIsPDF_A2 && !m_bIsPDF_A3 ) + { + // create a reset form action + aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" ); + } + } + else if( rWidget.m_bSubmit ) + { + // create a submit form action + aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" ); + appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() ); + aLine.append( "/Flags " ); + + sal_Int32 nFlags = 0; + switch( m_aContext.SubmitFormat ) + { + case PDFWriter::HTML: + nFlags |= 4; + break; + case PDFWriter::XML: + nFlags |= 32; + break; + case PDFWriter::PDF: + nFlags |= 256; + break; + case PDFWriter::FDF: + default: + break; + } + if( rWidget.m_bSubmitGet ) + nFlags |= 8; + aLine.append( nFlags ); + aLine.append( ">>>>\n" ); + } + else + { + // create a URI action + aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" ); + aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) ); + aLine.append( ")>>>>\n" ); + } + } + else + m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA ); + } + if( !rWidget.m_aDAString.isEmpty() ) + { + if( !rWidget.m_aDRDict.isEmpty() ) + { + aLine.append( "/DR<<" ); + aLine.append( rWidget.m_aDRDict ); + aLine.append( ">>\n" ); + } + else + { + aLine.append( "/DR<</Font<<" ); + appendBuildinFontsToDict( aLine ); + aLine.append( ">>>>\n" ); + } + aLine.append( "/DA" ); + appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine ); + aLine.append( "\n" ); + if( rWidget.m_nTextStyle & DrawTextFlags::Center ) + aLine.append( "/Q 1\n" ); + else if( rWidget.m_nTextStyle & DrawTextFlags::Right ) + aLine.append( "/Q 2\n" ); + } + // appearance characteristics for terminal fields + // which are supposed to have an appearance constructed + // by the viewer application + if( !rWidget.m_aMKDict.isEmpty() ) + { + aLine.append( "/MK<<" ); + aLine.append( rWidget.m_aMKDict ); + //add the CA string, encrypting it + appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine); + aLine.append( ">>\n" ); + } + + CHECK_RETURN( emitAppearances( rWidget, aLine ) ); + + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( updateObject( rWidget.m_nObject ) ); + CHECK_RETURN( writeBuffer( aLine ) ); + } + return true; +} + +bool PDFWriterImpl::emitAnnotations() +{ + if( m_aPages.empty() ) + return false; + + CHECK_RETURN( emitLinkAnnotations() ); + CHECK_RETURN(emitScreenAnnotations()); + CHECK_RETURN( emitNoteAnnotations() ); + CHECK_RETURN( emitWidgetAnnotations() ); + + return true; +} + +class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream > +{ + VclPtr<PDFWriterImpl> m_pWriter; + bool m_bWrite; + public: + explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {} + + virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override + { + if( m_bWrite && aData.hasElements() ) + { + sal_Int32 nBytes = aData.getLength(); + m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes ); + } + } + virtual void SAL_CALL flush() override {} + virtual void SAL_CALL closeOutput() override + { + m_bWrite = false; + } +}; + +bool PDFWriterImpl::emitEmbeddedFiles() +{ + for (auto& rEmbeddedFile : m_aEmbeddedFiles) + { + if (!updateObject(rEmbeddedFile.m_nObject)) + continue; + + sal_Int32 nSizeObject = createObject(); + sal_Int32 nParamsObject = createObject(); + + OStringBuffer aLine; + aLine.append(rEmbeddedFile.m_nObject); + aLine.append(" 0 obj\n"); + aLine.append("<< /Type /EmbeddedFile"); + if (!rEmbeddedFile.m_aSubType.isEmpty()) + { + aLine.append("/Subtype /"); + appendName(rEmbeddedFile.m_aSubType, aLine); + } + aLine.append(" /Length "); + appendObjectReference(nSizeObject, aLine); + aLine.append(" /Params "); + appendObjectReference(nParamsObject, aLine); + aLine.append(">>\nstream\n"); + checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject); + CHECK_RETURN(writeBuffer(aLine)); + disableStreamEncryption(); + aLine.setLength(0); + + sal_Int64 nSize{}; + if (!rEmbeddedFile.m_aDataContainer.isEmpty()) + { + nSize = rEmbeddedFile.m_aDataContainer.getSize(); + CHECK_RETURN(writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize())); + } + else if (rEmbeddedFile.m_pStream) + { + sal_uInt64 nBegin = getCurrentFilePosition(); + css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this)); + rEmbeddedFile.m_pStream->write(xStream); + rEmbeddedFile.m_pStream.reset(); + xStream.clear(); + nSize = sal_Int64(getCurrentFilePosition() - nBegin); + } + aLine.append("\nendstream\nendobj\n\n"); + CHECK_RETURN(writeBuffer(aLine)); + aLine.setLength(0); + + if (!updateObject(nSizeObject)) + return false; + aLine.append(nSizeObject); + aLine.append(" 0 obj\n"); + aLine.append(nSize); + aLine.append("\nendobj\n\n"); + if (!writeBuffer(aLine)) + return false; + aLine.setLength(0); + + if (!updateObject(nParamsObject)) + return false; + aLine.append(nParamsObject); + aLine.append(" 0 obj\n"); + aLine.append("<<"); + aLine.append("/Size "); + aLine.append(nSize); + aLine.append(">>"); + aLine.append("\nendobj\n\n"); + if (!writeBuffer(aLine)) + return false; + } + return true; +} + +#undef CHECK_RETURN +#define CHECK_RETURN( x ) if( !x ) return false + +bool PDFWriterImpl::emitCatalog() +{ + // build page tree + // currently there is only one node that contains all leaves + + // first create a page tree node id + sal_Int32 nTreeNode = createObject(); + + // emit global resource dictionary (page emit needs it) + CHECK_RETURN( emitResources() ); + + // emit all pages + for (auto & page : m_aPages) + if( ! page.emit( nTreeNode ) ) + return false; + + sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations(); + + sal_Int32 nOutlineDict = emitOutline(); + + // emit Output intent + sal_Int32 nOutputIntentObject = emitOutputIntent(); + + // emit metadata + sal_Int32 nMetadataObject = emitDocumentMetadata(); + + sal_Int32 nStructureDict = 0; + if(m_aStructure.size() > 1) + { + removePlaceholderSE(m_aStructure, m_aStructure[0]); + // check if dummy structure containers are needed + addInternalStructureContainer(m_aStructure[0]); + nStructureDict = m_aStructure[0].m_nObject = createObject(); + emitStructure( m_aStructure[ 0 ] ); + } + + // adjust tree node file offset + if( ! updateObject( nTreeNode ) ) + return false; + + // emit tree node + OStringBuffer aLine( 2048 ); + aLine.append( nTreeNode ); + aLine.append( " 0 obj\n" ); + aLine.append( "<</Type/Pages\n" ); + aLine.append( "/Resources " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R\n" ); + + if( m_aPages.empty() ) // sanity check, this should not happen + aLine.append( "/MediaBox[0 0 595 842]\n" ); // default A4 size in pt + + aLine.append("/Kids[ "); + unsigned int i = 0; + for (const auto & page : m_aPages) + { + aLine.append( page.m_nPageObject ); + aLine.append( " 0 R" ); + aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); + ++i; + } + aLine.append( "]\n" + "/Count " ); + aLine.append( static_cast<sal_Int32>(m_aPages.size()) ); + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + + // emit annotation objects + CHECK_RETURN( emitAnnotations() ); + CHECK_RETURN( emitEmbeddedFiles() ); + + // emit attached files + for (auto & rAttachedFile : m_aDocumentAttachedFiles) + { + if (!updateObject(rAttachedFile.mnObjectId)) + return false; + aLine.setLength( 0 ); + + appendObjectID(rAttachedFile.mnObjectId, aLine); + aLine.append("<</Type /Filespec"); + aLine.append("/F<"); + PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine); + aLine.append("> "); + if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) + { + aLine.append("/UF<"); + PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine); + aLine.append("> "); + } + if (!rAttachedFile.maDescription.isEmpty()) + { + aLine.append("/Desc <"); + PDFWriter::AppendUnicodeTextString(rAttachedFile.maDescription, aLine); + aLine.append("> "); + } + aLine.append("/EF <</F "); + appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine); + aLine.append(">>"); + aLine.append(">>\nendobj\n\n"); + CHECK_RETURN( writeBuffer( aLine ) ); + } + + // emit Catalog + m_nCatalogObject = createObject(); + if( ! updateObject( m_nCatalogObject ) ) + return false; + aLine.setLength( 0 ); + aLine.append( m_nCatalogObject ); + aLine.append( " 0 obj\n" + "<</Type/Catalog/Pages " ); + aLine.append( nTreeNode ); + aLine.append( " 0 R\n" ); + + // check if there are named destinations to emit (root must be inside the catalog) + if( nNamedDestinationsDictionary ) + { + aLine.append("/Dests "); + aLine.append( nNamedDestinationsDictionary ); + aLine.append( " 0 R\n" ); + } + + if (!m_aDocumentAttachedFiles.empty()) + { + aLine.append("/Names "); + aLine.append("<</EmbeddedFiles <</Names ["); + for (auto & rAttachedFile : m_aDocumentAttachedFiles) + { + aLine.append('<'); + PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine); + aLine.append('>'); + aLine.append(' '); + appendObjectReference(rAttachedFile.mnObjectId, aLine); + } + aLine.append("]>>>>"); + aLine.append("\n" ); + } + + if( m_aContext.PageLayout != PDFWriter::DefaultLayout ) + switch( m_aContext.PageLayout ) + { + default : + case PDFWriter::SinglePage : + aLine.append( "/PageLayout/SinglePage\n" ); + break; + case PDFWriter::Continuous : + aLine.append( "/PageLayout/OneColumn\n" ); + break; + case PDFWriter::ContinuousFacing : + // the flag m_aContext.FirstPageLeft below is used to set the page on the left side + aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side + break; + } + if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode ) + switch( m_aContext.PDFDocumentMode ) + { + default : + aLine.append( "/PageMode/UseNone\n" ); + break; + case PDFWriter::UseOutlines : + aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open + break; + case PDFWriter::UseThumbs : + aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open + break; + } + else if( m_aContext.OpenInFullScreenMode ) + aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen + + OStringBuffer aInitPageRef; + if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() ) + { + aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject ); + aInitPageRef.append( " 0 R" ); + } + else + aInitPageRef.append( "0" ); + + switch( m_aContext.PDFDocumentAction ) + { + case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default + default: + if( aInitPageRef.getLength() > 1 ) + { + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /XYZ null null 0]\n" ); + } + break; + case PDFWriter::FitInWindow : + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /Fit]\n" ); //Open fit page + break; + case PDFWriter::FitWidth : + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /FitH 842]\n" ); //Open fit width, default A4 height in pt, OK to use hardcoded value here? + break; + case PDFWriter::FitVisible : + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /FitBH 842]\n" ); //Open fit visible, , default A4 height in pt, OK to use hardcoded value here? + break; + case PDFWriter::ActionZoom : + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /XYZ null null " ); + if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 ) + aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 ); + else + aLine.append( "0" ); + aLine.append( "]\n" ); + break; + } + + // viewer preferences, if we had some, then emit + if( m_aContext.HideViewerToolbar || + (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || + m_aContext.HideViewerMenubar || + m_aContext.HideViewerWindowControls || m_aContext.FitWindow || + m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) || + m_aContext.OpenInFullScreenMode ) + { + aLine.append( "/ViewerPreferences<<" ); + if( m_aContext.HideViewerToolbar ) + aLine.append( "/HideToolbar true\n" ); + if( m_aContext.HideViewerMenubar ) + aLine.append( "/HideMenubar true\n" ); + if( m_aContext.HideViewerWindowControls ) + aLine.append( "/HideWindowUI true\n" ); + if( m_aContext.FitWindow ) + aLine.append( "/FitWindow true\n" ); + if( m_aContext.CenterWindow ) + aLine.append( "/CenterWindow true\n" ); + if (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) + aLine.append( "/DisplayDocTitle true\n" ); + if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) + aLine.append( "/Direction/R2L\n" ); + if( m_aContext.OpenInFullScreenMode ) + switch( m_aContext.PDFDocumentMode ) + { + default : + case PDFWriter::ModeDefault : + aLine.append( "/NonFullScreenPageMode/UseNone\n" ); + break; + case PDFWriter::UseOutlines : + aLine.append( "/NonFullScreenPageMode/UseOutlines\n" ); + break; + case PDFWriter::UseThumbs : + aLine.append( "/NonFullScreenPageMode/UseThumbs\n" ); + break; + } + aLine.append( ">>\n" ); + } + + if( nOutlineDict ) + { + aLine.append( "/Outlines " ); + aLine.append( nOutlineDict ); + aLine.append( " 0 R\n" ); + } + if( nStructureDict ) + { + aLine.append( "/StructTreeRoot " ); + aLine.append( nStructureDict ); + aLine.append( " 0 R\n" ); + } + if( !m_aContext.DocumentLocale.Language.isEmpty() ) + { + /* PDF allows only RFC 3066, see above in emitStructure(). */ + LanguageTag aLanguageTag( m_aContext.DocumentLocale); + OUString aLanguage, aScript, aCountry; + aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry); + if (!aLanguage.isEmpty()) + { + OUStringBuffer aLocBuf( 16 ); + aLocBuf.append( aLanguage ); + if( !aCountry.isEmpty() ) + { + aLocBuf.append( '-' ); + aLocBuf.append( aCountry ); + } + aLine.append( "/Lang" ); + appendLiteralStringEncrypt( aLocBuf, m_nCatalogObject, aLine ); + aLine.append( "\n" ); + } + } + if (m_aContext.Tagged) + { + aLine.append( "/MarkInfo<</Marked true>>\n" ); + } + if( !m_aWidgets.empty() ) + { + aLine.append( "/AcroForm<</Fields[\n" ); + int nWidgets = m_aWidgets.size(); + int nOut = 0; + for( int j = 0; j < nWidgets; j++ ) + { + // output only root fields + if( m_aWidgets[j].m_nParent < 1 ) + { + aLine.append( m_aWidgets[j].m_nObject ); + aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " ); + } + } + aLine.append( "\n]" ); + +#if HAVE_FEATURE_NSS + if (m_nSignatureObject != -1) + aLine.append( "/SigFlags 3"); +#endif + + aLine.append( "/DR " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R" ); + // NeedAppearances must not be used if PDF is signed + if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3 +#if HAVE_FEATURE_NSS + || ( m_nSignatureObject != -1 ) +#endif + ) + aLine.append( ">>\n" ); + else + aLine.append( "/NeedAppearances true>>\n" ); + } + + //check if there is a Metadata object + if( nOutputIntentObject ) + { + aLine.append("/OutputIntents["); + aLine.append( nOutputIntentObject ); + aLine.append( " 0 R]" ); + } + + if( nMetadataObject ) + { + aLine.append("/Metadata "); + aLine.append( nMetadataObject ); + aLine.append( " 0 R" ); + } + + aLine.append( ">>\n" + "endobj\n\n" ); + return writeBuffer( aLine ); +} + +#if HAVE_FEATURE_NSS + +bool PDFWriterImpl::emitSignature() +{ + if( !updateObject( m_nSignatureObject ) ) + return false; + + OStringBuffer aLine( 0x5000 ); + aLine.append( m_nSignatureObject ); + aLine.append( " 0 obj\n" ); + aLine.append("<</Contents <" ); + + sal_uInt64 nOffset = ~0U; + CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) ); + + m_nSignatureContentOffset = nOffset + aLine.getLength(); + + // reserve some space for the PKCS#7 object + OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH ); + comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0'); + aLine.append( aContentFiller ); + aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached"); + + if( !m_aContext.DocumentInfo.Author.isEmpty() ) + { + aLine.append( "/Name" ); + appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine ); + } + + aLine.append( " /M "); + appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine ); + + aLine.append( " /ByteRange [ 0 "); + aLine.append( m_nSignatureContentOffset - 1 ); + aLine.append( " " ); + aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 ); + aLine.append( " " ); + + m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength(); + + // mark the last ByteRange no and add some space. Now, we don't know + // how many bytes we need for this ByteRange value + // The real value will be overwritten in the finalizeSignature method + OStringBuffer aByteRangeFiller( 100 ); + comphelper::string::padToLength(aByteRangeFiller, 100, ' '); + aLine.append( aByteRangeFiller ); + aLine.append(" /Filter/Adobe.PPKMS"); + + //emit reason, location and contactinfo + if ( !m_aContext.SignReason.isEmpty() ) + { + aLine.append("/Reason"); + appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine ); + } + + if ( !m_aContext.SignLocation.isEmpty() ) + { + aLine.append("/Location"); + appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine ); + } + + if ( !m_aContext.SignContact.isEmpty() ) + { + aLine.append("/ContactInfo"); + appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine ); + } + + aLine.append(" >>\nendobj\n\n" ); + + return writeBuffer( aLine ); +} + +bool PDFWriterImpl::finalizeSignature() +{ + if (!m_aContext.SignCertificate.is()) + return false; + + // 1- calculate last ByteRange value + sal_uInt64 nOffset = ~0U; + CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) ); + + sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); + + // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position + sal_uInt64 nWritten = 0; + CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) ); + OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]"; + + if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None) + { + CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) ); + return false; + } + + // 3- create the PKCS#7 object using NSS + + // Prepare buffer and calculate PDF file digest + CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) ); + + std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]); + sal_uInt64 bytesRead1; + + //FIXME: Check if hash is calculated from the correct byterange + if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) || + bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1) + { + SAL_WARN("vcl.pdfwriter", "First buffer read failed"); + return false; + } + + std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]); + sal_uInt64 bytesRead2; + + if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) || + osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) || + bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo)) + { + SAL_WARN("vcl.pdfwriter", "Second buffer read failed"); + return false; + } + + OStringBuffer aCMSHexBuffer; + svl::crypto::Signing aSigning(m_aContext.SignCertificate); + aSigning.AddDataRange(buffer1.get(), bytesRead1); + aSigning.AddDataRange(buffer2.get(), bytesRead2); + aSigning.SetSignTSA(m_aContext.SignTSA); + aSigning.SetSignPassword(m_aContext.SignPassword); + if (!aSigning.Sign(aCMSHexBuffer)) + { + SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed"); + return false; + } + + assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH); + + // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object + nWritten = 0; + CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) ); + m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten); + + return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset); +} + +#endif //HAVE_FEATURE_NSS + +sal_Int32 PDFWriterImpl::emitInfoDict( ) +{ + sal_Int32 nObject = createObject(); + + if( updateObject( nObject ) ) + { + OStringBuffer aLine( 1024 ); + aLine.append( nObject ); + aLine.append( " 0 obj\n" + "<<" ); + if( !m_aContext.DocumentInfo.Title.isEmpty() ) + { + aLine.append( "/Title" ); + appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine ); + aLine.append( "\n" ); + } + if( !m_aContext.DocumentInfo.Author.isEmpty() ) + { + aLine.append( "/Author" ); + appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine ); + aLine.append( "\n" ); + } + if( !m_aContext.DocumentInfo.Subject.isEmpty() ) + { + aLine.append( "/Subject" ); + appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine ); + aLine.append( "\n" ); + } + if( !m_aContext.DocumentInfo.Keywords.isEmpty() ) + { + aLine.append( "/Keywords" ); + appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine ); + aLine.append( "\n" ); + } + if( !m_aContext.DocumentInfo.Creator.isEmpty() ) + { + aLine.append( "/Creator" ); + appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine ); + aLine.append( "\n" ); + } + if( !m_aContext.DocumentInfo.Producer.isEmpty() ) + { + aLine.append( "/Producer" ); + appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine ); + aLine.append( "\n" ); + } + + aLine.append( "/CreationDate" ); + appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine ); + aLine.append( ">>\nendobj\n\n" ); + if( ! writeBuffer( aLine ) ) + nObject = 0; + } + else + nObject = 0; + + return nObject; +} + +// Part of this function may be shared with method appendDest. +sal_Int32 PDFWriterImpl::emitNamedDestinations() +{ + sal_Int32 nCount = m_aNamedDests.size(); + if( nCount <= 0 ) + return 0;//define internal error + + //get the object number for all the destinations + sal_Int32 nObject = createObject(); + + if( updateObject( nObject ) ) + { + //emit the dictionary + OStringBuffer aLine( 1024 ); + aLine.append( nObject ); + aLine.append( " 0 obj\n" + "<<" ); + + sal_Int32 nDestID; + for( nDestID = 0; nDestID < nCount; nDestID++ ) + { + const PDFNamedDest& rDest = m_aNamedDests[ nDestID ]; + // In order to correctly function both under an Internet browser and + // directly with a reader (provided the reader has the feature) we + // need to set the name of the destination the same way it will be encoded + // in an Internet link + INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used + aLocalURL.SetMark( rDest.m_aDestName ); + + const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as + // in link creation ( see PDFWriterImpl::emitLinkAnnotations ) + const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; + + aLine.append( '/' ); + appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog ) + aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function + //maps the preceding character properly + aLine.append( rDestPage.m_nPageObject ); + aLine.append( " 0 R" ); + + switch( rDest.m_eType ) + { + case PDFWriter::DestAreaType::XYZ: + default: + aLine.append( "/XYZ " ); + appendFixedInt( rDest.m_aRect.Left(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rDest.m_aRect.Bottom(), aLine ); + aLine.append( " 0" ); + break; + case PDFWriter::DestAreaType::FitRectangle: + aLine.append( "/FitR " ); + appendFixedInt( rDest.m_aRect.Left(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rDest.m_aRect.Top(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rDest.m_aRect.Right(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rDest.m_aRect.Bottom(), aLine ); + break; + } + aLine.append( "]\n" ); + } + + //close + aLine.append( ">>\nendobj\n\n" ); + if( ! writeBuffer( aLine ) ) + nObject = 0; + } + else + nObject = 0; + + return nObject; +} + +// emits the output intent dictionary +sal_Int32 PDFWriterImpl::emitOutputIntent() +{ + if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 ) + return 0; + + //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1 + + OStringBuffer aLine( 1024 ); + sal_Int32 nICCObject = createObject(); + sal_Int32 nStreamLengthObject = createObject(); + + aLine.append( nICCObject ); +// sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16) + aLine.append( " 0 obj\n<</N 3/Length " ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 R" ); + if (!g_bDebugDisableCompression) + aLine.append( "/Filter/FlateDecode" ); + aLine.append( ">>\nstream\n" ); + if ( !updateObject( nICCObject ) ) return 0; + if ( !writeBuffer( aLine ) ) return 0; + //get file position + sal_uInt64 nBeginStreamPos = 0; + if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos)) + return 0; + beginCompression(); + checkAndEnableStreamEncryption( nICCObject ); + cmsHPROFILE hProfile = cmsCreate_sRGBProfile(); + //force ICC profile version 2.1 + cmsSetProfileVersion(hProfile, 2.1); + cmsUInt32Number nBytesNeeded = 0; + cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded); + if (!nBytesNeeded) + return 0; + std::vector<unsigned char> aBuffer(nBytesNeeded); + cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded); + cmsCloseProfile(hProfile); + bool written = writeBufferBytes( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) ); + disableStreamEncryption(); + endCompression(); + + sal_uInt64 nEndStreamPos = 0; + if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None) + return 0; + + if( !written ) + return 0; + if( ! writeBuffer( "\nendstream\nendobj\n\n" ) ) + return 0 ; + aLine.setLength( 0 ); + + //emit the stream length object + if ( !updateObject( nStreamLengthObject ) ) return 0; + aLine.setLength( 0 ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) ); + aLine.append( "\nendobj\n\n" ); + if ( !writeBuffer( aLine ) ) return 0; + aLine.setLength( 0 ); + + //emit the OutputIntent dictionary + sal_Int32 nOIObject = createObject(); + if ( !updateObject( nOIObject ) ) return 0; + aLine.append( nOIObject ); + aLine.append( " 0 obj\n" + "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier"); + + appendLiteralStringEncrypt( std::string_view("sRGB IEC61966-2.1") ,nOIObject, aLine ); + aLine.append("/DestOutputProfile "); + aLine.append( nICCObject ); + aLine.append( " 0 R>>\nendobj\n\n" ); + if ( !writeBuffer( aLine ) ) return 0; + + return nOIObject; +} + +static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta) +{ + if (!aValue.empty()) + { + aMeta = OUStringToOString(comphelper::string::encodeForXml(aValue), RTL_TEXTENCODING_UTF8); + } +} + +static void lcl_assignMeta(const css::uno::Sequence<OUString>& rValues, std::vector<OString>& rMeta) +{ + if (!rValues.hasElements()) + return; + + std::vector<OString> aNewMetaVector; + aNewMetaVector.reserve(rValues.getLength()); + + for (const OUString& rValue : rValues) + { + aNewMetaVector.emplace_back( + OUStringToOString(comphelper::string::encodeForXml(rValue), RTL_TEXTENCODING_UTF8)); + } + + rMeta = std::move(aNewMetaVector); +} + +// emits the document metadata +sal_Int32 PDFWriterImpl::emitDocumentMetadata() +{ + if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 && !m_bIsPDF_UA) + return 0; + + //get the object number for all the destinations + sal_Int32 nObject = createObject(); + + if( updateObject( nObject ) ) + { + pdf::XmpMetadata aMetadata; + + if (m_bIsPDF_A1) + aMetadata.mnPDF_A = 1; + else if (m_bIsPDF_A2) + aMetadata.mnPDF_A = 2; + else if (m_bIsPDF_A3) + aMetadata.mnPDF_A = 3; + + aMetadata.mbPDF_UA = m_bIsPDF_UA; + + lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle); + lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor); + lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject); + lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer); + aMetadata.msPDFVersion = getPDFVersionStr(m_aContext.Version); + lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords); + lcl_assignMeta(m_aContext.DocumentInfo.Contributor, aMetadata.maContributor); + lcl_assignMeta(m_aContext.DocumentInfo.Coverage, aMetadata.msCoverage); + lcl_assignMeta(m_aContext.DocumentInfo.Identifier, aMetadata.msIdentifier); + lcl_assignMeta(m_aContext.DocumentInfo.Publisher, aMetadata.maPublisher); + lcl_assignMeta(m_aContext.DocumentInfo.Relation, aMetadata.maRelation); + lcl_assignMeta(m_aContext.DocumentInfo.Rights, aMetadata.msRights); + lcl_assignMeta(m_aContext.DocumentInfo.Source, aMetadata.msSource); + lcl_assignMeta(m_aContext.DocumentInfo.Type, aMetadata.msType); + lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool); + aMetadata.m_sCreateDate = m_aCreationMetaDateString; + + OStringBuffer aMetadataObj( 1024 ); + + aMetadataObj.append( nObject ); + aMetadataObj.append( " 0 obj\n" ); + + aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " ); + + aMetadataObj.append( sal_Int32(aMetadata.getSize()) ); + aMetadataObj.append( ">>\nstream\n" ); + if ( !writeBuffer( aMetadataObj ) ) + return 0; + //emit the stream + if ( !writeBufferBytes( aMetadata.getData(), aMetadata.getSize() ) ) + return 0; + + if( ! writeBuffer( "\nendstream\nendobj\n\n" ) ) + nObject = 0; + } + else + nObject = 0; + + return nObject; +} + +bool PDFWriterImpl::emitTrailer() +{ + // emit doc info + sal_Int32 nDocInfoObject = emitInfoDict( ); + + sal_Int32 nSecObject = 0; + + if( m_aContext.Encryption.Encrypt() ) + { + //emit the security information + //must be emitted as indirect dictionary object, since + //Acrobat Reader 5 works only with this kind of implementation + nSecObject = createObject(); + + if( updateObject( nSecObject ) ) + { + OStringBuffer aLineS( 1024 ); + aLineS.append( nSecObject ); + aLineS.append( " 0 obj\n" + "<</Filter/Standard/V " ); + // check the version + aLineS.append( "2/Length 128/R 3" ); + + // emit the owner password, must not be encrypted + aLineS.append( "/O(" ); + appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS ); + aLineS.append( ")/U(" ); + appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS ); + aLineS.append( ")/P " );// the permission set + aLineS.append( m_nAccessPermissions ); + aLineS.append( ">>\nendobj\n\n" ); + if( !writeBuffer( aLineS ) ) + nSecObject = 0; + } + else + nSecObject = 0; + } + // emit xref table + // remember start + sal_uInt64 nXRefOffset = 0; + CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) ); + CHECK_RETURN( writeBuffer( "xref\n" ) ); + + sal_Int32 nObjects = m_aObjects.size(); + OStringBuffer aLine; + aLine.append( "0 " ); + aLine.append( static_cast<sal_Int32>(nObjects+1) ); + aLine.append( "\n" ); + aLine.append( "0000000000 65535 f \n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + + for( sal_Int32 i = 0; i < nObjects; i++ ) + { + aLine.setLength( 0 ); + OString aOffset = OString::number( m_aObjects[i] ); + for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ ) + aLine.append( '0' ); + aLine.append( aOffset ); + aLine.append( " 00000 n \n" ); + SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" ); + CHECK_RETURN( writeBuffer( aLine ) ); + } + + // prepare document checksum + OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 ); + ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize()); + for (sal_uInt8 i : nMD5Sum) + appendHex( i, aDocChecksum ); + // document id set in setDocInfo method + // emit trailer + aLine.setLength( 0 ); + aLine.append( "trailer\n" + "<</Size " ); + aLine.append( static_cast<sal_Int32>(nObjects+1) ); + aLine.append( "/Root " ); + aLine.append( m_nCatalogObject ); + aLine.append( " 0 R\n" ); + if( nSecObject ) + { + aLine.append( "/Encrypt "); + aLine.append( nSecObject ); + aLine.append( " 0 R\n" ); + } + if( nDocInfoObject ) + { + aLine.append( "/Info " ); + aLine.append( nDocInfoObject ); + aLine.append( " 0 R\n" ); + } + if( ! m_aContext.Encryption.DocumentIdentifier.empty() ) + { + aLine.append( "/ID [ <" ); + for (auto const& item : m_aContext.Encryption.DocumentIdentifier) + { + appendHex( sal_Int8(item), aLine ); + } + aLine.append( ">\n" + "<" ); + for (auto const& item : m_aContext.Encryption.DocumentIdentifier) + { + appendHex( sal_Int8(item), aLine ); + } + aLine.append( "> ]\n" ); + } + if( !aDocChecksum.isEmpty() ) + { + aLine.append( "/DocChecksum /" ); + aLine.append( aDocChecksum ); + aLine.append( "\n" ); + } + + if (!m_aDocumentAttachedFiles.empty()) + { + aLine.append( "/AdditionalStreams [" ); + for (auto const& rAttachedFile : m_aDocumentAttachedFiles) + { + aLine.append( "/" ); + appendName(rAttachedFile.maMimeType, aLine); + aLine.append(" "); + appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine); + aLine.append("\n"); + } + aLine.append( "]\n" ); + } + + aLine.append( ">>\n" + "startxref\n" ); + aLine.append( static_cast<sal_Int64>(nXRefOffset) ); + aLine.append( "\n" + "%%EOF\n" ); + return writeBuffer( aLine ); +} + +namespace { + +struct AnnotationSortEntry +{ + sal_Int32 nTabOrder; + sal_Int32 nObject; + sal_Int32 nWidgetIndex; + + AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) : + nTabOrder( nTab ), + nObject( nObj ), + nWidgetIndex( nI ) + {} +}; + +struct AnnotSortContainer +{ + o3tl::sorted_vector< sal_Int32 > aObjects; + std::vector< AnnotationSortEntry > aSortedAnnots; +}; + +struct AnnotSorterLess +{ + std::vector<PDFWidget>& m_rWidgets; + + explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {} + + bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight ) + { + if( rLeft.nTabOrder < rRight.nTabOrder ) + return true; + if( rRight.nTabOrder < rLeft.nTabOrder ) + return false; + if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 ) + return false; + if( rRight.nWidgetIndex < 0 ) + return true; + if( rLeft.nWidgetIndex < 0 ) + return false; + // remember: widget rects are in PDF coordinates, so they are ordered down up + if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() > + m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() ) + return true; + if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() > + m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() ) + return false; + if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() < + m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() ) + return true; + return false; + } +}; + +} + +void PDFWriterImpl::sortWidgets() +{ + // sort widget annotations on each page as per their + // TabOrder attribute + std::unordered_map< sal_Int32, AnnotSortContainer > sorted; + int nWidgets = m_aWidgets.size(); + for( int nW = 0; nW < nWidgets; nW++ ) + { + const PDFWidget& rWidget = m_aWidgets[nW]; + if( rWidget.m_nPage >= 0 ) + { + AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ]; + // optimize vector allocation + if( rCont.aSortedAnnots.empty() ) + rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() ); + // insert widget to tab sorter + // RadioButtons are not page annotations, only their individual check boxes are + if( rWidget.m_eType != PDFWriter::RadioButton ) + { + rCont.aObjects.insert( rWidget.m_nObject ); + rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW ); + } + } + } + for (auto & item : sorted) + { + // append entries for non widget annotations + PDFPage& rPage = m_aPages[ item.first ]; + unsigned int nAnnots = rPage.m_aAnnotations.size(); + for( unsigned int nA = 0; nA < nAnnots; nA++ ) + if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end()) + item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 ); + + AnnotSorterLess aLess( m_aWidgets ); + std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess ); + // sanity check + if( item.second.aSortedAnnots.size() == nAnnots) + { + for( unsigned int nA = 0; nA < nAnnots; nA++ ) + rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject; + } + else + { + SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" ); + SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions " + "on page nr " << item.first << ", " << + static_cast<tools::Long>(item.second.aSortedAnnots.size()) << " sorted and " << + static_cast<tools::Long>(nAnnots) << " unsorted"); + } + } + + // FIXME: implement tab order in structure tree for PDF 1.5 +} + +bool PDFWriterImpl::emit() +{ + endPage(); + + // resort structure tree and annotations if necessary + // needed for widget tab order + sortWidgets(); + +#if HAVE_FEATURE_NSS + if( m_aContext.SignPDF ) + { + // sign the document + PDFWriter::SignatureWidget aSignature; + aSignature.Name = "Signature1"; + createControl( aSignature, 0 ); + } +#endif + + // emit catalog + CHECK_RETURN( emitCatalog() ); + +#if HAVE_FEATURE_NSS + if (m_nSignatureObject != -1) // if document is signed, emit sigdict + { + if( !emitSignature() ) + { + m_aErrors.insert( PDFWriter::Error_Signature_Failed ); + return false; + } + } +#endif + + // emit trailer + CHECK_RETURN( emitTrailer() ); + +#if HAVE_FEATURE_NSS + if (m_nSignatureObject != -1) // finalize the signature + { + if( !finalizeSignature() ) + { + m_aErrors.insert( PDFWriter::Error_Signature_Failed ); + return false; + } + } +#endif + + m_aFile.close(); + m_bOpen = false; + + return true; +} + + +sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont ) +{ + Push(); + + SetFont( i_rFont ); + + const LogicalFontInstance* pFontInstance = GetFontInstance(); + const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace(); + sal_Int32 nFontID = 0; + auto it = m_aSystemFonts.find( pFace ); + if( it != m_aSystemFonts.end() ) + nFontID = it->second.m_nNormalFontID; + else + { + nFontID = m_nNextFID++; + m_aSystemFonts[ pFace ] = EmbedFont(); + m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance); + m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID; + } + + Pop(); + return nFontID; +} + +void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId, + const vcl::font::PhysicalFontFace* pFace, + const std::vector<sal_Ucs>& rCodeUnits, + sal_Int32 nGlyphWidth, + sal_uInt8& nMappedGlyph, + sal_Int32& nMappedFontObject) +{ + FontSubset& rSubset = m_aSubsets[ pFace ]; + // search for font specific glyphID + auto it = rSubset.m_aMapping.find( nFontGlyphId ); + if( it != rSubset.m_aMapping.end() ) + { + nMappedFontObject = it->second.m_nFontID; + nMappedGlyph = it->second.m_nSubsetGlyphID; + } + else + { + // create new subset if necessary + if( rSubset.m_aSubsets.empty() + || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) ) + { + rSubset.m_aSubsets.emplace_back( m_nNextFID++ ); + } + + // copy font id + nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID; + // create new glyph in subset + sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1); + nMappedGlyph = nNewId; + + // add new glyph to emitted font subset + GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ]; + rNewGlyphEmit.setGlyphId( nNewId ); + rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth)); + for (const auto nCode : rCodeUnits) + rNewGlyphEmit.addCode(nCode); + + // add new glyph to font mapping + Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ]; + rNewGlyph.m_nFontID = nMappedFontObject; + rNewGlyph.m_nSubsetGlyphID = nNewId; + } +} + +void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId, + const vcl::font::PhysicalFontFace* pFace, + const LogicalFontInstance* pFont, + const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth, + sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject) +{ + auto bVariations = !pFace->GetVariations(*pFont).empty(); + // tdf#155161 + // PDF doesn’t support CFF2 table and we currently don’t convert them to + // Type 1 (like we do with CFF table), so treat it like fonts with + // variations and embed as Type 3 fonts. + if (!pFace->GetRawFontData(HB_TAG('C', 'F', 'F', '2')).empty()) + bVariations = true; + + if (pFace->IsColorFont() || bVariations) + { + // Font has colors, check if this glyph has color layers or bitmap. + tools::Rectangle aRect; + auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId); + auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect); + if (!aLayers.empty() || !aBitmap.empty() || bVariations) + { + auto& rSubset = m_aType3Fonts[pFace]; + auto it = rSubset.m_aMapping.find(nFontGlyphId); + if (it != rSubset.m_aMapping.end()) + { + nMappedFontObject = it->second.m_nFontID; + nMappedGlyph = it->second.m_nSubsetGlyphID; + } + else + { + // create new subset if necessary + if (rSubset.m_aSubsets.empty() + || (rSubset.m_aSubsets.back().m_aMapping.size() > 254)) + { + rSubset.m_aSubsets.emplace_back(m_nNextFID++); + } + + // copy font id + nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID; + // create new glyph in subset + sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>( + rSubset.m_aSubsets.back().m_aMapping.size() + 1); + nMappedGlyph = nNewId; + + // add new glyph to emitted font subset + auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId]; + rNewGlyphEmit.setGlyphId(nNewId); + rNewGlyphEmit.setGlyphWidth(nGlyphWidth); + for (const auto nCode : rCodeUnits) + rNewGlyphEmit.addCode(nCode); + + // add color layers to the glyphs + if (!aLayers.empty()) + { + for (const auto& aLayer : aLayers) + { + sal_uInt8 nLayerGlyph; + sal_Int32 nLayerFontID; + registerSimpleGlyph(aLayer.nGlyphIndex, pFace, rCodeUnits, nGlyphWidth, + nLayerGlyph, nLayerFontID); + + rNewGlyphEmit.addColorLayer( + { nLayerFontID, nLayerGlyph, aLayer.nColorIndex }); + } + } + else if (!aBitmap.empty()) + rNewGlyphEmit.setColorBitmap(aBitmap, aRect); + else if (bVariations) + rNewGlyphEmit.setOutline(pFont->GetGlyphOutlineUntransformed(nFontGlyphId)); + + // add new glyph to font mapping + Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId]; + rNewGlyph.m_nFontID = nMappedFontObject; + rNewGlyph.m_nSubsetGlyphID = nNewId; + } + return; + } + } + + // If we reach here then the glyph has no color layers. + registerSimpleGlyph(nFontGlyphId, pFace, rCodeUnits, nGlyphWidth, nMappedGlyph, + nMappedFontObject); +} + +void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines ) +{ + push( PushFlags::ALL ); + + FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief(); + + Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor(); + Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor; + Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; + Color aReliefColor( COL_LIGHTGRAY ); + if( aTextColor == COL_BLACK ) + aTextColor = COL_WHITE; + if( aTextLineColor == COL_BLACK ) + aTextLineColor = COL_WHITE; + if( aOverlineColor == COL_BLACK ) + aOverlineColor = COL_WHITE; + // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct + if( aTextColor == COL_WHITE ) + aReliefColor = COL_BLACK; + + Font aSetFont = m_aCurrentPDFState.m_aFont; + aSetFont.SetRelief( FontRelief::NONE ); + aSetFont.SetShadow( false ); + + aSetFont.SetColor( aReliefColor ); + setTextLineColor( aReliefColor ); + setOverlineColor( aReliefColor ); + setFont( aSetFont ); + tools::Long nOff = 1 + GetDPIX()/300; + if( eRelief == FontRelief::Engraved ) + nOff = -nOff; + + rLayout.DrawOffset() += Point( nOff, nOff ); + updateGraphicsState(); + drawLayout( rLayout, rText, bTextLines ); + + rLayout.DrawOffset() -= Point( nOff, nOff ); + setTextLineColor( aTextLineColor ); + setOverlineColor( aOverlineColor ); + aSetFont.SetColor( aTextColor ); + setFont( aSetFont ); + updateGraphicsState(); + drawLayout( rLayout, rText, bTextLines ); + + // clean up the mess + pop(); +} + +void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines ) +{ + Font aSaveFont = m_aCurrentPDFState.m_aFont; + Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor; + Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor; + + Font& rFont = m_aCurrentPDFState.m_aFont; + if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 ) + rFont.SetColor( COL_LIGHTGRAY ); + else + rFont.SetColor( COL_BLACK ); + rFont.SetShadow( false ); + rFont.SetOutline( false ); + setFont( rFont ); + setTextLineColor( rFont.GetColor() ); + setOverlineColor( rFont.GetColor() ); + updateGraphicsState(); + + tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24); + if( rFont.IsOutline() ) + nOff++; + rLayout.DrawBase() += basegfx::B2DPoint(nOff, nOff); + drawLayout( rLayout, rText, bTextLines ); + rLayout.DrawBase() -= basegfx::B2DPoint(nOff, nOff); + + setFont( aSaveFont ); + setTextLineColor( aSaveTextLineColor ); + setOverlineColor( aSaveOverlineColor ); + updateGraphicsState(); +} + +void PDFWriterImpl::drawVerticalGlyphs( + const std::vector<PDFGlyph>& rGlyphs, + OStringBuffer& rLine, + const Point& rAlignOffset, + const Matrix3& rRotScale, + double fAngle, + double fXScale, + sal_Int32 nFontHeight) +{ + double nXOffset = 0; + Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos)); + aCurPos += rAlignOffset; + for( size_t i = 0; i < rGlyphs.size(); i++ ) + { + // have to emit each glyph on its own + double fDeltaAngle = 0.0; + double fYScale = 1.0; + double fTempXScale = fXScale; + + // perform artificial italics if necessary + double fSkew = 0.0; + if (rGlyphs[i].m_pFont->NeedsArtificialItalic()) + fSkew = ARTIFICIAL_ITALIC_SKEW; + + double fSkewB = fSkew; + double fSkewA = 0.0; + + Point aDeltaPos; + if (rGlyphs[i].m_pGlyph->IsVertical()) + { + fDeltaAngle = M_PI/2.0; + fYScale = fXScale; + fTempXScale = 1.0; + fSkewA = -fSkewB; + fSkewB = 0.0; + } + aDeltaPos += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint()); + if( i < rGlyphs.size()-1 ) + // #i120627# the text on the Y axis is reversed when export ppt file to PDF format + { + double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX(); + double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY(); + nXOffset += std::hypot(nOffsetX, nOffsetY); + } + if (!rGlyphs[i].m_pGlyph->glyphId()) + continue; + + aDeltaPos = rRotScale.transform( aDeltaPos ); + + Matrix3 aMat; + if( fSkewB != 0.0 || fSkewA != 0.0 ) + aMat.skew( fSkewA, fSkewB ); + aMat.scale( fTempXScale, fYScale ); + aMat.rotate( fAngle+fDeltaAngle ); + aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() ); + m_aPages.back().appendMatrix3(aMat, rLine); + rLine.append( " Tm" ); + if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId ) + { + rLine.append( " /F" ); + rLine.append( rGlyphs[i].m_nMappedFontId ); + rLine.append( ' ' ); + m_aPages.back().appendMappedLength( nFontHeight, rLine ); + rLine.append( " Tf" ); + } + rLine.append( "<" ); + appendHex( rGlyphs[i].m_nMappedGlyphId, rLine ); + rLine.append( ">Tj\n" ); + } +} + +void PDFWriterImpl::drawHorizontalGlyphs( + const std::vector<PDFGlyph>& rGlyphs, + OStringBuffer& rLine, + const Point& rAlignOffset, + bool bFirst, + double fAngle, + double fXScale, + sal_Int32 nFontHeight, + sal_Int32 nPixelFontHeight) +{ + // horizontal (= normal) case + + // fill in run end indices + // end is marked by index of the first glyph of the next run + // a run is marked by same mapped font id and same Y position + std::vector< sal_uInt32 > aRunEnds; + aRunEnds.reserve( rGlyphs.size() ); + for( size_t i = 1; i < rGlyphs.size(); i++ ) + { + if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId || + rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont || + rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() ) + { + aRunEnds.push_back(i); + } + } + // last run ends at last glyph + aRunEnds.push_back( rGlyphs.size() ); + + // loop over runs of the same font + sal_uInt32 nBeginRun = 0; + for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ ) + { + // setup text matrix back transformed to current coordinate system + Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos)); + aCurPos += rAlignOffset; + + // perform artificial italics if necessary + double fSkew = 0.0; + if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic()) + fSkew = ARTIFICIAL_ITALIC_SKEW; + + // the first run can be set with "Td" operator + // subsequent use of that operator would move + // the textline matrix relative to what was set before + // making use of that would drive us into rounding issues + Matrix3 aMat; + if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 ) + { + m_aPages.back().appendPoint( aCurPos, rLine ); + rLine.append( " Td " ); + } + else + { + if( fSkew != 0.0 ) + aMat.skew( 0.0, fSkew ); + aMat.scale( fXScale, 1.0 ); + aMat.rotate( fAngle ); + aMat.translate( aCurPos.X(), aCurPos.Y() ); + m_aPages.back().appendMatrix3(aMat, rLine); + rLine.append( " Tm\n" ); + } + // set up correct font + rLine.append( "/F" ); + rLine.append( rGlyphs[nBeginRun].m_nMappedFontId ); + rLine.append( ' ' ); + m_aPages.back().appendMappedLength( nFontHeight, rLine ); + rLine.append( " Tf" ); + + // output glyphs using Tj or TJ + OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 ); + aKernedLine.append( "[<" ); + aUnkernedLine.append( '<' ); + appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine ); + appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine ); + + aMat.invert(); + bool bNeedKern = false; + for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ ) + { + appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine ); + // check if default glyph positioning is sufficient + const basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos ); + const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos ); + double fAdvance = aThisPos.getX() - aPrevPos.getX(); + fAdvance *= 1000.0 / nPixelFontHeight; + const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5; + SAL_WARN_IF( + fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter", + "adjustment " << fAdjustment << " outside 32-bit int"); + const sal_Int32 nAdjustment = static_cast<sal_Int32>( + std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32))); + if( nAdjustment != 0 ) + { + // apply individual glyph positioning + bNeedKern = true; + aKernedLine.append( ">" ); + aKernedLine.append( nAdjustment ); + aKernedLine.append( "<" ); + } + appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine ); + } + aKernedLine.append( ">]TJ\n" ); + aUnkernedLine.append( ">Tj\n" ); + rLine.append( bNeedKern ? aKernedLine : aUnkernedLine ); + + // set beginning of next run + nBeginRun = aRunEnds[nRun]; + } +} + +void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines ) +{ + // relief takes precedence over shadow (see outdev3.cxx) + if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE ) + { + drawRelief( rLayout, rText, bTextLines ); + return; + } + else if( m_aCurrentPDFState.m_aFont.IsShadow() ) + drawShadow( rLayout, rText, bTextLines ); + + OStringBuffer aLine( 512 ); + + const int nMaxGlyphs = 256; + + std::vector<sal_Ucs> aCodeUnits; + bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical(); + int nIndex = 0; + double fXScale = 1.0; + sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight; + TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment(); + + // transform font height back to current units + // note: the layout calculates in outdevs device pixel !! + sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight ); + if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() ) + { + Font aFont( m_aCurrentPDFState.m_aFont ); + aFont.SetAverageFontWidth( 0 ); + FontMetric aMetric = GetFontMetric( aFont ); + if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() ) + { + fXScale = + static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) / + static_cast<double>(aMetric.GetAverageFontWidth()); + } + } + + // if the mapmode is distorted we need to adjust for that also + if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() ) + { + fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY()); + } + + Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation(); + // normalize angles + while( nAngle < 0_deg10 ) + nAngle += 3600_deg10; + nAngle = nAngle % 3600_deg10; + double fAngle = toRadians(nAngle); + + Matrix3 aRotScale; + aRotScale.scale( fXScale, 1.0 ); + if( fAngle != 0.0 ) + aRotScale.rotate( -fAngle ); + + bool bPop = false; + bool bABold = false; + // artificial bold necessary ? + if (GetFontInstance()->NeedsArtificialBold()) + { + aLine.append("q "); + bPop = true; + bABold = true; + } + // setup text colors (if necessary) + Color aStrokeColor( COL_TRANSPARENT ); + Color aNonStrokeColor( COL_TRANSPARENT ); + + if( m_aCurrentPDFState.m_aFont.IsOutline() ) + { + aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); + aNonStrokeColor = COL_WHITE; + } + else + aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); + if( bABold ) + aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); + + if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor ) + { + if( ! bPop ) + aLine.append( "q " ); + bPop = true; + appendStrokingColor( aStrokeColor, aLine ); + aLine.append( "\n" ); + } + if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor ) + { + if( ! bPop ) + aLine.append( "q " ); + bPop = true; + appendNonStrokingColor( aNonStrokeColor, aLine ); + aLine.append( "\n" ); + } + + // begin text object + aLine.append( "BT\n" ); + // outline attribute ? + if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold ) + { + // set correct text mode, set stroke width + aLine.append( "2 Tr " ); // fill, then stroke + + if( m_aCurrentPDFState.m_aFont.IsOutline() ) + { + // unclear what to do in case of outline and artificial bold + // for the time being outline wins + aLine.append( "0.25 w \n" ); + } + else + { + double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0; + m_aPages.back().appendMappedLength( fW, aLine ); + aLine.append ( " w\n" ); + } + } + + FontMetric aRefDevFontMetric = GetFontMetric(); + const GlyphItem* pGlyph = nullptr; + const LogicalFontInstance* pGlyphFont = nullptr; + + // collect the glyphs into a single array + std::vector< PDFGlyph > aGlyphs; + aGlyphs.reserve( nMaxGlyphs ); + // first get all the glyphs and register them; coordinates still in Pixel + basegfx::B2DPoint aPos; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont)) + { + const auto* pFace = pGlyphFont->GetFontFace(); + + aCodeUnits.clear(); + + // tdf#66597, tdf#115117 + // + // Here is how we embed textual content in PDF files, to allow for + // better text extraction for complex and typography-rich text. + // + // * If there is many to one or many to many mapping, use an + // ActualText span embedding the original string, since ToUnicode + // can't handle these. + // * If the one glyph is used for several Unicode code points, also + // use ActualText since ToUnicode can map each glyph in the font + // only once. + // * Limit ActualText to single cluster at a time, since using it + // for whole words or sentences breaks text selection and + // highlighting in PDF viewers (there will be no way to tell + // which glyphs belong to which characters). + // * Keep generating (now) redundant ToUnicode entries for + // compatibility with old tools not supporting ActualText. + + assert(pGlyph->charCount() >= 0); + for (int n = 0; n < pGlyph->charCount(); n++) + aCodeUnits.push_back(rText[pGlyph->charPos() + n]); + + bool bUseActualText = false; + + // If this is a start of complex cluster, use ActualText. + if (pGlyph->IsClusterStart()) + bUseActualText = true; + + const auto nGlyphId = pGlyph->glyphId(); + + // A glyph can't have more than one ToUnicode entry, use ActualText + // instead. + if (!aCodeUnits.empty() && !bUseActualText) + { + for (const auto& rSubset : m_aSubsets[pFace].m_aSubsets) + { + const auto& it = rSubset.m_aMapping.find(nGlyphId); + if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits) + { + bUseActualText = true; + aCodeUnits.clear(); + } + } + } + + auto nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, pGlyph->IsVertical(), false); + + sal_uInt8 nMappedGlyph; + sal_Int32 nMappedFontObject; + registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nGlyphWidth, nMappedGlyph, nMappedFontObject); + + int nCharPos = -1; + if (bUseActualText || pGlyph->IsInCluster()) + nCharPos = pGlyph->charPos(); + + aGlyphs.emplace_back(aPos, + pGlyph, + pGlyphFont, + XUnits(pFace->UnitsPerEm(), nGlyphWidth), + nMappedFontObject, + nMappedGlyph, + nCharPos); + } + + // Avoid fill color when map mode is in pixels, the below code assumes + // logic map mode. + bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel; + if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel) + { + // PDF doesn't have a text fill color, so draw a rectangle before + // drawing the actual text. + push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR); + setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor()); + // Avoid border around the rectangle for Writer shape text. + setLineColor(COL_TRANSPARENT); + + // The rectangle is the bounding box of the text, but also includes + // ascent / descent to match the on-screen rendering. + // This is the top left of the text without ascent / descent. + basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition()); + tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition), + Size(ImplDevicePixelToLogicWidth(rLayout.GetTextWidth()), 0)); + aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent()); + // This includes ascent / descent. + aRectangle.setHeight(aRefDevFontMetric.GetLineHeight()); + + const LogicalFontInstance* pFontInstance = GetFontInstance(); + if (pFontInstance->mnOrientation) + { + // Adapt rectangle for rotated text. + tools::Polygon aPolygon(aRectangle); + aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation); + drawPolygon(aPolygon); + } + else + drawRectangle(aRectangle); + + pop(); + } + + Point aAlignOffset; + if ( eAlign == ALIGN_BOTTOM ) + aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) ); + else if ( eAlign == ALIGN_TOP ) + aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() ); + if( aAlignOffset.X() || aAlignOffset.Y() ) + aAlignOffset = aRotScale.transform( aAlignOffset ); + + /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original + string contained only one of the UTF16 BOMs + */ + if( ! aGlyphs.empty() ) + { + size_t nStart = 0; + size_t nEnd = 0; + while (nStart < aGlyphs.size()) + { + while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos) + nEnd++; + + std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd); + + int nCharPos, nCharCount; + if (!aRun.front().m_pGlyph->IsRTLGlyph()) + { + nCharPos = aRun.front().m_nCharPos; + nCharCount = aRun.front().m_pGlyph->charCount(); + } + else + { + nCharPos = aRun.back().m_nCharPos; + nCharCount = aRun.back().m_pGlyph->charCount(); + } + + if (nCharPos >= 0 && nCharCount) + { + aLine.append("/Span<</ActualText<FEFF"); + for (int i = 0; i < nCharCount; i++) + { + sal_Unicode aChar = rText[nCharPos + i]; + appendHex(static_cast<sal_Int8>(aChar >> 8), aLine); + appendHex(static_cast<sal_Int8>(aChar & 255), aLine); + } + aLine.append( ">>>\nBDC\n" ); + } + + if (bVertical) + drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight); + else + drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight); + + if (nCharPos >= 0 && nCharCount) + aLine.append( "EMC\n" ); + + nStart = nEnd; + } + } + + // end textobject + aLine.append( "ET\n" ); + if( bPop ) + aLine.append( "Q\n" ); + + writeBuffer( aLine ); + + // draw eventual textlines + FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout(); + FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline(); + FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline(); + if( bTextLines && + ( + ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) || + ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) || + ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW ) + ) + ) + { + bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove(); + if( m_aCurrentPDFState.m_aFont.IsWordLineMode() ) + { + basegfx::B2DPoint aStartPt; + double nWidth = 0; + nIndex = 0; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex)) + { + if (!pGlyph->IsSpacing()) + { + if( !nWidth ) + aStartPt = aPos; + + nWidth += pGlyph->newWidth(); + } + else if( nWidth > 0 ) + { + drawTextLine( SubPixelToLogic(aStartPt), + ImplDevicePixelToLogicWidth( nWidth ), + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + nWidth = 0; + } + } + + if( nWidth > 0 ) + { + drawTextLine( SubPixelToLogic(aStartPt), + ImplDevicePixelToLogicWidth( nWidth ), + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } + } + else + { + basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition(); + int nWidth = rLayout.GetTextWidth(); + drawTextLine( SubPixelToLogic(aStartPt), + ImplDevicePixelToLogicWidth( nWidth ), + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } + } + + // write eventual emphasis marks + if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) ) + return; + + push( PushFlags::ALL ); + + aLine.setLength( 0 ); + aLine.append( "q\n" ); + + FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle(); + + tools::Long nEmphHeight; + if ( nEmphMark & FontEmphasisMark::PosBelow ) + nEmphHeight = GetEmphasisDescent(); + else + nEmphHeight = GetEmphasisAscent(); + + vcl::font::EmphasisMark aEmphasisMark(nEmphMark, ImplDevicePixelToLogicWidth(nEmphHeight), GetDPIY()); + if ( aEmphasisMark.IsShapePolyLine() ) + { + setLineColor( m_aCurrentPDFState.m_aFont.GetColor() ); + setFillColor( COL_TRANSPARENT ); + } + else + { + setFillColor( m_aCurrentPDFState.m_aFont.GetColor() ); + setLineColor( COL_TRANSPARENT ); + } + + writeBuffer( aLine ); + + Point aOffset(0,0); + Point aOffsetVert(0,0); + + if ( nEmphMark & FontEmphasisMark::PosBelow ) + { + aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() ); + aOffsetVert = aOffset; + } + else + { + aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) ); + // Todo: use ideographic em-box or ideographic character face information. + aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() + + GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset())); + } + + tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2; + tools::Long nEmphHeight2 = nEmphHeight / 2; + aOffset += Point( nEmphWidth2, nEmphHeight2 ); + + if ( eAlign == ALIGN_BOTTOM ) + aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) ); + else if ( eAlign == ALIGN_TOP ) + aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() ); + + tools::Rectangle aRectangle; + nIndex = 0; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont)) + { + if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle)) + continue; + + if (!pGlyph->IsSpacing()) + { + basegfx::B2DPoint aAdjOffset; + if (pGlyph->IsVertical()) + { + aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y()); + aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2); + } + else + { + aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y()); + aAdjOffset.adjustX(aRectangle.Left() + (aRectangle.GetWidth() - aEmphasisMark.GetWidth()) / 2 ); + } + + aAdjOffset = aRotScale.transform( aAdjOffset ); + + aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2); + + basegfx::B2DPoint aMarkDevPos(aPos); + aMarkDevPos += aAdjOffset; + Point aMarkPos = SubPixelToLogic(aMarkDevPos); + drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(), + aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(), + aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() ); + } + } + + writeBuffer( "Q\n" ); + pop(); + +} + +void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY, + const tools::PolyPolygon& rPolyPoly, bool bPolyLine, + const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 ) +{ + // TODO: pass nWidth as width of this mark + // long nWidth = 0; + + if ( rPolyPoly.Count() ) + { + if ( bPolyLine ) + { + tools::Polygon aPoly = rPolyPoly.GetObject( 0 ); + aPoly.Move( nX, nY ); + drawPolyLine( aPoly ); + } + else + { + tools::PolyPolygon aPolyPoly = rPolyPoly; + aPolyPoly.Move( nX, nY ); + drawPolyPolygon( aPolyPoly ); + } + } + + if ( !rRect1.IsEmpty() ) + { + tools::Rectangle aRect( Point( nX+rRect1.Left(), + nY+rRect1.Top() ), rRect1.GetSize() ); + drawRectangle( aRect ); + } + + if ( !rRect2.IsEmpty() ) + { + tools::Rectangle aRect( Point( nX+rRect2.Left(), + nY+rRect2.Top() ), rRect2.GetSize() ); + + drawRectangle( aRect ); + } +} + +void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines ) +{ + MARK( "drawText" ); + + updateGraphicsState(); + + // get a layout from the OutputDevice's SalGraphics + // this also enforces font substitution and sets the font on SalGraphics + const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()-> + GetLayoutGlyphs( this, rText, nIndex, nLen ); + std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, + 0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs ); + if( pLayout ) + { + drawLayout( *pLayout, rText, bTextLines ); + } +} + +void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen ) +{ + MARK( "drawText with array" ); + + updateGraphicsState(); + + // get a layout from the OutputDevice's SalGraphics + // this also enforces font substitution and sets the font on SalGraphics + const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()-> + GetLayoutGlyphs( this, rText, nIndex, nLen ); + std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray, + SalLayoutFlags::NONE, nullptr, layoutGlyphs ); + if( pLayout ) + { + drawLayout( *pLayout, rText, true ); + } +} + +void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen ) +{ + MARK( "drawStretchText" ); + + updateGraphicsState(); + + // get a layout from the OutputDevice's SalGraphics + // this also enforces font substitution and sets the font on SalGraphics + const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()-> + GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth ); + std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth, + {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs ); + if( pLayout ) + { + drawLayout( *pLayout, rText, true ); + } +} + +void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle ) +{ + tools::Long nWidth = rRect.GetWidth(); + tools::Long nHeight = rRect.GetHeight(); + + if ( nWidth <= 0 || nHeight <= 0 ) + return; + + MARK( "drawText with rectangle" ); + + updateGraphicsState(); + + // clip with rectangle + OStringBuffer aLine; + aLine.append( "q " ); + m_aPages.back().appendRect( rRect, aLine ); + aLine.append( " W* n\n" ); + writeBuffer( aLine ); + + // if disabled text is needed, put in here + + Point aPos = rRect.TopLeft(); + + tools::Long nTextHeight = GetTextHeight(); + sal_Int32 nMnemonicPos = -1; + + OUString aStr = rOrigStr; + if ( nStyle & DrawTextFlags::Mnemonic ) + aStr = removeMnemonicFromString( aStr, nMnemonicPos ); + + // multiline text + if ( nStyle & DrawTextFlags::MultiLine ) + { + ImplMultiTextLineInfo aMultiLineInfo; + sal_Int32 i; + sal_Int32 nFormatLines; + + if ( nTextHeight ) + { + vcl::DefaultTextLayout aLayout( *this ); + OUString aLastLine; + aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle ); + sal_Int32 nLines = nHeight/nTextHeight; + nFormatLines = aMultiLineInfo.Count(); + if ( !nLines ) + nLines = 1; + if ( nFormatLines > nLines ) + { + if ( nStyle & DrawTextFlags::EndEllipsis ) + { + // handle last line + nFormatLines = nLines-1; + + ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines ); + aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF); + // replace line feed by space + aLastLine = aLastLine.replace('\n', ' '); + aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle ); + nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom); + nStyle |= DrawTextFlags::Top; + } + } + + // vertical alignment + if ( nStyle & DrawTextFlags::Bottom ) + aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) ); + else if ( nStyle & DrawTextFlags::VCenter ) + aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 ); + + // draw all lines excluding the last + for ( i = 0; i < nFormatLines; i++ ) + { + ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); + if ( nStyle & DrawTextFlags::Right ) + aPos.AdjustX(nWidth-rLineInfo.GetWidth() ); + else if ( nStyle & DrawTextFlags::Center ) + aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 ); + sal_Int32 nIndex = rLineInfo.GetIndex(); + sal_Int32 nLineLen = rLineInfo.GetLen(); + drawText( aPos, aStr, nIndex, nLineLen ); + // mnemonics should not appear in documents, + // if the need arises, put them in here + aPos.AdjustY(nTextHeight ); + aPos.setX( rRect.Left() ); + } + + // output last line left adjusted since it was shortened + if (!aLastLine.isEmpty()) + drawText( aPos, aLastLine, 0, aLastLine.getLength() ); + } + } + else + { + tools::Long nTextWidth = GetTextWidth( aStr ); + + // Evt. Text kuerzen + if ( nTextWidth > nWidth ) + { + if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) ) + { + aStr = GetEllipsisString( aStr, nWidth, nStyle ); + nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right); + nStyle |= DrawTextFlags::Left; + nTextWidth = GetTextWidth( aStr ); + } + } + + // vertical alignment + if ( nStyle & DrawTextFlags::Right ) + aPos.AdjustX(nWidth-nTextWidth ); + else if ( nStyle & DrawTextFlags::Center ) + aPos.AdjustX((nWidth-nTextWidth)/2 ); + + if ( nStyle & DrawTextFlags::Bottom ) + aPos.AdjustY(nHeight-nTextHeight ); + else if ( nStyle & DrawTextFlags::VCenter ) + aPos.AdjustY((nHeight-nTextHeight)/2 ); + + // mnemonics should be inserted here if the need arises + + // draw the actual text + drawText( aPos, aStr, 0, aStr.getLength() ); + } + + // reset clip region to original value + aLine.setLength( 0 ); + aLine.append( "Q\n" ); + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop ) +{ + MARK( "drawLine" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) + return; + + OStringBuffer aLine; + m_aPages.back().appendPoint( rStart, aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( rStop, aLine ); + aLine.append( " l S\n" ); + + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo ) +{ + MARK( "drawLine with LineInfo" ); + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) + return; + + if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 ) + { + drawLine( rStart, rStop ); + return; + } + + OStringBuffer aLine; + + aLine.append( "q " ); + if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) + { + m_aPages.back().appendPoint( rStart, aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( rStop, aLine ); + aLine.append( " l S Q\n" ); + + writeBuffer( aLine ); + } + else + { + PDFWriter::ExtLineInfo aInfo; + convertLineInfoToExtLineInfo( rInfo, aInfo ); + Point aPolyPoints[2] = { rStart, rStop }; + tools::Polygon aPoly( 2, aPolyPoints ); + drawPolyLine( aPoly, aInfo ); + } +} + +#define HCONV( x ) ImplDevicePixelToLogicHeight( x ) + +void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove ) +{ + // note: units in pFontInstance are ref device pixel + const LogicalFontInstance* pFontInstance = GetFontInstance(); + tools::Long nLineHeight = 0; + tools::Long nLinePos = 0; + + appendStrokingColor( aColor, aLine ); + aLine.append( "\n" ); + + if ( bIsAbove ) + { + if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() ) + ImplInitAboveTextLineSize(); + nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() ); + nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() ); + } + else + { + if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() ) + ImplInitTextLineSize(); + nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() ); + nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() ); + } + if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) ) + nLineHeight = 3; + + tools::Long nLineWidth = GetDPIX()/450; + if ( ! nLineWidth ) + nLineWidth = 1; + + if ( eTextLine == LINESTYLE_BOLDWAVE ) + nLineWidth = 3*nLineWidth; + + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine ); + aLine.append( " w " ); + + if ( eTextLine == LINESTYLE_DOUBLEWAVE ) + { + tools::Long nOrgLineHeight = nLineHeight; + nLineHeight /= 3; + if ( nLineHeight < 2 ) + { + if ( nOrgLineHeight > 1 ) + nLineHeight = 2; + else + nLineHeight = 1; + } + tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2); + if ( nLineDY < nLineWidth ) + nLineDY = nLineWidth; + tools::Long nLineDY2 = nLineDY/2; + if ( !nLineDY2 ) + nLineDY2 = 1; + + nLinePos -= nLineWidth-nLineDY2; + + m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); + + nLinePos += nLineWidth+nLineDY; + m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); + } + else + { + if ( eTextLine != LINESTYLE_BOLDWAVE ) + nLinePos -= nLineWidth/2; + m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine ); + } +} + +void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove ) +{ + // note: units in pFontInstance are ref device pixel + const LogicalFontInstance* pFontInstance = GetFontInstance(); + tools::Long nLineHeight = 0; + tools::Long nLinePos = 0; + tools::Long nLinePos2 = 0; + + if ( eTextLine > LINESTYLE_BOLDWAVE ) + eTextLine = LINESTYLE_SINGLE; + + switch ( eTextLine ) + { + case LINESTYLE_SINGLE: + case LINESTYLE_DOTTED: + case LINESTYLE_DASH: + case LINESTYLE_LONGDASH: + case LINESTYLE_DASHDOT: + case LINESTYLE_DASHDOTDOT: + if ( bIsAbove ) + { + if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() ) + ImplInitAboveTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetAboveUnderlineOffset(); + } + else + { + if ( !pFontInstance->mxFontMetric->GetUnderlineSize() ) + ImplInitTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetUnderlineOffset(); + } + break; + case LINESTYLE_BOLD: + case LINESTYLE_BOLDDOTTED: + case LINESTYLE_BOLDDASH: + case LINESTYLE_BOLDLONGDASH: + case LINESTYLE_BOLDDASHDOT: + case LINESTYLE_BOLDDASHDOTDOT: + if ( bIsAbove ) + { + if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() ) + ImplInitAboveTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset(); + } + else + { + if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() ) + ImplInitTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetBoldUnderlineOffset(); + } + break; + case LINESTYLE_DOUBLE: + if ( bIsAbove ) + { + if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() ) + ImplInitAboveTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1(); + nLinePos2 = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2(); + } + else + { + if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() ) + ImplInitTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1(); + nLinePos2 = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2(); + } + break; + default: + break; + } + + if ( !nLineHeight ) + return; + + // tdf#154235 + // nLinePos/nLinePos2 is the distance from baseline to the top of the line, + // while in PDF we stroke the line so the position is to the middle of the + // line, we add half of nLineHeight to account for that. + auto nOffset = nLineHeight / 2; + if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE) + { + // Except when outlining, as now we are drawing a rectangle, so + // nLinePos is the bottom of the rectangle, so need to add nLineHeight + // to it. + nOffset = nLineHeight; + } + + nLineHeight = HCONV(nLineHeight); + nLinePos = HCONV(nLinePos + nOffset); + nLinePos2 = HCONV(nLinePos2 + nOffset); + + // outline attribute ? + if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE) + { + appendStrokingColor(aColor, aLine); // stroke with text color + aLine.append( " " ); + appendNonStrokingColor(COL_WHITE, aLine); // fill with white + aLine.append( "\n" ); + aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout + + // draw rectangle instead + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); + aLine.append( " " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); + aLine.append( " re h B\n" ); + return; + } + + + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); + aLine.append( " w " ); + appendStrokingColor( aColor, aLine ); + aLine.append( "\n" ); + + switch ( eTextLine ) + { + case LINESTYLE_DOTTED: + case LINESTYLE_BOLDDOTTED: + aLine.append( "[ " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); + aLine.append( " ] 0 d\n" ); + break; + case LINESTYLE_DASH: + case LINESTYLE_LONGDASH: + case LINESTYLE_BOLDDASH: + case LINESTYLE_BOLDLONGDASH: + { + sal_Int32 nDashLength = 4*nLineHeight; + sal_Int32 nVoidLength = 2*nLineHeight; + if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) ) + nDashLength = 8*nLineHeight; + + aLine.append( "[ " ); + m_aPages.back().appendMappedLength( nDashLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( " ] 0 d\n" ); + } + break; + case LINESTYLE_DASHDOT: + case LINESTYLE_BOLDDASHDOT: + { + sal_Int32 nDashLength = 4*nLineHeight; + sal_Int32 nVoidLength = 2*nLineHeight; + aLine.append( "[ " ); + m_aPages.back().appendMappedLength( nDashLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( " ] 0 d\n" ); + } + break; + case LINESTYLE_DASHDOTDOT: + case LINESTYLE_BOLDDASHDOTDOT: + { + sal_Int32 nDashLength = 4*nLineHeight; + sal_Int32 nVoidLength = 2*nLineHeight; + aLine.append( "[ " ); + m_aPages.back().appendMappedLength( nDashLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( " ] 0 d\n" ); + } + break; + default: + break; + } + + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); + aLine.append( " m " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); + aLine.append( " l S\n" ); + if ( eTextLine == LINESTYLE_DOUBLE ) + { + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); + aLine.append( " m " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); + aLine.append( " l S\n" ); + } + +} + +void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor ) +{ + // note: units in pFontInstance are ref device pixel + const LogicalFontInstance* pFontInstance = GetFontInstance(); + tools::Long nLineHeight = 0; + tools::Long nLinePos = 0; + tools::Long nLinePos2 = 0; + + if ( eStrikeout > STRIKEOUT_X ) + eStrikeout = STRIKEOUT_SINGLE; + + switch ( eStrikeout ) + { + case STRIKEOUT_SINGLE: + if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() ) + ImplInitTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize(); + nLinePos = pFontInstance->mxFontMetric->GetStrikeoutOffset(); + break; + case STRIKEOUT_BOLD: + if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() ) + ImplInitTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize(); + nLinePos = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset(); + break; + case STRIKEOUT_DOUBLE: + if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() ) + ImplInitTextLineSize(); + nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize(); + nLinePos = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1(); + nLinePos2 = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2(); + break; + default: + break; + } + + if ( !nLineHeight ) + return; + + // tdf#154235 + // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line, + // while in PDF we stroke the line so the position is to the middle of the + // line, we add half of nLineHeight to account for that. + nLinePos = HCONV(nLinePos + nLineHeight / 2); + nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2); + nLineHeight = HCONV(nLineHeight); + + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); + aLine.append( " w " ); + appendStrokingColor( aColor, aLine ); + aLine.append( "\n" ); + + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); + aLine.append( " m " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); + aLine.append( " l S\n" ); + + if ( eStrikeout == STRIKEOUT_DOUBLE ) + { + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); + aLine.append( " m " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); + aLine.append( " l S\n" ); + } + +} + +void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout ) +{ + //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need + //to tweak this + + OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" ); + OUString aStrikeout = aStrikeoutChar; + while( GetTextWidth( aStrikeout ) < nWidth ) + aStrikeout += aStrikeout; + + // do not get broader than nWidth modulo 1 character + while( GetTextWidth( aStrikeout ) >= nWidth ) + aStrikeout = aStrikeout.copy(1); + aStrikeout += aStrikeoutChar; + bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow(); + if ( bShadow ) + { + Font aFont = m_aCurrentPDFState.m_aFont; + aFont.SetShadow( false ); + setFont( aFont ); + updateGraphicsState(); + } + + // strikeout string is left aligned non-CTL text + vcl::text::ComplexTextLayoutFlags nOrigTLM = GetLayoutMode(); + SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong); + + push( PushFlags::CLIPREGION ); + FontMetric aRefDevFontMetric = GetFontMetric(); + tools::Rectangle aRect; + aRect.SetLeft( rPos.X() ); + aRect.SetRight( aRect.Left()+nWidth ); + aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() ); + aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() ); + + const LogicalFontInstance* pFontInstance = GetFontInstance(); + if (pFontInstance->mnOrientation) + { + tools::Polygon aPoly( aRect ); + aPoly.Rotate( rPos, pFontInstance->mnOrientation); + aRect = aPoly.GetBoundRect(); + } + + intersectClipRegion( aRect ); + drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false ); + pop(); + + SetLayoutMode( nOrigTLM ); + + if ( bShadow ) + { + Font aFont = m_aCurrentPDFState.m_aFont; + aFont.SetShadow( true ); + setFont( aFont ); + updateGraphicsState(); + } +} + +void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove ) +{ + if ( !nWidth || + ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) && + ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) && + ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) ) + return; + + MARK( "drawTextLine" ); + updateGraphicsState(); + + // note: units in pFontInstance are ref device pixel + const LogicalFontInstance* pFontInstance = GetFontInstance(); + Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor; + Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; + Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor(); + bool bStrikeoutDone = false; + bool bUnderlineDone = false; + bool bOverlineDone = false; + + if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) ) + { + drawStrikeoutChar( rPos, nWidth, eStrikeout ); + bStrikeoutDone = true; + } + + Point aPos( rPos ); + TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment(); + if( eAlign == ALIGN_TOP ) + aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() )); + else if( eAlign == ALIGN_BOTTOM ) + aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) ); + + OStringBuffer aLine( 512 ); + // save GS + aLine.append( "q " ); + + // rotate and translate matrix + double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation()); + Matrix3 aMat; + aMat.rotate( fAngle ); + aMat.translate( aPos.X(), aPos.Y() ); + m_aPages.back().appendMatrix3(aMat, aLine); + aLine.append( " cm\n" ); + + if ( aUnderlineColor.IsTransparent() ) + aUnderlineColor = aStrikeoutColor; + + if ( aOverlineColor.IsTransparent() ) + aOverlineColor = aStrikeoutColor; + + if ( (eUnderline == LINESTYLE_SMALLWAVE) || + (eUnderline == LINESTYLE_WAVE) || + (eUnderline == LINESTYLE_DOUBLEWAVE) || + (eUnderline == LINESTYLE_BOLDWAVE) ) + { + drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + bUnderlineDone = true; + } + + if ( (eOverline == LINESTYLE_SMALLWAVE) || + (eOverline == LINESTYLE_WAVE) || + (eOverline == LINESTYLE_DOUBLEWAVE) || + (eOverline == LINESTYLE_BOLDWAVE) ) + { + drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); + bOverlineDone = true; + } + + if ( !bUnderlineDone ) + { + drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + } + + if ( !bOverlineDone ) + { + drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); + } + + if ( !bStrikeoutDone ) + { + drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor ); + } + + aLine.append( "Q\n" ); + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly ) +{ + MARK( "drawPolygon" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) + return; + + int nPoints = rPoly.GetSize(); + OStringBuffer aLine( 20 * nPoints ); + m_aPages.back().appendPolygon( rPoly, aLine ); + if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) + aLine.append( "B*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) + aLine.append( "S\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly ) +{ + MARK( "drawPolyPolygon" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) + return; + + int nPolygons = rPolyPoly.Count(); + + OStringBuffer aLine( 40 * nPolygons ); + m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); + if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) + aLine.append( "B*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) + aLine.append( "S\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent ) +{ + SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" ); + nTransparentPercent = nTransparentPercent % 100; + + MARK( "drawTransparent" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) + return; + + if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) + { + m_aErrors.insert( m_bIsPDF_A1 ? + PDFWriter::Warning_Transparency_Omitted_PDFA : + PDFWriter::Warning_Transparency_Omitted_PDF13 ); + + drawPolyPolygon( rPolyPoly ); + return; + } + + // create XObject + m_aTransparentObjects.emplace_back( ); + // FIXME: polygons with beziers may yield incorrect bound rect + m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect(); + // convert rectangle to default user space + m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); + m_aTransparentObjects.back().m_nObject = createObject(); + m_aTransparentObjects.back().m_nExtGStateObject = createObject(); + m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0; + m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 )); + // create XObject's content stream + OStringBuffer aContent( 256 ); + m_aPages.back().appendPolyPolygon( rPolyPoly, aContent ); + if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT && + m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT ) + aContent.append( " B*\n" ); + else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT ) + aContent.append( " S\n" ); + else + aContent.append( " f*\n" ); + m_aTransparentObjects.back().m_pContentStream->WriteBytes( + aContent.getStr(), aContent.getLength() ); + + OStringBuffer aObjName( 16 ); + aObjName.append( "Tr" ); + aObjName.append( m_aTransparentObjects.back().m_nObject ); + OString aTrName( aObjName.makeStringAndClear() ); + aObjName.append( "EGS" ); + aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); + OString aExtName( aObjName.makeStringAndClear() ); + + OString aLine = + // insert XObject + "q /" + + aExtName + + " gs /" + + aTrName + + " Do Q\n"; + writeBuffer( aLine ); + + pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject ); + pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); +} + +void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams) +{ + if( nObject < 0 ) + return; + + switch( eKind ) + { + case ResourceKind::XObject: + rResourceDict.m_aXObjects[rResource] = nObject; + if (!rOutputStreams.empty()) + rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject; + break; + case ResourceKind::ExtGState: + rResourceDict.m_aExtGStates[rResource] = nObject; + if (!rOutputStreams.empty()) + rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject; + break; + case ResourceKind::Shading: + rResourceDict.m_aShadings[rResource] = nObject; + if (!rOutputStreams.empty()) + rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject; + break; + case ResourceKind::Pattern: + rResourceDict.m_aPatterns[rResource] = nObject; + if (!rOutputStreams.empty()) + rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject; + break; + } +} + +void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject ) +{ + pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams); +} + +void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect ) +{ + push( PushFlags::ALL ); + + // force reemitting clip region inside the new stream, and + // prevent emitting an unbalanced "Q" at the start + clearClipRegion(); + // this is needed to point m_aCurrentPDFState at the pushed state + // ... but it's pointless to actually write into the "outer" stream here! + updateGraphicsState(Mode::NOWRITE); + + m_aOutputStreams.push_front( StreamRedirect() ); + m_aOutputStreams.front().m_pStream = pStream; + m_aOutputStreams.front().m_aMapMode = m_aMapMode; + + if( !rTargetRect.IsEmpty() ) + { + m_aOutputStreams.front().m_aTargetRect = + lcl_convert( m_aGraphicsStack.front().m_aMapMode, + m_aMapMode, + this, + rTargetRect ); + Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft(); + tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight()); + aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) ); + m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta ); + } + + // setup graphics state for independent object stream + + // force reemitting colors + m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT; + m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT; +} + +SvStream* PDFWriterImpl::endRedirect() +{ + SvStream* pStream = nullptr; + if( ! m_aOutputStreams.empty() ) + { + pStream = m_aOutputStreams.front().m_pStream; + m_aMapMode = m_aOutputStreams.front().m_aMapMode; + m_aOutputStreams.pop_front(); + } + + pop(); + + m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT; + m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT; + + // needed after pop() to set m_aCurrentPDFState + updateGraphicsState(Mode::NOWRITE); + + return pStream; +} + +void PDFWriterImpl::beginTransparencyGroup() +{ + updateGraphicsState(); + if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) + beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() ); +} + +void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent ) +{ + SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" ); + nTransparentPercent = nTransparentPercent % 100; + + if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) + return; + + // create XObject + m_aTransparentObjects.emplace_back( ); + m_aTransparentObjects.back().m_aBoundRect = rBoundingBox; + // convert rectangle to default user space + m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); + m_aTransparentObjects.back().m_nObject = createObject(); + m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0; + // get XObject's content stream + m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) ); + m_aTransparentObjects.back().m_nExtGStateObject = createObject(); + + OStringBuffer aObjName( 16 ); + aObjName.append( "Tr" ); + aObjName.append( m_aTransparentObjects.back().m_nObject ); + OString aTrName( aObjName.makeStringAndClear() ); + aObjName.append( "EGS" ); + aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); + OString aExtName( aObjName.makeStringAndClear() ); + + OString aLine = + // insert XObject + "q /" + + aExtName + + " gs /" + + aTrName + + " Do Q\n"; + writeBuffer( aLine ); + + pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject ); + pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); + +} + +void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect ) +{ + MARK( "drawRectangle" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) + return; + + OStringBuffer aLine( 40 ); + m_aPages.back().appendRect( rRect, aLine ); + + if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) + aLine.append( " B*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) + aLine.append( " S\n" ); + else + aLine.append( " f*\n" ); + + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) +{ + MARK( "drawRectangle with rounded edges" ); + + if( !nHorzRound && !nVertRound ) + drawRectangle( rRect ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) + return; + + if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 ) + nHorzRound = rRect.GetWidth()/2; + if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 ) + nVertRound = rRect.GetHeight()/2; + + Point aPoints[16]; + const double kappa = 0.5522847498; + const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5); + const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5); + + aPoints[1] = Point( rRect.Left() + nHorzRound, rRect.Top() ); + aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); + aPoints[2] = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() ); + aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() ); + + aPoints[5] = Point( rRect.Right()+1, rRect.Top()+nVertRound ); + aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky ); + aPoints[6] = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound ); + aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky ); + + aPoints[9] = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 ); + aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() ); + aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() ); + aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() ); + + aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound ); + aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky ); + aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound ); + aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky ); + + OStringBuffer aLine( 80 ); + m_aPages.back().appendPoint( aPoints[1], aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( aPoints[2], aLine ); + aLine.append( " l " ); + m_aPages.back().appendPoint( aPoints[3], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[4], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[5], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[6], aLine ); + aLine.append( " l " ); + m_aPages.back().appendPoint( aPoints[7], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[8], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[9], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[10], aLine ); + aLine.append( " l " ); + m_aPages.back().appendPoint( aPoints[11], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[12], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[13], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[14], aLine ); + aLine.append( " l " ); + m_aPages.back().appendPoint( aPoints[15], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[0], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[1], aLine ); + aLine.append( " c " ); + + if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) + aLine.append( "b*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) + aLine.append( "s\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect ) +{ + MARK( "drawEllipse" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) + return; + + Point aPoints[12]; + const double kappa = 0.5522847498; + const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5); + const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5); + + aPoints[1] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() ); + aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); + aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() ); + + aPoints[4] = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 ); + aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky ); + aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky ); + + aPoints[7] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 ); + aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() ); + aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() ); + + aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 ); + aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky ); + aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky ); + + OStringBuffer aLine( 80 ); + m_aPages.back().appendPoint( aPoints[1], aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( aPoints[2], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[3], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[4], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[5], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[6], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[7], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[8], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[9], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[10], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[11], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[0], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[1], aLine ); + aLine.append( " c " ); + + if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) + aLine.append( "b*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) + aLine.append( "s\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine ); +} + +static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint ) +{ + Point aOrigin((rRect.Left()+rRect.Right()+1)/2, + (rRect.Top()+rRect.Bottom()+1)/2); + Point aPoint = rPoint - aOrigin; + + double fX = static_cast<double>(aPoint.X()); + double fY = static_cast<double>(-aPoint.Y()); + + if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0)) + throw o3tl::divide_by_zero(); + + if( rRect.GetWidth() > rRect.GetHeight() ) + fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight())); + else if( rRect.GetHeight() > rRect.GetWidth() ) + fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth())); + return atan2( fY, fX ); +} + +void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord ) +{ + MARK( "drawArc" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) + return; + + // calculate start and stop angles + const double fStartAngle = calcAngle( rRect, rStart ); + double fStopAngle = calcAngle( rRect, rStop ); + while( fStopAngle < fStartAngle ) + fStopAngle += 2.0*M_PI; + const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1; + const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments); + const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0); + const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0; + const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0; + + const Point aCenter( (rRect.Left()+rRect.Right()+1)/2, + (rRect.Top()+rRect.Bottom()+1)/2 ); + + OStringBuffer aLine( 30*nFragments ); + Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ), + -static_cast<int>(halfHeight * sin(fStartAngle) ) ); + aPoint += aCenter; + m_aPages.back().appendPoint( aPoint, aLine ); + aLine.append( " m " ); + if( !basegfx::fTools::equal(fStartAngle, fStopAngle) ) + { + for( int i = 0; i < nFragments; i++ ) + { + const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta; + const double fStopFragment = fStartFragment + fFragmentDelta; + aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ), + -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) ); + aPoint += aCenter; + m_aPages.back().appendPoint( aPoint, aLine ); + aLine.append( ' ' ); + + aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ), + -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) ); + aPoint += aCenter; + m_aPages.back().appendPoint( aPoint, aLine ); + aLine.append( ' ' ); + + aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ), + -static_cast<int>(halfHeight * sin(fStopFragment) ) ); + aPoint += aCenter; + m_aPages.back().appendPoint( aPoint, aLine ); + aLine.append( " c\n" ); + } + } + if( bWithChord || bWithPie ) + { + if( bWithPie ) + { + m_aPages.back().appendPoint( aCenter, aLine ); + aLine.append( " l " ); + } + aLine.append( "h " ); + } + if( ! bWithChord && ! bWithPie ) + aLine.append( "S\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && + m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) + aLine.append( "B*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) + aLine.append( "S\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly ) +{ + MARK( "drawPolyLine" ); + + sal_uInt16 nPoints = rPoly.GetSize(); + if( nPoints < 2 ) + return; + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) + return; + + OStringBuffer aLine( 20 * nPoints ); + m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] ); + aLine.append( "S\n" ); + + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo ) +{ + MARK( "drawPolyLine with LineInfo" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) + return; + + OStringBuffer aLine; + aLine.append( "q " ); + if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) + { + writeBuffer( aLine ); + drawPolyLine( rPoly ); + writeBuffer( "Q\n" ); + } + else + { + PDFWriter::ExtLineInfo aInfo; + convertLineInfoToExtLineInfo( rInfo, aInfo ); + drawPolyLine( rPoly, aInfo ); + } +} + +void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut ) +{ + SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" ); + rOut.m_fLineWidth = rIn.GetWidth(); + rOut.m_fTransparency = 0.0; + rOut.m_eCap = PDFWriter::capButt; + rOut.m_eJoin = PDFWriter::joinMiter; + rOut.m_fMiterLimit = 10; + rOut.m_aDashArray = rIn.GetDotDashArray(); + + // add LineJoin + switch(rIn.GetLineJoin()) + { + case basegfx::B2DLineJoin::Bevel : + { + rOut.m_eJoin = PDFWriter::joinBevel; + break; + } + // Pdf has no 'none' lineJoin, default is miter + case basegfx::B2DLineJoin::NONE : + case basegfx::B2DLineJoin::Miter : + { + rOut.m_eJoin = PDFWriter::joinMiter; + break; + } + case basegfx::B2DLineJoin::Round : + { + rOut.m_eJoin = PDFWriter::joinRound; + break; + } + } + + // add LineCap + switch(rIn.GetLineCap()) + { + default: /* css::drawing::LineCap_BUTT */ + { + rOut.m_eCap = PDFWriter::capButt; + break; + } + case css::drawing::LineCap_ROUND: + { + rOut.m_eCap = PDFWriter::capRound; + break; + } + case css::drawing::LineCap_SQUARE: + { + rOut.m_eCap = PDFWriter::capSquare; + break; + } + } +} + +void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo ) +{ + MARK( "drawPolyLine with ExtLineInfo" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) + return; + + if( rInfo.m_fTransparency >= 1.0 ) + return; + + if( rInfo.m_fTransparency != 0.0 ) + beginTransparencyGroup(); + + OStringBuffer aLine; + aLine.append( "q " ); + m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine ); + aLine.append( " w" ); + if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader + { + switch( rInfo.m_eCap ) + { + default: + case PDFWriter::capButt: aLine.append( " 0 J" );break; + case PDFWriter::capRound: aLine.append( " 1 J" );break; + case PDFWriter::capSquare: aLine.append( " 2 J" );break; + } + switch( rInfo.m_eJoin ) + { + default: + case PDFWriter::joinMiter: + { + double fLimit = rInfo.m_fMiterLimit; + if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit ) + fLimit = fLimit / rInfo.m_fLineWidth; + if( fLimit < 1.0 ) + fLimit = 1.0; + aLine.append( " 0 j " ); + appendDouble( fLimit, aLine ); + aLine.append( " M" ); + } + break; + case PDFWriter::joinRound: aLine.append( " 1 j" );break; + case PDFWriter::joinBevel: aLine.append( " 2 j" );break; + } + if( !rInfo.m_aDashArray.empty() ) + { + aLine.append( " [ " ); + for (auto const& dash : rInfo.m_aDashArray) + { + m_aPages.back().appendMappedLength( dash, aLine ); + aLine.append( ' ' ); + } + aLine.append( "] 0 d" ); + } + aLine.append( "\n" ); + writeBuffer( aLine ); + drawPolyLine( rPoly ); + } + else + { + basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon()); + basegfx::B2DPolyPolygon aPolyPoly; + + basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly); + + // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments. + // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality) + // this line needs to be removed and the loop below adapted accordingly + aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly); + + const sal_uInt32 nPolygonCount(aPolyPoly.count()); + + for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ ) + { + aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " ); + aPoly = aPolyPoly.getB2DPolygon( nPoly ); + const sal_uInt32 nPointCount(aPoly.count()); + + if(nPointCount) + { + const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1); + basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0)); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + if( a > 0 ) + aLine.append( " " ); + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex)); + + m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()), + FRound(aCurrent.getY()) ), + aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( Point( FRound(aNext.getX()), + FRound(aNext.getY()) ), + aLine ); + aLine.append( " l" ); + + // prepare next edge + aCurrent = aNext; + } + } + } + aLine.append( " S " ); + writeBuffer( aLine ); + } + writeBuffer( "Q\n" ); + + if( rInfo.m_fTransparency == 0.0 ) + return; + + // FIXME: actually this may be incorrect with bezier polygons + tools::Rectangle aBoundRect( rPoly.GetBoundRect() ); + // avoid clipping with thick lines + if( rInfo.m_fLineWidth > 0.0 ) + { + sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth); + aBoundRect.AdjustTop( -nLW ); + aBoundRect.AdjustLeft( -nLW ); + aBoundRect.AdjustRight(nLW ); + aBoundRect.AdjustBottom(nLW ); + } + endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) ); +} + +void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor ) +{ + MARK( "drawPixel" ); + + Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor ); + + if( aColor == COL_TRANSPARENT ) + return; + + // pixels are drawn in line color, so have to set + // the nonstroking color to line color + Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; + setFillColor( aColor ); + + updateGraphicsState(); + + OStringBuffer aLine( 20 ); + m_aPages.back().appendPoint( rPoint, aLine ); + aLine.append( ' ' ); + appendDouble( 1.0/double(GetDPIX()), aLine ); + aLine.append( ' ' ); + appendDouble( 1.0/double(GetDPIY()), aLine ); + aLine.append( " re f\n" ); + writeBuffer( aLine ); + + setFillColor( aOldFillColor ); +} + +void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject ) +{ + CHECK_RETURN2( updateObject( rObject.m_nObject ) ); + + bool bFlateFilter = compressStream( rObject.m_pContentStream.get() ); + sal_uInt64 nSize = rObject.m_pContentStream->TellEnd(); + rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN ); + if (g_bDebugDisableCompression) + { + emitComment( "PDFWriterImpl::writeTransparentObject" ); + } + OStringBuffer aLine( 512 ); + CHECK_RETURN2( updateObject( rObject.m_nObject ) ); + aLine.append( rObject.m_nObject ); + aLine.append( " 0 obj\n" + "<</Type/XObject\n" + "/Subtype/Form\n" + "/BBox[ " ); + appendFixedInt( rObject.m_aBoundRect.Left(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Top(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Right(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine ); + aLine.append( " ]\n" ); + if( ! m_bIsPDF_A1 ) + { + // 7.8.3 Resource dicts are required for content streams + aLine.append( "/Resources " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R\n" ); + + aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" ); + } + + aLine.append( "/Length " ); + aLine.append( static_cast<sal_Int32>(nSize) ); + aLine.append( "\n" ); + if( bFlateFilter ) + aLine.append( "/Filter/FlateDecode\n" ); + aLine.append( ">>\n" + "stream\n" ); + CHECK_RETURN2( writeBuffer( aLine ) ); + checkAndEnableStreamEncryption( rObject.m_nObject ); + CHECK_RETURN2( writeBufferBytes( rObject.m_pContentStream->GetData(), nSize ) ); + disableStreamEncryption(); + aLine.setLength( 0 ); + aLine.append( "\n" + "endstream\n" + "endobj\n\n" ); + CHECK_RETURN2( writeBuffer( aLine ) ); + + // write ExtGState dict for this XObject + aLine.setLength( 0 ); + aLine.append( rObject.m_nExtGStateObject ); + aLine.append( " 0 obj\n" + "<<" ); + + if( m_bIsPDF_A1 ) + { + aLine.append( "/CA 1.0/ca 1.0" ); + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); + } + else + { + aLine.append( "/CA " ); + appendDouble( rObject.m_fAlpha, aLine ); + aLine.append( "\n" + " /ca " ); + appendDouble( rObject.m_fAlpha, aLine ); + } + aLine.append( "\n" ); + + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) ); + CHECK_RETURN2( writeBuffer( aLine ) ); +} + +bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject ) +{ + // LO internal gradient -> PDF shading type: + // * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples + // [t=0:colorStart, t=1:colorEnd] + // * css::awt::GradientStyle_AXIAL: axial shading, using sampled-function with 3 samples + // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd] + // * other styles: function shading with aSize.Width() * aSize.Height() samples + sal_Int32 nFunctionObject = createObject(); + CHECK_RETURN( updateObject( nFunctionObject ) ); + + ScopedVclPtrInstance< VirtualDevice > aDev; + aDev->SetOutputSizePixel( rObject.m_aSize ); + aDev->SetMapMode( MapMode( MapUnit::MapPixel ) ); + if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) + aDev->SetDrawMode( aDev->GetDrawMode() | + ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText | + DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) ); + aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient ); + + Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize ); + BitmapScopedReadAccess pAccess(aSample); + + Size aSize = aSample.GetSizePixel(); + + sal_Int32 nStreamLengthObject = createObject(); + if (g_bDebugDisableCompression) + { + emitComment( "PDFWriterImpl::writeGradientFunction" ); + } + OStringBuffer aLine( 120 ); + aLine.append( nFunctionObject ); + aLine.append( " 0 obj\n" + "<</FunctionType 0\n"); + switch (rObject.m_aGradient.GetStyle()) + { + case css::awt::GradientStyle_LINEAR: + case css::awt::GradientStyle_AXIAL: + aLine.append("/Domain[ 0 1]\n"); + break; + default: + aLine.append("/Domain[ 0 1 0 1]\n"); + } + aLine.append("/Size[ " ); + switch (rObject.m_aGradient.GetStyle()) + { + case css::awt::GradientStyle_LINEAR: + aLine.append('2'); + break; + case css::awt::GradientStyle_AXIAL: + aLine.append('3'); + break; + default: + aLine.append( static_cast<sal_Int32>(aSize.Width()) ); + aLine.append( ' ' ); + aLine.append( static_cast<sal_Int32>(aSize.Height()) ); + } + aLine.append( " ]\n" + "/BitsPerSample 8\n" + "/Range[ 0 1 0 1 0 1 ]\n" + "/Order 3\n" + "/Length " ); + aLine.append( nStreamLengthObject ); + if (!g_bDebugDisableCompression) + aLine.append( " 0 R\n" + "/Filter/FlateDecode" + ">>\n" + "stream\n" ); + else + aLine.append( " 0 R\n" + ">>\n" + "stream\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + + sal_uInt64 nStartStreamPos = 0; + CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) ); + + checkAndEnableStreamEncryption( nFunctionObject ); + beginCompression(); + sal_uInt8 aCol[3]; + switch (rObject.m_aGradient.GetStyle()) + { + case css::awt::GradientStyle_AXIAL: + aCol[0] = rObject.m_aGradient.GetEndColor().GetRed(); + aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen(); + aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue(); + CHECK_RETURN( writeBufferBytes( aCol, 3 ) ); + [[fallthrough]]; + case css::awt::GradientStyle_LINEAR: + { + aCol[0] = rObject.m_aGradient.GetStartColor().GetRed(); + aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen(); + aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue(); + CHECK_RETURN( writeBufferBytes( aCol, 3 ) ); + + aCol[0] = rObject.m_aGradient.GetEndColor().GetRed(); + aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen(); + aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue(); + CHECK_RETURN( writeBufferBytes( aCol, 3 ) ); + break; + } + default: + for( int y = aSize.Height()-1; y >= 0; y-- ) + { + for( tools::Long x = 0; x < aSize.Width(); x++ ) + { + BitmapColor aColor = pAccess->GetColor( y, x ); + aCol[0] = aColor.GetRed(); + aCol[1] = aColor.GetGreen(); + aCol[2] = aColor.GetBlue(); + CHECK_RETURN( writeBufferBytes( aCol, 3 ) ); + } + } + } + endCompression(); + disableStreamEncryption(); + + sal_uInt64 nEndStreamPos = 0; + CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) ); + + aLine.setLength( 0 ); + aLine.append( "\nendstream\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + + // write stream length + CHECK_RETURN( updateObject( nStreamLengthObject ) ); + aLine.setLength( 0 ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) ); + aLine.append( "\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + + CHECK_RETURN( updateObject( rObject.m_nObject ) ); + aLine.setLength( 0 ); + aLine.append( rObject.m_nObject ); + aLine.append( " 0 obj\n"); + switch (rObject.m_aGradient.GetStyle()) + { + case css::awt::GradientStyle_LINEAR: + case css::awt::GradientStyle_AXIAL: + aLine.append("<</ShadingType 2\n"); + break; + default: + aLine.append("<</ShadingType 1\n"); + } + aLine.append("/ColorSpace/DeviceRGB\n" + "/AntiAlias true\n"); + + // Determination of shading axis + // See: OutputDevice::ImplDrawLinearGradient for reference + tools::Rectangle aRect; + aRect.SetLeft(0); + aRect.SetTop(0); + aRect.SetRight( aSize.Width() ); + aRect.SetBottom( aSize.Height() ); + + tools::Rectangle aBoundRect; + Point aCenter; + Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10; + rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter ); + + const bool bLinear = (rObject.m_aGradient.GetStyle() == css::awt::GradientStyle_LINEAR); + double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0; + if ( !bLinear ) + { + fBorder /= 2.0; + } + + aBoundRect.AdjustBottom( -fBorder ); + if (!bLinear) + { + aBoundRect.AdjustTop(fBorder ); + } + + switch (rObject.m_aGradient.GetStyle()) + { + case css::awt::GradientStyle_LINEAR: + case css::awt::GradientStyle_AXIAL: + { + aLine.append("/Domain[ 0 1 ]\n" + "/Coords[ " ); + tools::Polygon aPoly( 2 ); + aPoly[0] = aBoundRect.BottomCenter(); + aPoly[1] = aBoundRect.TopCenter(); + aPoly.Rotate( aCenter, 3600_deg10 - nAngle ); + + aLine.append( static_cast<sal_Int32>(aPoly[0].X()) ); + aLine.append( " " ); + aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) ); + aLine.append( " " ); + aLine.append( static_cast<sal_Int32>(aPoly[1].X())); + aLine.append( " "); + aLine.append( static_cast<sal_Int32>(aPoly[1].Y())); + aLine.append( " ]\n"); + aLine.append("/Extend [true true]\n"); + break; + } + default: + aLine.append("/Domain[ 0 1 0 1 ]\n" + "/Matrix[ " ); + aLine.append( static_cast<sal_Int32>(aSize.Width()) ); + aLine.append( " 0 0 " ); + aLine.append( static_cast<sal_Int32>(aSize.Height()) ); + aLine.append( " 0 0 ]\n"); + } + aLine.append("/Function " ); + aLine.append( nFunctionObject ); + aLine.append( " 0 R\n" + ">>\n" + "endobj\n\n" ); + return writeBuffer( aLine ); +} + +void PDFWriterImpl::writeJPG( const JPGEmit& rObject ) +{ + if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject) + { + writeReferenceXObject(rObject.m_aReferenceXObject); + return; + } + + CHECK_RETURN2( rObject.m_pStream ); + CHECK_RETURN2( updateObject( rObject.m_nObject ) ); + + sal_uInt64 nLength = rObject.m_pStream->TellEnd(); + rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN ); + + sal_Int32 nMaskObject = 0; + if( !rObject.m_aAlphaMask.IsEmpty() ) + { + if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 + && !m_bIsPDF_A1) + { + nMaskObject = createObject(); + } + else if( m_bIsPDF_A1 ) + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); + else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 ); + + } + if (g_bDebugDisableCompression) + { + emitComment( "PDFWriterImpl::writeJPG" ); + } + + OStringBuffer aLine(200); + aLine.append( rObject.m_nObject ); + aLine.append( " 0 obj\n" + "<</Type/XObject/Subtype/Image/Width " ); + aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) ); + aLine.append( " /Height " ); + aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) ); + aLine.append( " /BitsPerComponent 8 " ); + if( rObject.m_bTrueColor ) + aLine.append( "/ColorSpace/DeviceRGB" ); + else + aLine.append( "/ColorSpace/DeviceGray" ); + aLine.append( "/Filter/DCTDecode/Length " ); + aLine.append( static_cast<sal_Int64>(nLength) ); + if( nMaskObject ) + { + aLine.append(" /SMask "); + aLine.append( nMaskObject ); + aLine.append( " 0 R " ); + } + aLine.append( ">>\nstream\n" ); + CHECK_RETURN2( writeBuffer( aLine ) ); + + checkAndEnableStreamEncryption( rObject.m_nObject ); + CHECK_RETURN2( writeBufferBytes( rObject.m_pStream->GetData(), nLength ) ); + disableStreamEncryption(); + + aLine.setLength( 0 ); + CHECK_RETURN2( writeBuffer( "\nendstream\nendobj\n\n" ) ); + + if( nMaskObject ) + { + BitmapEmit aEmit; + aEmit.m_nObject = nMaskObject; + aEmit.m_aBitmap = BitmapEx( rObject.m_aAlphaMask.GetBitmap(), rObject.m_aAlphaMask ); + writeBitmapObject( aEmit, true ); + } + + writeReferenceXObject(rObject.m_aReferenceXObject); +} + +void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit) +{ + if (rEmit.m_nFormObject <= 0) + return; + + // Count /Matrix and /BBox. + // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired + // rendering DPI so we have to take into account that here too. + static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi(); + static const double fMagicScaleFactor = PDF_INSERT_MAGIC_SCALE_FACTOR; + + sal_Int32 nOldDPIX = GetDPIX(); + sal_Int32 nOldDPIY = GetDPIY(); + SetDPIX(fResolutionDPI); + SetDPIY(fResolutionDPI); + Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit())); + SetDPIX(nOldDPIX); + SetDPIY(nOldDPIY); + double fScaleX = 1.0 / aSize.Width(); + double fScaleY = 1.0 / aSize.Height(); + + sal_Int32 nWrappedFormObject = 0; + if (!m_aContext.UseReferenceXObject) + { + // tdf#156842 increase scale for external PDF data + // Multiply PDF_INSERT_MAGIC_SCALE_FACTOR for platforms like macOS + // that scale all images by this number. + // This fix also allows the CppunitTest_vcl_pdfexport to run + // successfully on macOS. + fScaleX = fMagicScaleFactor / aSize.Width(); + fScaleY = fMagicScaleFactor / aSize.Height(); + + // Parse the PDF data, we need that to write the PDF dictionary of our + // object. + if (rEmit.m_nExternalPDFDataIndex < 0) + return; + auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex); + auto& pPDFDocument = rExternalPDFStream.getPDFDocument(); + if (!pPDFDocument) + { + // Couldn't parse the document and can't continue + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document"); + return; + } + + std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages(); + if (aPages.empty()) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages"); + return; + } + + size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0; + + filter::PDFObjectElement* pPage = aPages[nPageIndex]; + if (!pPage) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page"); + return; + } + + double aOrigin[2] = { 0.0, 0.0 }; + if (auto* pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"_ostr))) + { + const auto& rElements = pArray->GetElements(); + if (rElements.size() >= 4) + { + // get x1, y1 of the rectangle. + for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx) + { + if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx])) + aOrigin[nIdx] = pNumElement->GetValue(); + } + } + } + + std::vector<filter::PDFObjectElement*> aContentStreams; + if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"_ostr)) + aContentStreams.push_back(pContentStream); + else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"_ostr))) + { + for (const auto pElement : pArray->GetElements()) + { + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); + if (!pReference) + continue; + + filter::PDFObjectElement* pObject = pReference->LookupObject(); + if (!pObject) + continue; + + aContentStreams.push_back(pObject); + } + } + + if (aContentStreams.empty()) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream"); + return; + } + + // Merge link annotations from pPage to our page. + std::vector<filter::PDFObjectElement*> aAnnots; + if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Annots"_ostr))) + { + for (const auto pElement : pArray->GetElements()) + { + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); + if (!pReference) + { + continue; + } + + filter::PDFObjectElement* pObject = pReference->LookupObject(); + if (!pObject) + { + continue; + } + + auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr)); + if (!pType || pType->GetValue() != "Annot") + { + continue; + } + + auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr)); + if (!pSubtype || pSubtype->GetValue() != "Link") + { + continue; + } + + // Reference to a link annotation object, remember it. + aAnnots.push_back(pObject); + } + } + if (!aAnnots.empty()) + { + PDFObjectCopier aCopier(*this); + SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer(); + std::map<sal_Int32, sal_Int32> aMap; + for (const auto& pAnnot : aAnnots) + { + // Copy over the annotation and refer to its new id. + sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pAnnot, aMap); + m_aPages.back().m_aAnnotations.push_back(nNewId); + } + } + + nWrappedFormObject = createObject(); + // Write the form XObject wrapped below. This is a separate object from + // the wrapper, this way there is no need to alter the stream contents. + + OStringBuffer aLine; + aLine.append(nWrappedFormObject); + aLine.append(" 0 obj\n"); + aLine.append("<< /Type /XObject"); + aLine.append(" /Subtype /Form"); + + tools::Long nWidth = aSize.Width(); + tools::Long nHeight = aSize.Height(); + basegfx::B2DRange aBBox(0, 0, aSize.Width(), aSize.Height()); + if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate"_ostr))) + { + // The original page was rotated, then construct a transformation matrix which does the + // same with our form object. + sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360; + // /Rotate is clockwise, matrix rotate is counter-clockwise. + sal_Int32 nAngle = -1 * nRotAngle; + + // The bounding box just rotates. + basegfx::B2DHomMatrix aBBoxMat; + aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue())); + aBBox.transform(aBBoxMat); + + // Now transform the object: rotate around the center and make sure that the rotation + // doesn't affect the aspect ratio. + basegfx::B2DHomMatrix aMat; + aMat.translate(-0.5 * aBBox.getWidth() - aOrigin[0], -0.5 * aBBox.getHeight() - aOrigin[1]); + aMat.rotate(basegfx::deg2rad(nAngle)); + aMat.translate(0.5 * nWidth, 0.5 * nHeight); + + aLine.append(" /Matrix [ "); + aLine.append(aMat.a()); + aLine.append(" "); + aLine.append(aMat.b()); + aLine.append(" "); + aLine.append(aMat.c()); + aLine.append(" "); + aLine.append(aMat.d()); + aLine.append(" "); + aLine.append(aMat.e()); + aLine.append(" "); + aLine.append(aMat.f()); + aLine.append(" ] "); + } + + PDFObjectCopier aCopier(*this); + auto & rResources = rExternalPDFStream.getCopiedResources(); + aCopier.copyPageResources(pPage, aLine, rResources); + + aLine.append(" /BBox [ "); + aLine.append(aOrigin[0]); + aLine.append(' '); + aLine.append(aOrigin[1]); + aLine.append(' '); + aLine.append(aBBox.getWidth() + aOrigin[0]); + aLine.append(' '); + aLine.append(aBBox.getHeight() + aOrigin[1]); + aLine.append(" ]"); + + if (!g_bDebugDisableCompression) + aLine.append(" /Filter/FlateDecode"); + aLine.append(" /Length "); + + SvMemoryStream aStream; + bool bCompressed = false; + sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed); + aLine.append(nLength); + + aLine.append(">>\nstream\n"); + if (g_bDebugDisableCompression) + { + emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject"); + } + if (!updateObject(nWrappedFormObject)) + return; + if (!writeBuffer(aLine)) + return; + aLine.setLength(0); + + checkAndEnableStreamEncryption(nWrappedFormObject); + // Copy the original page streams to the form XObject stream. + aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize()); + if (!writeBuffer(aLine)) + return; + aLine.setLength(0); + disableStreamEncryption(); + + aLine.append("\nendstream\nendobj\n\n"); + if (!writeBuffer(aLine)) + return; + } + + OStringBuffer aLine; + if (g_bDebugDisableCompression) + { + emitComment("PDFWriterImpl::writeReferenceXObject, FormObject"); + } + if (!updateObject(rEmit.m_nFormObject)) + return; + + // Now have all the info to write the form XObject. + aLine.append(rEmit.m_nFormObject); + aLine.append(" 0 obj\n"); + aLine.append("<< /Type /XObject"); + aLine.append(" /Subtype /Form"); + aLine.append(" /Resources << /XObject<<"); + + sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject; + aLine.append(" /Im"); + aLine.append(nObject); + aLine.append(" "); + aLine.append(nObject); + aLine.append(" 0 R"); + + aLine.append(">> >>"); + aLine.append(" /Matrix [ "); + appendDouble(fScaleX, aLine); + aLine.append(" 0 0 "); + appendDouble(fScaleY, aLine); + aLine.append(" 0 0 ]"); + aLine.append(" /BBox [ 0 0 "); + // tdf#157680 reduce size by magic scale factor in /BBox + aLine.append(aSize.Width() / fMagicScaleFactor); + aLine.append(" "); + // tdf#157680 reduce size by magic scale factor in /BBox + aLine.append(aSize.Height() / fMagicScaleFactor); + aLine.append(" ]\n"); + + if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0) + { + // Write the reference dictionary. + aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) "); + if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) + { // ISO 14289-1:2014, Clause: 7.11 + aLine.append("/UF (<embedded file>) "); + } + aLine.append("/EF << /F "); + aLine.append(rEmit.m_nEmbeddedObject); + aLine.append(" 0 R >> >> /Page 0 >>\n"); + } + + aLine.append("/Length "); + + OStringBuffer aStream; + aStream.append("q "); + if (m_aContext.UseReferenceXObject) + { + // Reference XObject markup is used, just refer to the fallback bitmap + // here. + aStream.append(aSize.Width()); + aStream.append(" 0 0 "); + aStream.append(aSize.Height()); + aStream.append(" 0 0 cm\n"); + aStream.append("/Im"); + aStream.append(rEmit.m_nBitmapObject); + aStream.append(" Do\n"); + } + else + { + // Reset line width to the default. + aStream.append(" 1 w\n"); + + // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be + // consistent with that. + aStream.append("1 1 1 rg\n"); + aStream.append("0 0 "); + aStream.append(aSize.Width()); + aStream.append(" "); + aStream.append(aSize.Height()); + aStream.append(" re\n"); + aStream.append("f*\n"); + + // Reset non-stroking color in case the XObject uses the default + aStream.append("0 0 0 rg\n"); + // No reference XObject, draw the form XObject containing the original + // page streams. + aStream.append("/Im"); + aStream.append(nWrappedFormObject); + aStream.append(" Do\n"); + } + aStream.append("Q"); + aLine.append(aStream.getLength()); + + aLine.append(">>\nstream\n"); + if (!writeBuffer(aLine)) + return; + aLine.setLength(0); + + checkAndEnableStreamEncryption(rEmit.m_nFormObject); + aLine.append(aStream.getStr()); + if (!writeBuffer(aLine)) + return; + aLine.setLength(0); + disableStreamEncryption(); + + aLine.append("\nendstream\nendobj\n\n"); + CHECK_RETURN2(writeBuffer(aLine)); +} + +bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject, bool bMask ) +{ + if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject) + { + writeReferenceXObject(rObject.m_aReferenceXObject); + return true; + } + + CHECK_RETURN( updateObject( rObject.m_nObject ) ); + + Bitmap aBitmap; + bool bWriteMask = false; + if( ! bMask ) + { + aBitmap = rObject.m_aBitmap.GetBitmap(); + if( rObject.m_aBitmap.IsAlpha() ) + { + if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) + bWriteMask = true; + // else draw without alpha channel + } + } + else + { + if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() ) + { + if( rObject.m_aBitmap.IsAlpha() ) + { + aBitmap = rObject.m_aBitmap.GetAlphaMask().GetBitmap(); + aBitmap.Convert( BmpConversion::N1BitThreshold ); + SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "mask conversion failed" ); + } + } + else if (aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP) + { + aBitmap = rObject.m_aBitmap.GetAlphaMask().GetBitmap(); + aBitmap.Convert( BmpConversion::N8BitGreys ); + SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "alpha mask conversion failed" ); + } + } + + BitmapScopedReadAccess pAccess(aBitmap); + + bool bTrueColor = true; + sal_Int32 nBitsPerComponent = 0; + auto const ePixelFormat = aBitmap.getPixelFormat(); + switch (ePixelFormat) + { + case vcl::PixelFormat::N8_BPP: + bTrueColor = false; + nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat); + break; + case vcl::PixelFormat::N24_BPP: + case vcl::PixelFormat::N32_BPP: + bTrueColor = true; + nBitsPerComponent = 8; + break; + case vcl::PixelFormat::INVALID: + return false; + } + + sal_Int32 nStreamLengthObject = createObject(); + sal_Int32 nMaskObject = 0; + + if (g_bDebugDisableCompression) + { + emitComment( "PDFWriterImpl::writeBitmapObject" ); + } + OStringBuffer aLine(1024); + aLine.append( rObject.m_nObject ); + aLine.append( " 0 obj\n" + "<</Type/XObject/Subtype/Image/Width " ); + aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) ); + aLine.append( "/Height " ); + aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) ); + aLine.append( "/BitsPerComponent " ); + aLine.append( nBitsPerComponent ); + aLine.append( "/Length " ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 R\n" ); + if (!g_bDebugDisableCompression) + { + if( nBitsPerComponent != 1 ) + { + aLine.append( "/Filter/FlateDecode" ); + } + else + { + aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " ); + aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) ); + aLine.append( ">>\n" ); + } + } + if( ! bMask ) + { + aLine.append( "/ColorSpace" ); + if( bTrueColor ) + aLine.append( "/DeviceRGB\n" ); + else if( aBitmap.HasGreyPaletteAny() ) + { + aLine.append( "/DeviceGray\n" ); + if (aBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP) + { + // #i47395# 1 bit bitmaps occasionally have an inverted grey palette + sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) ); + assert( nBlackIndex == 0 || nBlackIndex == 1); + sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) ); + if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) && + pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) ) + { + // It is black and white + if( nBlackIndex == 1 ) + aLine.append( "/Decode[1 0]\n" ); + } + else + { + // It is two levels of grey + aLine.append( "/Decode[" ); + assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() && + pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() && + pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() && + pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() ); + aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 ); + aLine.append( " " ); + aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 ); + aLine.append( "]\n" ); + } + } + } + else + { + aLine.append( "[ /Indexed/DeviceRGB " ); + aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) ); + aLine.append( "\n<" ); + if( m_aContext.Encryption.Encrypt() ) + { + enableStringEncryption( rObject.m_nObject ); + //check encryption buffer size + m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3); + int nChar = 0; + //fill the encryption buffer + for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) + { + const BitmapColor& rColor = pAccess->GetPaletteColor( i ); + m_vEncryptionBuffer[nChar++] = rColor.GetRed(); + m_vEncryptionBuffer[nChar++] = rColor.GetGreen(); + m_vEncryptionBuffer[nChar++] = rColor.GetBlue(); + } + //encrypt the colorspace lookup table + rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar ); + //now queue the data for output + nChar = 0; + for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) + { + appendHex(m_vEncryptionBuffer[nChar++], aLine ); + appendHex(m_vEncryptionBuffer[nChar++], aLine ); + appendHex(m_vEncryptionBuffer[nChar++], aLine ); + } + } + else //no encryption requested (PDF/A-1a program flow drops here) + { + for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) + { + const BitmapColor& rColor = pAccess->GetPaletteColor( i ); + appendHex( rColor.GetRed(), aLine ); + appendHex( rColor.GetGreen(), aLine ); + appendHex( rColor.GetBlue(), aLine ); + } + } + aLine.append( ">\n]\n" ); + } + } + else + { + aLine.append( "/ColorSpace/DeviceGray\n" + "/Decode [ 1 0 ]\n" ); + } + + if (!bMask && !m_bIsPDF_A1) + { + if( bWriteMask ) + { + nMaskObject = createObject(); + if (rObject.m_aBitmap.IsAlpha()) + aLine.append( "/SMask " ); + else + aLine.append( "/Mask " ); + aLine.append( nMaskObject ); + aLine.append( " 0 R\n" ); + } + } + else if( m_bIsPDF_A1 && bWriteMask ) + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); + + aLine.append( ">>\n" + "stream\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + sal_uInt64 nStartPos = 0; + CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) ); + + checkAndEnableStreamEncryption( rObject.m_nObject ); + if (!g_bDebugDisableCompression && nBitsPerComponent == 1) + { + writeG4Stream(pAccess.get()); + } + else + { + beginCompression(); + if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb ) + { + //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits). + const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U; + + for( tools::Long i = 0; i < pAccess->Height(); i++ ) + { + CHECK_RETURN( writeBufferBytes( pAccess->GetScanline( i ), nScanLineBytes ) ); + } + } + else + { + const int nScanLineBytes = pAccess->Width()*3; + std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]); + for( tools::Long y = 0; y < pAccess->Height(); y++ ) + { + for( tools::Long x = 0; x < pAccess->Width(); x++ ) + { + BitmapColor aColor = pAccess->GetColor( y, x ); + xCol[3*x+0] = aColor.GetRed(); + xCol[3*x+1] = aColor.GetGreen(); + xCol[3*x+2] = aColor.GetBlue(); + } + CHECK_RETURN(writeBufferBytes(xCol.get(), nScanLineBytes)); + } + } + endCompression(); + } + disableStreamEncryption(); + + sal_uInt64 nEndPos = 0; + CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) ); + aLine.setLength( 0 ); + aLine.append( "\nendstream\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + CHECK_RETURN( updateObject( nStreamLengthObject ) ); + aLine.setLength( 0 ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) ); + aLine.append( "\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine ) ); + + if( nMaskObject ) + { + BitmapEmit aEmit; + aEmit.m_nObject = nMaskObject; + aEmit.m_aBitmap = rObject.m_aBitmap; + return writeBitmapObject( aEmit, true ); + } + + writeReferenceXObject(rObject.m_aReferenceXObject); + + return true; +} + +void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject) +{ + // The bitmap object is always a valid identifier, even if the graphic has + // no pdf data. + rEmit.m_nBitmapObject = nBitmapObject; + + if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf) + return; + + BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer(); + + if (m_aContext.UseReferenceXObject) + { + // Store the original PDF data as an embedded file. + auto nObjectID = addEmbeddedFile(rDataContainer); + rEmit.m_nEmbeddedObject = nObjectID; + } + else + { + sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer); + rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex(); + rEmit.m_nExternalPDFDataIndex = aIndex; + } + + rEmit.m_nFormObject = createObject(); + rEmit.m_aPixelSize = rGraphic.GetPrefSize(); +} + +void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic ) +{ + MARK( "drawJPGBitmap" ); + + OStringBuffer aLine( 80 ); + updateGraphicsState(); + + // #i40055# sanity check + if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) ) + return; + if( ! (rSizePixel.Width() && rSizePixel.Height()) ) + return; + + rDCTData.Seek( 0 ); + if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale ) + { + // need to convert to grayscale; + // load stream to bitmap and draw the bitmap instead + Graphic aGraphic; + GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG ); + if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() ) + { + Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() ); + BitmapEx aBmpEx( aBmp, rAlphaMask ); + drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx ); + } + else + drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() ); + return; + } + + std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream); + pStream->WriteStream( rDCTData ); + pStream->Seek( STREAM_SEEK_TO_END ); + + BitmapID aID; + aID.m_aPixelSize = rSizePixel; + aID.m_nSize = pStream->Tell(); + pStream->Seek( STREAM_SEEK_TO_BEGIN ); + aID.m_nChecksum = rtl_crc32( 0, pStream->GetData(), aID.m_nSize ); + if( ! rAlphaMask.IsEmpty() ) + aID.m_nMaskChecksum = rAlphaMask.GetChecksum(); + + std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(), + [&](const JPGEmit& arg) { return aID == arg.m_aID; }); + if( it == m_aJPGs.end() ) + { + m_aJPGs.emplace( m_aJPGs.begin() ); + JPGEmit& rEmit = m_aJPGs.front(); + if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject) + rEmit.m_nObject = createObject(); + rEmit.m_aID = aID; + rEmit.m_pStream = std::move( pStream ); + rEmit.m_bTrueColor = bIsTrueColor; + if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel ) + rEmit.m_aAlphaMask = rAlphaMask; + createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject); + + it = m_aJPGs.begin(); + } + + aLine.append( "q " ); + sal_Int32 nCheckWidth = 0; + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth ); + aLine.append( " 0 0 " ); + sal_Int32 nCheckHeight = 0; + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine ); + aLine.append( " cm\n/Im" ); + sal_Int32 nObject = it->m_aReferenceXObject.getObject(); + aLine.append(nObject); + aLine.append( " Do Q\n" ); + if( nCheckWidth == 0 || nCheckHeight == 0 ) + { + // #i97512# avoid invalid current matrix + aLine.setLength( 0 ); + aLine.append( "\n%jpeg image /Im" ); + aLine.append( it->m_nObject ); + aLine.append( " scaled to zero size, omitted\n" ); + } + writeBuffer( aLine ); + + OString aObjName = "Im" + OString::number(nObject); + pushResource( ResourceKind::XObject, aObjName, nObject ); + +} + +void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor ) +{ + OStringBuffer aLine( 80 ); + updateGraphicsState(); + + aLine.append( "q " ); + if( rFillColor != COL_TRANSPARENT ) + { + appendNonStrokingColor( rFillColor, aLine ); + aLine.append( ' ' ); + } + sal_Int32 nCheckWidth = 0; + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth ); + aLine.append( " 0 0 " ); + sal_Int32 nCheckHeight = 0; + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine ); + aLine.append( " cm\n/Im" ); + sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject(); + aLine.append(nObject); + aLine.append( " Do Q\n" ); + if( nCheckWidth == 0 || nCheckHeight == 0 ) + { + // #i97512# avoid invalid current matrix + aLine.setLength( 0 ); + aLine.append( "\n%bitmap image /Im" ); + aLine.append( rBitmap.m_nObject ); + aLine.append( " scaled to zero size, omitted\n" ); + } + writeBuffer( aLine ); +} + +const BitmapEmit& PDFWriterImpl::createBitmapEmit(const BitmapEx& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams) +{ + BitmapEx aBitmap( i_rBitmap ); + auto ePixelFormat = aBitmap.GetBitmap().getPixelFormat(); + if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) + aBitmap.Convert(BmpConversion::N8BitGreys); + BitmapID aID; + aID.m_aPixelSize = aBitmap.GetSizePixel(); + aID.m_nSize = vcl::pixelFormatBitCount(ePixelFormat); + aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum(); + aID.m_nMaskChecksum = 0; + if( aBitmap.IsAlpha() ) + aID.m_nMaskChecksum = aBitmap.GetAlphaMask().GetChecksum(); + std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(), + [&](const BitmapEmit& arg) { return aID == arg.m_aID; }); + if (it == rBitmaps.end()) + { + rBitmaps.push_front(BitmapEmit()); + rBitmaps.front().m_aID = aID; + rBitmaps.front().m_aBitmap = aBitmap; + if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject) + rBitmaps.front().m_nObject = createObject(); + createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject); + it = rBitmaps.begin(); + } + + sal_Int32 nObject = it->m_aReferenceXObject.getObject(); + OString aObjName = "Im" + OString::number(nObject); + pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams); + + return *it; +} + +const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic ) +{ + return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams); +} + +void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic ) +{ + MARK( "drawBitmap (Bitmap)" ); + + // #i40055# sanity check + if( ! (rDestSize.Width() && rDestSize.Height()) ) + return; + + const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic ); + drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT ); +} + +void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap ) +{ + MARK( "drawBitmap (BitmapEx)" ); + + // #i40055# sanity check + if( ! (rDestSize.Width() && rDestSize.Height()) ) + return; + + const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() ); + drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT ); +} + +sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize ) +{ + Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode, + MapMode( MapUnit::MapPoint ), + this, + rSize ) ); + // check if we already have this gradient + // rounding to point will generally lose some pixels + // round up to point boundary + aPtSize.AdjustWidth( 1 ); + aPtSize.AdjustHeight( 1 ); + std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(), + [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); }); + + if( it == m_aGradients.end() ) + { + m_aGradients.push_front( GradientEmit() ); + m_aGradients.front().m_aGradient = rGradient; + m_aGradients.front().m_nObject = createObject(); + m_aGradients.front().m_aSize = aPtSize; + it = m_aGradients.begin(); + } + + OStringBuffer aObjName( 16 ); + aObjName.append( 'P' ); + aObjName.append( it->m_nObject ); + pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject ); + + return it->m_nObject; +} + +void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient ) +{ + MARK( "drawGradient (Rectangle)" ); + + sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() ); + + Point aTranslate( rRect.BottomLeft() ); + aTranslate += Point( 0, 1 ); + + updateGraphicsState(); + + OStringBuffer aLine( 80 ); + aLine.append( "q 1 0 0 1 " ); + m_aPages.back().appendPoint( aTranslate, aLine ); + aLine.append( " cm " ); + // if a stroke is appended reset the clip region before stroke + if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) + aLine.append( "q " ); + aLine.append( "0 0 " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine ); + aLine.append( " re W n\n" ); + + aLine.append( "/P" ); + aLine.append( nGradient ); + aLine.append( " sh " ); + if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) + { + aLine.append( "Q 0 0 " ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine ); + aLine.append( " re S " ); + } + aLine.append( "Q\n" ); + writeBuffer( aLine ); +} + +void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) +{ + MARK( "drawHatch" ); + + updateGraphicsState(); + + if( rPolyPoly.Count() ) + { + tools::PolyPolygon aPolyPoly( rPolyPoly ); + + aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME ); + push( PushFlags::LINECOLOR ); + setLineColor( rHatch.GetColor() ); + DrawHatch( aPolyPoly, rHatch, false ); + pop(); + } +} + +void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall ) +{ + MARK( "drawWallpaper" ); + + bool bDrawColor = false; + bool bDrawGradient = false; + bool bDrawBitmap = false; + + BitmapEx aBitmap; + Point aBmpPos = rRect.TopLeft(); + Size aBmpSize; + if( rWall.IsBitmap() ) + { + aBitmap = rWall.GetBitmap(); + aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(), + getMapMode(), + this, + aBitmap.GetPrefSize() ); + tools::Rectangle aRect( rRect ); + if( rWall.IsRect() ) + { + aRect = rWall.GetRect(); + aBmpPos = aRect.TopLeft(); + aBmpSize = aRect.GetSize(); + } + if( rWall.GetStyle() != WallpaperStyle::Scale ) + { + if( rWall.GetStyle() != WallpaperStyle::Tile ) + { + bDrawBitmap = true; + if( rWall.IsGradient() ) + bDrawGradient = true; + else + bDrawColor = true; + switch( rWall.GetStyle() ) + { + case WallpaperStyle::TopLeft: + break; + case WallpaperStyle::Top: + aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); + break; + case WallpaperStyle::Left: + aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); + break; + case WallpaperStyle::TopRight: + aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); + break; + case WallpaperStyle::Center: + aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); + aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); + break; + case WallpaperStyle::Right: + aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); + aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); + break; + case WallpaperStyle::BottomLeft: + aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); + break; + case WallpaperStyle::Bottom: + aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); + aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); + break; + case WallpaperStyle::BottomRight: + aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); + aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); + break; + default: ; + } + } + else + { + // push the bitmap + const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() ); + + // convert to page coordinates; this needs to be done here + // since the emit does not know the page anymore + tools::Rectangle aConvertRect( aBmpPos, aBmpSize ); + m_aPages.back().convertRect( aConvertRect ); + + OString aImageName = "Im" + OString::number( rEmit.m_nObject ); + + // push the pattern + OStringBuffer aTilingStream( 32 ); + appendFixedInt( aConvertRect.GetWidth(), aTilingStream ); + aTilingStream.append( " 0 0 " ); + appendFixedInt( aConvertRect.GetHeight(), aTilingStream ); + aTilingStream.append( " 0 0 cm\n/" ); + aTilingStream.append( aImageName ); + aTilingStream.append( " Do\n" ); + + m_aTilings.emplace_back( ); + m_aTilings.back().m_nObject = createObject(); + m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() ); + m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream()); + m_aTilings.back().m_pTilingStream->WriteBytes( + aTilingStream.getStr(), aTilingStream.getLength() ); + // phase the tiling so wallpaper begins on upper left + if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0)) + throw o3tl::divide_by_zero(); + m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor; + m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor; + m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject; + + updateGraphicsState(); + + OStringBuffer aObjName( 16 ); + aObjName.append( 'P' ); + aObjName.append( m_aTilings.back().m_nObject ); + OString aPatternName( aObjName.makeStringAndClear() ); + pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject ); + + // fill a rRect with the pattern + OStringBuffer aLine( 100 ); + aLine.append( "q /Pattern cs /" ); + aLine.append( aPatternName ); + aLine.append( " scn " ); + m_aPages.back().appendRect( rRect, aLine ); + aLine.append( " f Q\n" ); + writeBuffer( aLine ); + } + } + else + { + aBmpPos = aRect.TopLeft(); + aBmpSize = aRect.GetSize(); + bDrawBitmap = true; + } + + if( aBitmap.IsAlpha() ) + { + if( rWall.IsGradient() ) + bDrawGradient = true; + else + bDrawColor = true; + } + } + else if( rWall.IsGradient() ) + bDrawGradient = true; + else + bDrawColor = true; + + if( bDrawGradient ) + { + drawGradient( rRect, rWall.GetGradient() ); + } + if( bDrawColor ) + { + Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor; + Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; + setLineColor( COL_TRANSPARENT ); + setFillColor( rWall.GetColor() ); + drawRectangle( rRect ); + setLineColor( aOldLineColor ); + setFillColor( aOldFillColor ); + } + if( bDrawBitmap ) + { + // set temporary clip region since aBmpPos and aBmpSize + // may be outside rRect + OStringBuffer aLine( 20 ); + aLine.append( "q " ); + m_aPages.back().appendRect( rRect, aLine ); + aLine.append( " W n\n" ); + writeBuffer( aLine ); + drawBitmap( aBmpPos, aBmpSize, aBitmap ); + writeBuffer( "Q\n" ); + } +} + +void PDFWriterImpl::updateGraphicsState(Mode const mode) +{ + OStringBuffer aLine( 256 ); + GraphicsState& rNewState = m_aGraphicsStack.front(); + // first set clip region since it might invalidate everything else + + if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion ) + { + rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion; + + if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion || + ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) ) + { + if( m_aCurrentPDFState.m_bClipRegion ) + { + aLine.append( "Q " ); + // invalidate everything but the clip region + m_aCurrentPDFState = GraphicsState(); + rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion; + } + if( rNewState.m_bClipRegion ) + { + // clip region is always stored in private PDF mapmode + MapMode aNewMapMode = rNewState.m_aMapMode; + rNewState.m_aMapMode = m_aMapMode; + SetMapMode( rNewState.m_aMapMode ); + m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; + + aLine.append("q "); + if ( rNewState.m_aClipRegion.count() ) + { + m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine ); + } + else + { + // tdf#130150 Need to revert tdf#99680, that breaks the + // rule that an set but empty clip-region clips everything + // aka draws nothing -> nothing is in an empty clip-region + aLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible + } + aLine.append( "W* n\n" ); + + rNewState.m_aMapMode = aNewMapMode; + SetMapMode( rNewState.m_aMapMode ); + m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; + } + } + } + + if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode ) + { + rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode; + SetMapMode( rNewState.m_aMapMode ); + } + + if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font ) + { + rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font; + SetFont( rNewState.m_aFont ); + } + + if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode ) + { + rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode; + SetLayoutMode( rNewState.m_nLayoutMode ); + } + + if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage ) + { + rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage; + SetDigitLanguage( rNewState.m_aDigitLanguage ); + } + + if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor ) + { + rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor; + if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor && + rNewState.m_aLineColor != COL_TRANSPARENT ) + { + appendStrokingColor( rNewState.m_aLineColor, aLine ); + aLine.append( "\n" ); + } + } + + if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor ) + { + rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor; + if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor && + rNewState.m_aFillColor != COL_TRANSPARENT ) + { + appendNonStrokingColor( rNewState.m_aFillColor, aLine ); + aLine.append( "\n" ); + } + } + + if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent ) + { + rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent; + if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) + { + // TODO: switch extended graphicsstate + } + } + + // everything is up to date now + m_aCurrentPDFState = m_aGraphicsStack.front(); + if ((mode != Mode::NOWRITE) && !aLine.isEmpty()) + writeBuffer( aLine ); +} + +/* #i47544# imitate OutputDevice behaviour: +* if a font with a nontransparent color is set, it overwrites the current +* text color. OTOH setting the text color will overwrite the color of the font. +*/ +void PDFWriterImpl::setFont( const vcl::Font& rFont ) +{ + Color aColor = rFont.GetColor(); + if( aColor == COL_TRANSPARENT ) + aColor = m_aGraphicsStack.front().m_aFont.GetColor(); + m_aGraphicsStack.front().m_aFont = rFont; + m_aGraphicsStack.front().m_aFont.SetColor( aColor ); + m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font; +} + +void PDFWriterImpl::push( PushFlags nFlags ) +{ + OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" ); + m_aGraphicsStack.push_front( m_aGraphicsStack.front() ); + m_aGraphicsStack.front().m_nFlags = nFlags; +} + +void PDFWriterImpl::pop() +{ + OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" ); + if( m_aGraphicsStack.size() < 2 ) + return; + + GraphicsState aState = m_aGraphicsStack.front(); + m_aGraphicsStack.pop_front(); + GraphicsState& rOld = m_aGraphicsStack.front(); + + // move those parameters back that were not pushed + // in the first place + if( ! (aState.m_nFlags & PushFlags::LINECOLOR) ) + setLineColor( aState.m_aLineColor ); + if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) ) + setFillColor( aState.m_aFillColor ); + if( ! (aState.m_nFlags & PushFlags::FONT) ) + setFont( aState.m_aFont ); + if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) ) + setTextColor( aState.m_aFont.GetColor() ); + if( ! (aState.m_nFlags & PushFlags::MAPMODE) ) + setMapMode( aState.m_aMapMode ); + if( ! (aState.m_nFlags & PushFlags::CLIPREGION) ) + { + // do not use setClipRegion here + // it would convert again assuming the current mapmode + rOld.m_aClipRegion = aState.m_aClipRegion; + rOld.m_bClipRegion = aState.m_bClipRegion; + } + if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) ) + setTextLineColor( aState.m_aTextLineColor ); + if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) ) + setOverlineColor( aState.m_aOverlineColor ); + if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) ) + setTextAlign( aState.m_aFont.GetAlignment() ); + if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) ) + setTextFillColor( aState.m_aFont.GetFillColor() ); + if( ! (aState.m_nFlags & PushFlags::REFPOINT) ) + { + // what ? + } + // invalidate graphics state + m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All; +} + +void PDFWriterImpl::setMapMode( const MapMode& rMapMode ) +{ + m_aGraphicsStack.front().m_aMapMode = rMapMode; + SetMapMode( rMapMode ); + m_aCurrentPDFState.m_aMapMode = rMapMode; +} + +void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion ) +{ + // tdf#130150 improve coordinate manipulations to double precision transformations + const basegfx::B2DHomMatrix aCurrentTransform( + GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode)); + basegfx::B2DPolyPolygon aRegion(rRegion); + + aRegion.transform(aCurrentTransform); + m_aGraphicsStack.front().m_aClipRegion = aRegion; + m_aGraphicsStack.front().m_bClipRegion = true; + m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; +} + +void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY ) +{ + if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) ) + return; + + // tdf#130150 improve coordinate manipulations to double precision transformations + basegfx::B2DHomMatrix aConvertA; + + if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit()) + { + aConvertA = GetInverseViewTransformation(m_aMapMode); + } + else + { + aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode); + } + + basegfx::B2DPoint aB2DPointA(nX, nY); + basegfx::B2DPoint aB2DPointB(0.0, 0.0); + aB2DPointA *= aConvertA; + aB2DPointB *= aConvertA; + aB2DPointA -= aB2DPointB; + basegfx::B2DHomMatrix aMat; + + aMat.translate(aB2DPointA.getX(), aB2DPointA.getY()); + m_aGraphicsStack.front().m_aClipRegion.transform( aMat ); + m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; +} + +void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect ) +{ + basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rRect) ) ); + intersectClipRegion( aRect ); +} + +void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion ) +{ + // tdf#130150 improve coordinate manipulations to double precision transformations + const basegfx::B2DHomMatrix aCurrentTransform( + GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode)); + basegfx::B2DPolyPolygon aRegion(rRegion); + + aRegion.transform(aCurrentTransform); + m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; + + if( m_aGraphicsStack.front().m_bClipRegion ) + { + basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) ); + aRegion = basegfx::utils::prepareForPolygonOperation( aRegion ); + m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion ); + } + else + { + m_aGraphicsStack.front().m_aClipRegion = aRegion; + m_aGraphicsStack.front().m_bClipRegion = true; + } +} + +void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr ) +{ + if (nPageNr < 0) + nPageNr = m_nCurrentPage; + + if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size()) + return; + + m_aNotes.emplace_back(); + auto & rNoteEntry = m_aNotes.back(); + rNoteEntry.m_nObject = createObject(); + rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject(); + rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject; + rNoteEntry.m_aContents = rNote; + rNoteEntry.m_aRect = rRect; + rNoteEntry.m_nPage = nPageNr; + // convert to default user space now, since the mapmode may change + m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect); + + // insert note to page's annotation list + m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject); + m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject); +} + +sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText) +{ + if( nPageNr < 0 ) + nPageNr = m_nCurrentPage; + + if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) + return -1; + + sal_Int32 nRet = m_aLinks.size(); + + m_aLinks.emplace_back(rAltText); + m_aLinks.back().m_nObject = createObject(); + m_aLinks.back().m_nPage = nPageNr; + m_aLinks.back().m_aRect = rRect; + // convert to default user space now, since the mapmode may change + m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect ); + + // insert link to page's annotation list + m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject ); + + return nRet; +} + +sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType) +{ + if (nPageNr < 0) + nPageNr = m_nCurrentPage; + + if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size()) + return -1; + + sal_Int32 nRet = m_aScreens.size(); + + m_aScreens.emplace_back(rAltText, rMimeType); + m_aScreens.back().m_nObject = createObject(); + m_aScreens.back().m_nPage = nPageNr; + m_aScreens.back().m_aRect = rRect; + // Convert to default user space now, since the mapmode may change. + m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect); + + // Insert link to page's annotation list. + m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject); + + return nRet; +} + +sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) +{ + if( nPageNr < 0 ) + nPageNr = m_nCurrentPage; + + if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) + return -1; + + sal_Int32 nRet = m_aNamedDests.size(); + + m_aNamedDests.emplace_back( ); + m_aNamedDests.back().m_aDestName = sDestName; + m_aNamedDests.back().m_nPage = nPageNr; + m_aNamedDests.back().m_eType = eType; + m_aNamedDests.back().m_aRect = rRect; + // convert to default user space now, since the mapmode may change + m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect ); + + return nRet; +} + +sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) +{ + if( nPageNr < 0 ) + nPageNr = m_nCurrentPage; + + if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) + return -1; + + sal_Int32 nRet = m_aDests.size(); + + m_aDests.emplace_back( ); + m_aDests.back().m_nPage = nPageNr; + m_aDests.back().m_eType = eType; + m_aDests.back().m_aRect = rRect; + // convert to default user space now, since the mapmode may change + m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect ); + + return nRet; +} + +sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) +{ + m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType ); + return m_aDestinationIdTranslation[ nDestId ]; +} + +void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) +{ + if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() ) + return; + if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() ) + return; + + m_aLinks[ nLinkId ].m_nDest = nDestId; +} + +void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL ) +{ + if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() ) + return; + + m_aLinks[ nLinkId ].m_nDest = -1; + + using namespace ::com::sun::star; + + if (!m_xTrans.is()) + { + uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() ); + m_xTrans = util::URLTransformer::create(xContext); + } + + util::URL aURL; + aURL.Complete = rURL; + + m_xTrans->parseStrict( aURL ); + + m_aLinks[ nLinkId ].m_aURL = aURL.Complete; +} + +void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL) +{ + if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size()) + return; + + m_aScreens[nScreenId].m_aURL = rURL; +} + +void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL) +{ + if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size()) + return; + + m_aScreens[nScreenId].m_aTempFileURL = rURL; + m_aScreens[nScreenId].m_nTempFileObject = createObject(); +} + +void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId ) +{ + m_aLinkPropertyMap[ nPropertyId ] = nLinkId; +} + +sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID ) +{ + // create new item + sal_Int32 nNewItem = m_aOutline.size(); + m_aOutline.emplace_back( ); + + // set item attributes + setOutlineItemParent( nNewItem, nParent ); + setOutlineItemText( nNewItem, rText ); + setOutlineItemDest( nNewItem, nDestID ); + + return nNewItem; +} + +void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent ) +{ + if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) + return; + + if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem ) + { + nNewParent = 0; + } + // insert item to new parent's list of children + m_aOutline[ nNewParent ].m_aChildren.push_back( nItem ); +} + +void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText ) +{ + if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) + return; + + m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText ); +} + +void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID ) +{ + if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist + return; + if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist + return; + m_aOutline[nItem].m_nDestID = nDestID; +} + +const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType ) +{ + static constexpr auto aTagStrings = frozen::make_map<PDFWriter::StructElement, const char*>({ + { PDFWriter::NonStructElement, "NonStruct" }, + { PDFWriter::Document, "Document" }, + { PDFWriter::Part, "Part" }, + { PDFWriter::Article, "Art" }, + { PDFWriter::Section, "Sect" }, + { PDFWriter::Division, "Div" }, + { PDFWriter::BlockQuote, "BlockQuote" }, + { PDFWriter::Caption, "Caption" }, + { PDFWriter::TOC, "TOC" }, + { PDFWriter::TOCI, "TOCI" }, + { PDFWriter::Index, "Index" }, + { PDFWriter::Paragraph, "P" }, + { PDFWriter::Heading, "H" }, + { PDFWriter::H1, "H1" }, + { PDFWriter::H2, "H2" }, + { PDFWriter::H3, "H3" }, + { PDFWriter::H4, "H4" }, + { PDFWriter::H5, "H5" }, + { PDFWriter::H6, "H6" }, + { PDFWriter::List, "L" }, + { PDFWriter::ListItem, "LI" }, + { PDFWriter::LILabel, "Lbl" }, + { PDFWriter::LIBody, "LBody" }, + { PDFWriter::Table, "Table" }, + { PDFWriter::TableRow, "TR" }, + { PDFWriter::TableHeader, "TH" }, + { PDFWriter::TableData, "TD" }, + { PDFWriter::Span, "Span" }, + { PDFWriter::Quote, "Quote" }, + { PDFWriter::Note, "Note" }, + { PDFWriter::Reference, "Reference" }, + { PDFWriter::BibEntry, "BibEntry" }, + { PDFWriter::Code, "Code" }, + { PDFWriter::Link, "Link" }, + { PDFWriter::Annot, "Annot" }, + { PDFWriter::Ruby, "Ruby" }, + { PDFWriter::RB, "RB" }, + { PDFWriter::RT, "RT" }, + { PDFWriter::RP, "RP" }, + { PDFWriter::Warichu, "Warichu" }, + { PDFWriter::WT, "WT" }, + { PDFWriter::WP, "WP" }, + { PDFWriter::Figure, "Figure" }, + { PDFWriter::Formula, "Formula"}, + { PDFWriter::Form, "Form" } + }); + + if (eType == PDFWriter::Annot + && m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5) + { + return "Figure"; // fallback + } + + auto it = aTagStrings.find( eType ); + + return it != aTagStrings.end() ? it->second : "Div"; +} + +void PDFWriterImpl::addRoleMap(OString aAlias, PDFWriter::StructElement eType) +{ + OString aTag = getStructureTag(eType); + // For PDF/UA it's not allowed to map an alias with the same name. + // Not aware of a reason for doing it in any case, so just don't do it. + if (aAlias != aTag) + m_aRoleMap[aAlias] = aTag; +} + +void PDFWriterImpl::beginStructureElementMCSeq() +{ + assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType); + if( m_bEmitStructure && + m_nCurrentStructElement > 0 && // StructTreeRoot + // Document = SwPageFrame => this is not *inside* the page content + // stream so do not emit MCID! + m_aStructure[m_nCurrentStructElement].m_oType && + *m_aStructure[m_nCurrentStructElement].m_oType != PDFWriter::Document && + ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence + ) + { + PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ]; + OStringBuffer aLine( 128 ); + sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size(); + aLine.append( "/" ); + if( !rEle.m_aAlias.isEmpty() ) + aLine.append( rEle.m_aAlias ); + else + aLine.append( getStructureTag(*rEle.m_oType) ); + aLine.append( "<</MCID " ); + aLine.append( nMCID ); + aLine.append( ">>BDC\n" ); + writeBuffer( aLine ); + + // update the element's content list + SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object " + << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = " + << rEle.m_nFirstPageObject); + rEle.m_aKids.emplace_back(MCIDReference{m_aPages[m_nCurrentPage].m_nPageObject, nMCID}); + // update the page's mcid parent list + m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject ); + // mark element MC sequence as open + rEle.m_bOpenMCSeq = true; + } + // handle artifacts + else if( ! m_bEmitStructure && m_aContext.Tagged && + m_nCurrentStructElement > 0 && + m_aStructure[m_nCurrentStructElement].m_oType && + *m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement && + ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence + ) + { + OString aLine = "/Artifact "_ostr; + writeBuffer( aLine ); + // emit property list if requested + OStringBuffer buf; + for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes) + { + appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false); + } + if (buf.isEmpty()) + { + writeBuffer("BMC\n"); + } + else + { + writeBuffer("<<"); + writeBuffer(buf); + writeBuffer(">> BDC\n"); + } + // mark element MC sequence as open + m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true; + } +} + +void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode) +{ + if (m_nCurrentStructElement > 0 // not StructTreeRoot + && m_aStructure[m_nCurrentStructElement].m_oType + && (m_bEmitStructure + || (endMode != EndMode::OnlyStruct + && m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement)) + && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq) + { + writeBuffer( "EMC\n" ); + m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false; + } +} + +bool PDFWriterImpl::checkEmitStructure() +{ + bool bEmit = false; + if( m_aContext.Tagged ) + { + bEmit = true; + sal_Int32 nEle = m_nCurrentStructElement; + while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() ) + { + if (m_aStructure[nEle].m_oType + && *m_aStructure[nEle].m_oType == PDFWriter::NonStructElement) + { + bEmit = false; + break; + } + nEle = m_aStructure[ nEle ].m_nParentElement; + } + } + return bEmit; +} + +sal_Int32 PDFWriterImpl::ensureStructureElement() +{ + if( ! m_aContext.Tagged ) + return -1; + + sal_Int32 nNewId = sal_Int32(m_aStructure.size()); + m_aStructure.emplace_back(); + PDFStructureElement& rEle = m_aStructure.back(); + // leave rEle.m_oType uninitialised + rEle.m_nOwnElement = nNewId; + // temporary parent + rEle.m_nParentElement = m_nCurrentStructElement; + rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject; + m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId ); + return nNewId; +} + +void PDFWriterImpl::initStructureElement(sal_Int32 const id, + PDFWriter::StructElement const eType, std::u16string_view const rAlias) +{ + if( ! m_aContext.Tagged ) + return; + + if( m_nCurrentStructElement == 0 && + eType != PDFWriter::Document && eType != PDFWriter::NonStructElement ) + { + // struct tree root hit, but not beginning document + // this might happen with setCurrentStructureElement + // silently insert structure into document again if one properly exists + if( ! m_aStructure[ 0 ].m_aChildren.empty() ) + { + const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren; + auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(), + [&](sal_Int32 nElement) { + return m_aStructure[nElement].m_oType + && *m_aStructure[nElement].m_oType == PDFWriter::Document; }); + if( it != rRootChildren.end() ) + { + m_nCurrentStructElement = *it; + SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" ); + } + else { + OSL_FAIL( "document structure in disorder !" ); + } + } + else { + OSL_FAIL( "PDF document structure MUST be contained in a Document element" ); + } + } + + PDFStructureElement& rEle = m_aStructure[id]; + assert(!rEle.m_oType); + rEle.m_oType.emplace(eType); + // remove it from its possibly placeholder parent; append to real parent + auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(), + m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id)); + assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end()); + m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it); + rEle.m_nParentElement = m_nCurrentStructElement; + rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject; + m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id); + + // handle alias names + if( !rAlias.empty() && eType != PDFWriter::NonStructElement ) + { + OStringBuffer aNameBuf( rAlias.size() ); + appendName( rAlias, aNameBuf ); + OString aAliasName( aNameBuf.makeStringAndClear() ); + rEle.m_aAlias = aAliasName; + addRoleMap(aAliasName, eType); + } + + if (m_bEmitStructure && eType != PDFWriter::NonStructElement) // don't create nonexistent objects + { + rEle.m_nObject = createObject(); + // update parent's kids list + m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject}); + // ISO 14289-1:2014, Clause: 7.9 + if (*rEle.m_oType == PDFWriter::Note) + { + m_StructElemObjsWithID.insert(rEle.m_nObject); + } + } +} + +void PDFWriterImpl::beginStructureElement(sal_Int32 const id) +{ + if( m_nCurrentPage < 0 ) + return; + + if( ! m_aContext.Tagged ) + return; + + assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged"); + + // close eventual current MC sequence + endStructureElementMCSeq(EndMode::OnlyStruct); + + PDFStructureElement& rEle = m_aStructure[id]; + m_StructElementStack.push(m_nCurrentStructElement); + m_nCurrentStructElement = id; + + if (g_bDebugDisableCompression) + { + OStringBuffer aLine( "beginStructureElement " ); + aLine.append( m_nCurrentStructElement ); + aLine.append( ": " ); + aLine.append( rEle.m_oType + ? getStructureTag(*rEle.m_oType) + : "<placeholder>" ); + if( !rEle.m_aAlias.isEmpty() ) + { + aLine.append( " aliased as \"" ); + aLine.append( rEle.m_aAlias ); + aLine.append( '\"' ); + } + emitComment( aLine.getStr() ); + } + + // check whether to emit structure henceforth + m_bEmitStructure = checkEmitStructure(); +} + +void PDFWriterImpl::endStructureElement() +{ + if( m_nCurrentPage < 0 ) + return; + + if( ! m_aContext.Tagged ) + return; + + if( m_nCurrentStructElement == 0 ) + { + // hit the struct tree root, that means there is an endStructureElement + // without corresponding beginStructureElement + return; + } + + // end the marked content sequence + endStructureElementMCSeq(); + + OStringBuffer aLine; + if (g_bDebugDisableCompression) + { + aLine.append( "endStructureElement " ); + aLine.append( m_nCurrentStructElement ); + aLine.append( ": " ); + aLine.append( m_aStructure[m_nCurrentStructElement].m_oType + ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) + : "<placeholder>" ); + if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() ) + { + aLine.append( " aliased as \"" ); + aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); + aLine.append( '\"' ); + } + } + + // "end" the structure element, the parent becomes current element + m_nCurrentStructElement = m_StructElementStack.top(); + m_StructElementStack.pop(); + + // check whether to emit structure henceforth + m_bEmitStructure = checkEmitStructure(); + + if (g_bDebugDisableCompression && m_bEmitStructure) + { + emitComment( aLine.getStr() ); + } +} + +namespace { + +void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure, + std::vector<sal_Int32>::iterator & rParentIt) +{ + PDFStructureElement& rEle(rStructure[*rParentIt]); + removePlaceholderSE(rStructure, rEle); + + if (!rEle.m_oType) + { + // Placeholder was not initialised - should not happen when printing + // a full page, but might if a selection is printed, which can be only + // a shape without its anchor. + // Handle this by moving the children to the parent SE. + PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]); + rParentIt = rParent.m_aChildren.erase(rParentIt); + std::vector<sal_Int32> children; + for (auto const child : rEle.m_aChildren) + { + PDFStructureElement& rChild = rStructure[child]; + rChild.m_nParentElement = rEle.m_nParentElement; + children.push_back(rChild.m_nOwnElement); + } + rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end()) + + children.size(); + } + else + { + ++rParentIt; + } + +} + +void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle) +{ + for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ) + { + removePlaceholderSEImpl(rStructure, it); + } +} + +} // end anonymous namespace + +/* + * This function adds an internal structure list container to overcome the 8191 elements array limitation + * in kids element emission. + * Recursive function + * + */ +void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle ) +{ + if (rEle.m_nOwnElement != rEle.m_nParentElement + && *rEle.m_oType == PDFWriter::NonStructElement) + { + return; + } + + for (auto const& child : rEle.m_aChildren) + { + assert(child > 0 && o3tl::make_unsigned(child) < m_aStructure.size()); + if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() ) + { + PDFStructureElement& rChild = m_aStructure[ child ]; + if (*rChild.m_oType != PDFWriter::NonStructElement) + { + //triggered when a child of the rEle element is found + assert(rChild.m_nParentElement == rEle.m_nOwnElement); + if( rChild.m_nParentElement == rEle.m_nOwnElement ) + addInternalStructureContainer( rChild );//examine the child + else + { + OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" ); + SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child ); + } + } + } + else + { + OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" ); + SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child ); + } + } + + if( rEle.m_nOwnElement == rEle.m_nParentElement ) + return; + + if( rEle.m_aKids.empty() ) + return; + + if( rEle.m_aKids.size() <= ncMaxPDFArraySize ) return; + + //then we need to add the containers for the kids elements + // a list to be used for the new kid element + std::list< PDFStructureElementKid > aNewKids; + std::vector< sal_Int32 > aNewChildren; + + // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?) + OString aAliasName("Div"_ostr); + addRoleMap(aAliasName, PDFWriter::Division); + + while( rEle.m_aKids.size() > ncMaxPDFArraySize ) + { + sal_Int32 nCurrentStructElement = rEle.m_nOwnElement; + sal_Int32 nNewId = sal_Int32(m_aStructure.size()); + m_aStructure.emplace_back( ); + PDFStructureElement& rEleNew = m_aStructure.back(); + rEleNew.m_aAlias = aAliasName; + rEleNew.m_oType.emplace(PDFWriter::Division); // a new Div type container + rEleNew.m_nOwnElement = nNewId; + rEleNew.m_nParentElement = nCurrentStructElement; + //inherit the same page as the first child to be reparented + rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject; + rEleNew.m_nObject = createObject();//assign a PDF object number + //add the object to the kid list of the parent + aNewKids.emplace_back(ObjReference{rEleNew.m_nObject}); + aNewChildren.push_back( nNewId ); + + std::vector< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() ); + std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() ); + advance( aChildEndIt, ncMaxPDFArraySize ); + advance( aKidEndIt, ncMaxPDFArraySize ); + + rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(), + rEle.m_aKids, + rEle.m_aKids.begin(), + aKidEndIt ); + rEleNew.m_aChildren.insert( rEleNew.m_aChildren.begin(), + rEle.m_aChildren.begin(), + aChildEndIt ); + rEle.m_aChildren.erase( rEle.m_aChildren.begin(), aChildEndIt ); + + // set the kid's new parent + for (auto const& child : rEleNew.m_aChildren) + { + m_aStructure[ child ].m_nParentElement = nNewId; + } + } + //finally add the new kids resulting from the container added + rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() ); + rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() ); +} + +bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle ) +{ + bool bSuccess = false; + + if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() ) + { + // end eventual previous marked content sequence + endStructureElementMCSeq(); + + m_nCurrentStructElement = nEle; + m_bEmitStructure = checkEmitStructure(); + if (g_bDebugDisableCompression) + { + OStringBuffer aLine( "setCurrentStructureElement " ); + aLine.append( m_nCurrentStructElement ); + aLine.append( ": " ); + aLine.append( m_aStructure[m_nCurrentStructElement].m_oType + ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) + : "<placeholder>" ); + if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() ) + { + aLine.append( " aliased as \"" ); + aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); + aLine.append( '\"' ); + } + if( ! m_bEmitStructure ) + aLine.append( " (inside NonStruct)" ); + emitComment( aLine.getStr() ); + } + bSuccess = true; + } + + return bSuccess; +} + +bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal ) +{ + if( !m_aContext.Tagged ) + return false; + + assert(m_aStructure[m_nCurrentStructElement].m_oType); + bool bInsert = false; + if (m_nCurrentStructElement > 0 + && (m_bEmitStructure + // allow it for topmost non-structured element + || (m_aContext.Tagged + && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement + || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType + || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != PDFWriter::NonStructElement)))) + { + PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; + switch( eAttr ) + { + case PDFWriter::Placement: + if( eVal == PDFWriter::Block || + eVal == PDFWriter::Inline || + eVal == PDFWriter::Before || + eVal == PDFWriter::Start || + eVal == PDFWriter::End ) + bInsert = true; + break; + case PDFWriter::WritingMode: + if( eVal == PDFWriter::LrTb || + eVal == PDFWriter::RlTb || + eVal == PDFWriter::TbRl ) + { + bInsert = true; + } + break; + case PDFWriter::TextAlign: + if( eVal == PDFWriter::Start || + eVal == PDFWriter::Center || + eVal == PDFWriter::End || + eVal == PDFWriter::Justify ) + { + if( eType == PDFWriter::Paragraph || + eType == PDFWriter::Heading || + eType == PDFWriter::H1 || + eType == PDFWriter::H2 || + eType == PDFWriter::H3 || + eType == PDFWriter::H4 || + eType == PDFWriter::H5 || + eType == PDFWriter::H6 || + eType == PDFWriter::List || + eType == PDFWriter::ListItem || + eType == PDFWriter::LILabel || + eType == PDFWriter::LIBody || + eType == PDFWriter::Table || + eType == PDFWriter::TableRow || + eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData ) + { + bInsert = true; + } + } + break; + case PDFWriter::Width: + case PDFWriter::Height: + if( eVal == PDFWriter::Auto ) + { + if( eType == PDFWriter::Figure || + eType == PDFWriter::Formula || + eType == PDFWriter::Form || + eType == PDFWriter::Table || + eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData ) + { + bInsert = true; + } + } + break; + case PDFWriter::BlockAlign: + if( eVal == PDFWriter::Before || + eVal == PDFWriter::Middle || + eVal == PDFWriter::After || + eVal == PDFWriter::Justify ) + { + if( eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData ) + { + bInsert = true; + } + } + break; + case PDFWriter::InlineAlign: + if( eVal == PDFWriter::Start || + eVal == PDFWriter::Center || + eVal == PDFWriter::End ) + { + if( eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData ) + { + bInsert = true; + } + } + break; + case PDFWriter::LineHeight: + if( eVal == PDFWriter::Normal || + eVal == PDFWriter::Auto ) + { + // only for ILSE and BLSE + if( eType == PDFWriter::Paragraph || + eType == PDFWriter::Heading || + eType == PDFWriter::H1 || + eType == PDFWriter::H2 || + eType == PDFWriter::H3 || + eType == PDFWriter::H4 || + eType == PDFWriter::H5 || + eType == PDFWriter::H6 || + eType == PDFWriter::List || + eType == PDFWriter::ListItem || + eType == PDFWriter::LILabel || + eType == PDFWriter::LIBody || + eType == PDFWriter::Table || + eType == PDFWriter::TableRow || + eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData || + eType == PDFWriter::Span || + eType == PDFWriter::Quote || + eType == PDFWriter::Note || + eType == PDFWriter::Reference || + eType == PDFWriter::BibEntry || + eType == PDFWriter::Code || + eType == PDFWriter::Link ) + { + bInsert = true; + } + } + break; + case PDFWriter::TextDecorationType: + if( eVal == PDFWriter::NONE || + eVal == PDFWriter::Underline || + eVal == PDFWriter::Overline || + eVal == PDFWriter::LineThrough ) + { + // only for ILSE and BLSE + if( eType == PDFWriter::Paragraph || + eType == PDFWriter::Heading || + eType == PDFWriter::H1 || + eType == PDFWriter::H2 || + eType == PDFWriter::H3 || + eType == PDFWriter::H4 || + eType == PDFWriter::H5 || + eType == PDFWriter::H6 || + eType == PDFWriter::List || + eType == PDFWriter::ListItem || + eType == PDFWriter::LILabel || + eType == PDFWriter::LIBody || + eType == PDFWriter::Table || + eType == PDFWriter::TableRow || + eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData || + eType == PDFWriter::Span || + eType == PDFWriter::Quote || + eType == PDFWriter::Note || + eType == PDFWriter::Reference || + eType == PDFWriter::BibEntry || + eType == PDFWriter::Code || + eType == PDFWriter::Link ) + { + bInsert = true; + } + } + break; + case PDFWriter::Scope: + if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both) + { + if (eType == PDFWriter::TableHeader + && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version) + { + bInsert = true; + } + } + break; + case PDFWriter::Type: + if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page) + // + Background for PDF >= 1.7 + { + if (eType == PDFWriter::NonStructElement) + { + bInsert = true; + } + } + break; + case PDFWriter::Subtype: + if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark) + { + if (eType == PDFWriter::NonStructElement + && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) + { + bInsert = true; + } + } + break; + case PDFWriter::Role: + if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv) + { + if (eType == PDFWriter::Form + && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) + { + bInsert = true; + } + } + break; + case PDFWriter::RubyAlign: + if (eVal == PDFWriter::RStart || eVal == PDFWriter::RCenter || eVal == PDFWriter::REnd || eVal == PDFWriter::RJustify || eVal == PDFWriter::RDistribute) + { + if (eType == PDFWriter::RT + && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version) + { + bInsert = true; + } + } + break; + case PDFWriter::RubyPosition: + if (eVal == PDFWriter::RBefore || eVal == PDFWriter::RAfter || eVal == PDFWriter::RWarichu || eVal == PDFWriter::RInline) + { + if (eType == PDFWriter::RT + && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version) + { + bInsert = true; + } + } + break; + case PDFWriter::ListNumbering: + if( eVal == PDFWriter::NONE || + eVal == PDFWriter::Disc || + eVal == PDFWriter::Circle || + eVal == PDFWriter::Square || + eVal == PDFWriter::Decimal || + eVal == PDFWriter::UpperRoman || + eVal == PDFWriter::LowerRoman || + eVal == PDFWriter::UpperAlpha || + eVal == PDFWriter::LowerAlpha ) + { + if( eType == PDFWriter::List ) + bInsert = true; + } + break; + default: break; + } + } + + if( bInsert ) + m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal ); + else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) + SAL_INFO("vcl.pdfwriter", + "rejecting setStructureAttribute( " << getAttributeTag( eAttr ) + << ", " << getAttributeValueTag( eVal ) + << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) + << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias + << ") element"); + + return bInsert; +} + +bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue ) +{ + if( ! m_aContext.Tagged ) + return false; + + assert(m_aStructure[m_nCurrentStructElement].m_oType); + bool bInsert = false; + if( m_nCurrentStructElement > 0 && m_bEmitStructure ) + { + if( eAttr == PDFWriter::Language ) + { + m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale(); + return true; + } + + PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; + switch( eAttr ) + { + case PDFWriter::SpaceBefore: + case PDFWriter::SpaceAfter: + case PDFWriter::StartIndent: + case PDFWriter::EndIndent: + // just for BLSE + if( eType == PDFWriter::Paragraph || + eType == PDFWriter::Heading || + eType == PDFWriter::H1 || + eType == PDFWriter::H2 || + eType == PDFWriter::H3 || + eType == PDFWriter::H4 || + eType == PDFWriter::H5 || + eType == PDFWriter::H6 || + eType == PDFWriter::List || + eType == PDFWriter::ListItem || + eType == PDFWriter::LILabel || + eType == PDFWriter::LIBody || + eType == PDFWriter::Table || + eType == PDFWriter::TableRow || + eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData ) + { + bInsert = true; + } + break; + case PDFWriter::TextIndent: + // paragraph like BLSE and additional elements + if( eType == PDFWriter::Paragraph || + eType == PDFWriter::Heading || + eType == PDFWriter::H1 || + eType == PDFWriter::H2 || + eType == PDFWriter::H3 || + eType == PDFWriter::H4 || + eType == PDFWriter::H5 || + eType == PDFWriter::H6 || + eType == PDFWriter::LILabel || + eType == PDFWriter::LIBody || + eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData ) + { + bInsert = true; + } + break; + case PDFWriter::Width: + case PDFWriter::Height: + if( eType == PDFWriter::Figure || + eType == PDFWriter::Formula || + eType == PDFWriter::Form || + eType == PDFWriter::Table || + eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData ) + { + bInsert = true; + } + break; + case PDFWriter::LineHeight: + case PDFWriter::BaselineShift: + // only for ILSE and BLSE + if( eType == PDFWriter::Paragraph || + eType == PDFWriter::Heading || + eType == PDFWriter::H1 || + eType == PDFWriter::H2 || + eType == PDFWriter::H3 || + eType == PDFWriter::H4 || + eType == PDFWriter::H5 || + eType == PDFWriter::H6 || + eType == PDFWriter::List || + eType == PDFWriter::ListItem || + eType == PDFWriter::LILabel || + eType == PDFWriter::LIBody || + eType == PDFWriter::Table || + eType == PDFWriter::TableRow || + eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData || + eType == PDFWriter::Span || + eType == PDFWriter::Quote || + eType == PDFWriter::Note || + eType == PDFWriter::Reference || + eType == PDFWriter::BibEntry || + eType == PDFWriter::Code || + eType == PDFWriter::Link ) + { + bInsert = true; + } + break; + case PDFWriter::RowSpan: + case PDFWriter::ColSpan: + // only for table cells + if( eType == PDFWriter::TableHeader || + eType == PDFWriter::TableData ) + { + bInsert = true; + } + break; + case PDFWriter::LinkAnnotation: + if( eType == PDFWriter::Link ) + bInsert = true; + break; + default: break; + } + } + + if( bInsert ) + m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue ); + else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) + SAL_INFO("vcl.pdfwriter", + "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr ) + << ", " << static_cast<int>(nValue) + << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) + << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias + << ") element"); + + return bInsert; +} + +void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect ) +{ + sal_Int32 nPageNr = m_nCurrentPage; + if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged ) + return; + + if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) ) + return; + + assert(m_aStructure[m_nCurrentStructElement].m_oType); + PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; + if( eType == PDFWriter::Figure || + eType == PDFWriter::Formula || + eType == PDFWriter::Form || + eType == PDFWriter::Division || + eType == PDFWriter::Table ) + { + m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect; + // convert to default user space now, since the mapmode may change + m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox ); + } +} + +void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds) +{ + assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage))); + + if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure) + { + return; + } + + m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds; +} + +void PDFWriterImpl::setActualText( const OUString& rText ) +{ + if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) + { + m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText; + } +} + +void PDFWriterImpl::setAlternateText( const OUString& rText ) +{ + if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) + { + m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText; + } +} + +void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr ) +{ + if( nPageNr < 0 ) + nPageNr = m_nCurrentPage; + + if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) + return; + + m_aPages[ nPageNr ].m_eTransition = eType; + m_aPages[ nPageNr ].m_nTransTime = nMilliSec; +} + +void PDFWriterImpl::ensureUniqueRadioOnValues() +{ + // loop over radio groups + for (auto const& group : m_aRadioGroupWidgets) + { + PDFWidget& rGroupWidget = m_aWidgets[ group.second ]; + // check whether all kids have a unique OnValue + std::unordered_map< OUString, sal_Int32 > aOnValues; + bool bIsUnique = true; + for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) + { + const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue; + SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal); + if( aOnValues.find( rVal ) == aOnValues.end() ) + { + aOnValues[ rVal ] = 1; + } + else + { + bIsUnique = false; + break; + } + } + if( ! bIsUnique ) + { + SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" ); + // make unique by using ascending OnValues + int nKid = 0; + for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) + { + PDFWidget& rKid = m_aWidgets[nKidIndex]; + rKid.m_aOnValue = OUString::number( nKid+1 ); + if( rKid.m_aValue != "Off" ) + rKid.m_aValue = rKid.m_aOnValue; + ++nKid; + } + } + // finally move the "Yes" appearance to the OnValue appearance + for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) + { + PDFWidget& rKid = m_aWidgets[nKidIndex]; + if ( !rKid.m_aOnValue.isEmpty() ) + { + auto app_it = rKid.m_aAppearances.find( "N"_ostr ); + if( app_it != rKid.m_aAppearances.end() ) + { + auto stream_it = app_it->second.find( "Yes"_ostr ); + if( stream_it != app_it->second.end() ) + { + SvMemoryStream* pStream = stream_it->second; + app_it->second.erase( stream_it ); + OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 ); + appendName( rKid.m_aOnValue, aBuf ); + (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; + } + else + SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" ); + } + } + + if ( !rKid.m_aOffValue.isEmpty() ) + { + auto app_it = rKid.m_aAppearances.find( "N"_ostr ); + if( app_it != rKid.m_aAppearances.end() ) + { + auto stream_it = app_it->second.find( "Off"_ostr ); + if( stream_it != app_it->second.end() ) + { + SvMemoryStream* pStream = stream_it->second; + app_it->second.erase( stream_it ); + OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 ); + appendName( rKid.m_aOffValue, aBuf ); + (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; + } + else + SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" ); + } + } + + // update selected radio button + if( rKid.m_aValue != "Off" ) + { + rGroupWidget.m_aValue = rKid.m_aValue; + } + } + } +} + +sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn ) +{ + sal_Int32 nRadioGroupWidget = -1; + + std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup ); + + if( it == m_aRadioGroupWidgets.end() ) + { + m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget = + sal_Int32(m_aWidgets.size()); + + // new group, insert the radiobutton + m_aWidgets.emplace_back( ); + m_aWidgets.back().m_nObject = createObject(); + m_aWidgets.back().m_nPage = m_nCurrentPage; + m_aWidgets.back().m_eType = PDFWriter::RadioButton; + m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup; + m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits + + createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn ); + } + else + nRadioGroupWidget = it->second; + + return nRadioGroupWidget; +} + +sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr ) +{ + if( nPageNr < 0 ) + nPageNr = m_nCurrentPage; + + if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) + return -1; + + bool sigHidden(true); + sal_Int32 nNewWidget = m_aWidgets.size(); + m_aWidgets.emplace_back( ); + + m_aWidgets.back().m_nObject = createObject(); + m_aWidgets.back().m_aRect = rControl.Location; + m_aWidgets.back().m_nPage = nPageNr; + m_aWidgets.back().m_eType = rControl.getType(); + + sal_Int32 nRadioGroupWidget = -1; + // for unknown reasons the radio buttons of a radio group must not have a + // field name, else the buttons are in fact check boxes - + // that is multiple buttons of the radio group can be selected + if( rControl.getType() == PDFWriter::RadioButton ) + nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) ); + else + { + createWidgetFieldName( nNewWidget, rControl ); + } + + // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid + PDFWidget& rNewWidget = m_aWidgets[nNewWidget]; + rNewWidget.m_aDescription = rControl.Description; + rNewWidget.m_aText = rControl.Text; + rNewWidget.m_nTextStyle = rControl.TextStyle & + ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top | + DrawTextFlags::VCenter | DrawTextFlags::Bottom | + DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); + rNewWidget.m_nTabOrder = rControl.TabOrder; + + // various properties are set via the flags (/Ff) property of the field dict + if( rControl.ReadOnly ) + rNewWidget.m_nFlags |= 1; + if( rControl.getType() == PDFWriter::PushButton ) + { + const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl); + if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) + rNewWidget.m_nTextStyle = + DrawTextFlags::Center | DrawTextFlags::VCenter | + DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; + + rNewWidget.m_nFlags |= 0x00010000; + if( !rBtn.URL.isEmpty() ) + rNewWidget.m_aListEntries.push_back( rBtn.URL ); + rNewWidget.m_bSubmit = rBtn.Submit; + rNewWidget.m_bSubmitGet = rBtn.SubmitGet; + rNewWidget.m_nDest = rBtn.Dest; + createDefaultPushButtonAppearance( rNewWidget, rBtn ); + } + else if( rControl.getType() == PDFWriter::RadioButton ) + { + const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl); + if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) + rNewWidget.m_nTextStyle = + DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; + /* PDF sees a RadioButton group as one radio button with + * children which are in turn check boxes + * + * so we need to create a radio button on demand for a new group + * and insert a checkbox for each RadioButtonWidget as its child + */ + rNewWidget.m_eType = PDFWriter::CheckBox; + rNewWidget.m_nRadioGroup = rBtn.RadioGroup; + + SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" ); + + PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget]; + rRadioButton.m_aKids.push_back( rNewWidget.m_nObject ); + rRadioButton.m_aKidsIndex.push_back( nNewWidget ); + rNewWidget.m_nParent = rRadioButton.m_nObject; + + rNewWidget.m_aValue = "Off"; + rNewWidget.m_aOnValue = rBtn.OnValue; + rNewWidget.m_aOffValue = rBtn.OffValue; + if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected ) + { + rNewWidget.m_aValue = rNewWidget.m_aOnValue; + rRadioButton.m_aValue = rNewWidget.m_aOnValue; + } + createDefaultRadioButtonAppearance( rNewWidget, rBtn ); + + // union rect of radio group + tools::Rectangle aRect = rNewWidget.m_aRect; + m_aPages[ nPageNr ].convertRect( aRect ); + rRadioButton.m_aRect.Union( aRect ); + } + else if( rControl.getType() == PDFWriter::CheckBox ) + { + const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl); + if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) + rNewWidget.m_nTextStyle = + DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; + + rNewWidget.m_aValue + = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" ); + rNewWidget.m_aOnValue = rBox.OnValue; + rNewWidget.m_aOffValue = rBox.OffValue; + // create default appearance before m_aRect gets transformed + createDefaultCheckBoxAppearance( rNewWidget, rBox ); + } + else if( rControl.getType() == PDFWriter::ListBox ) + { + if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) + rNewWidget.m_nTextStyle = DrawTextFlags::VCenter; + + const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl); + rNewWidget.m_aListEntries = rLstBox.Entries; + rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries; + rNewWidget.m_aValue = rLstBox.Text; + if( rLstBox.DropDown ) + rNewWidget.m_nFlags |= 0x00020000; + if (rLstBox.MultiSelect && !rLstBox.DropDown) + rNewWidget.m_nFlags |= 0x00200000; + + createDefaultListBoxAppearance( rNewWidget, rLstBox ); + } + else if( rControl.getType() == PDFWriter::ComboBox ) + { + if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) + rNewWidget.m_nTextStyle = DrawTextFlags::VCenter; + + const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl); + rNewWidget.m_aValue = rBox.Text; + rNewWidget.m_aListEntries = rBox.Entries; + rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag + + PDFWriter::ListBoxWidget aLBox; + aLBox.Name = rBox.Name; + aLBox.Description = rBox.Description; + aLBox.Text = rBox.Text; + aLBox.TextStyle = rBox.TextStyle; + aLBox.ReadOnly = rBox.ReadOnly; + aLBox.Border = rBox.Border; + aLBox.BorderColor = rBox.BorderColor; + aLBox.Background = rBox.Background; + aLBox.BackgroundColor = rBox.BackgroundColor; + aLBox.TextFont = rBox.TextFont; + aLBox.TextColor = rBox.TextColor; + aLBox.DropDown = true; + aLBox.MultiSelect = false; + aLBox.Entries = rBox.Entries; + + createDefaultListBoxAppearance( rNewWidget, aLBox ); + } + else if( rControl.getType() == PDFWriter::Edit ) + { + if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) + rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter; + + const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl); + if( rEdit.MultiLine ) + { + rNewWidget.m_nFlags |= 0x00001000; + rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; + } + if( rEdit.Password ) + rNewWidget.m_nFlags |= 0x00002000; + if (rEdit.FileSelect) + rNewWidget.m_nFlags |= 0x00100000; + rNewWidget.m_nMaxLen = rEdit.MaxLen; + rNewWidget.m_nFormat = rEdit.Format; + rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol; + rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy; + rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol; + rNewWidget.m_aTimeFormat = rEdit.TimeFormat; + rNewWidget.m_aDateFormat = rEdit.DateFormat; + rNewWidget.m_aValue = rEdit.Text; + + createDefaultEditAppearance( rNewWidget, rEdit ); + } +#if HAVE_FEATURE_NSS + else if( rControl.getType() == PDFWriter::Signature) + { + sigHidden = true; + + rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0); + + m_nSignatureObject = createObject(); + rNewWidget.m_aValue = OUString::number( m_nSignatureObject ); + rNewWidget.m_aValue += " 0 R"; + // let's add a fake appearance + rNewWidget.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream(); + } +#endif + + // if control is a hidden signature, do not convert coordinates since we + // need /Rect [ 0 0 0 0 ] + if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) ) + { + // convert to default user space now, since the mapmode may change + // note: create default appearances before m_aRect gets transformed + m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect ); + } + + // insert widget to page's annotation list + m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject ); + + return nNewWidget; +} + +void PDFWriterImpl::MARK( const char* pString ) +{ + beginStructureElementMCSeq(); + if (g_bDebugDisableCompression) + emitComment( pString ); +} + +sal_Int32 ReferenceXObjectEmit::getObject() const +{ + if (m_nFormObject > 0) + return m_nFormObject; + else + return m_nBitmapObject; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/pdfwriter_impl2.cxx b/vcl/source/gdi/pdfwriter_impl2.cxx new file mode 100644 index 0000000000..58fc31aafd --- /dev/null +++ b/vcl/source/gdi/pdfwriter_impl2.cxx @@ -0,0 +1,2047 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <pdf/pdfwriter_impl.hxx> + +#include <vcl/pdfextoutdevdata.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/graph.hxx> + +#include <unotools/streamwrap.hxx> + +#include <tools/helpers.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> + +#include <comphelper/fileformat.h> +#include <comphelper/hash.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/beans/XMaterialHolder.hpp> + +#include <cppuhelper/implbase.hxx> +#include <o3tl/unit_conversion.hxx> +#include <vcl/skia/SkiaHelper.hxx> + +#include <sal/log.hxx> +#include <memory> + +using namespace vcl; +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; + +static bool lcl_canUsePDFAxialShading(const Gradient& rGradient); + +void PDFWriterImpl::implWriteGradient( const tools::PolyPolygon& i_rPolyPoly, const Gradient& i_rGradient, + VirtualDevice* i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext ) +{ + GDIMetaFile aTmpMtf; + Gradient aGradient(i_rGradient); + + aGradient.AddGradientActions( i_rPolyPoly.GetBoundRect(), aTmpMtf ); + + m_rOuterFace.Push(); + m_rOuterFace.IntersectClipRegion( i_rPolyPoly.getB2DPolyPolygon() ); + playMetafile( aTmpMtf, nullptr, i_rContext, i_pDummyVDev ); + m_rOuterFace.Pop(); +} + +void PDFWriterImpl::implWriteBitmapEx( const Point& i_rPoint, const Size& i_rSize, const BitmapEx& i_rBitmapEx, const Graphic& i_Graphic, + VirtualDevice const * i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext ) +{ + if ( i_rBitmapEx.IsEmpty() || !i_rSize.Width() || !i_rSize.Height() ) + return; + + BitmapEx aBitmapEx( i_rBitmapEx ); + Point aPoint( i_rPoint ); + Size aSize( i_rSize ); + + // #i19065# Negative sizes have mirror semantics on + // OutputDevice. BitmapEx and co. have no idea about that, so + // perform that _before_ doing anything with aBitmapEx. + BmpMirrorFlags nMirrorFlags(BmpMirrorFlags::NONE); + if( aSize.Width() < 0 ) + { + aSize.setWidth( aSize.Width() * -1 ); + aPoint.AdjustX( -(aSize.Width()) ); + nMirrorFlags |= BmpMirrorFlags::Horizontal; + } + if( aSize.Height() < 0 ) + { + aSize.setHeight( aSize.Height() * -1 ); + aPoint.AdjustY( -(aSize.Height()) ); + nMirrorFlags |= BmpMirrorFlags::Vertical; + } + + if( nMirrorFlags != BmpMirrorFlags::NONE ) + { + aBitmapEx.Mirror( nMirrorFlags ); + } + + bool bIsJpeg = false, bIsPng = false; + if( i_Graphic.GetType() != GraphicType::NONE && i_Graphic.GetBitmapEx() == aBitmapEx ) + { + GfxLinkType eType = i_Graphic.GetGfxLink().GetType(); + bIsJpeg = (eType == GfxLinkType::NativeJpg); + bIsPng = (eType == GfxLinkType::NativePng); + } + + // Do not downsample images smaller than 50x50px. + const Size aBmpSize(aBitmapEx.GetSizePixel()); + if (i_rContext.m_nMaxImageResolution > 50 && aBmpSize.getWidth() > 50 + && aBmpSize.getHeight() > 50) + { + // do downsampling if necessary + const Size aDstSizeTwip( i_pDummyVDev->PixelToLogic(i_pDummyVDev->LogicToPixel(aSize), MapMode(MapUnit::MapTwip)) ); + const double fBmpPixelX = aBmpSize.Width(); + const double fBmpPixelY = aBmpSize.Height(); + const double fMaxPixelX + = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in) + * i_rContext.m_nMaxImageResolution; + const double fMaxPixelY + = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in) + * i_rContext.m_nMaxImageResolution; + + // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance) + if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) || + ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) && + ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) ) + { + // do scaling + Size aNewBmpSize; + const double fBmpWH = fBmpPixelX / fBmpPixelY; + const double fMaxWH = fMaxPixelX / fMaxPixelY; + + if( fBmpWH < fMaxWH ) + { + aNewBmpSize.setWidth( FRound( fMaxPixelY * fBmpWH ) ); + aNewBmpSize.setHeight( FRound( fMaxPixelY ) ); + } + else if( fBmpWH > 0.0 ) + { + aNewBmpSize.setWidth( FRound( fMaxPixelX ) ); + aNewBmpSize.setHeight( FRound( fMaxPixelX / fBmpWH) ); + } + + if( aNewBmpSize.Width() && aNewBmpSize.Height() ) + { + // #i121233# Use best quality for PDF exports + aBitmapEx.Scale( aNewBmpSize, BmpScaleFlag::BestQuality ); + } + else + { + aBitmapEx.SetEmpty(); + } + } + } + + const Size aSizePixel( aBitmapEx.GetSizePixel() ); + if ( !(aSizePixel.Width() && aSizePixel.Height()) ) + return; + + if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) + aBitmapEx.Convert(BmpConversion::N8BitGreys); + bool bUseJPGCompression = !i_rContext.m_bOnlyLosslessCompression; + if ( bIsPng || ( aSizePixel.Width() < 32 ) || ( aSizePixel.Height() < 32 ) ) + bUseJPGCompression = false; + + auto pStrm=std::make_shared<SvMemoryStream>(); + AlphaMask aAlphaMask; + + bool bTrueColorJPG = true; + if ( bUseJPGCompression ) + { + // TODO this checks could be done much earlier, saving us + // from trying conversion & stores before... + if ( !aBitmapEx.IsAlpha() ) + { + const auto& rCacheEntry=m_aPDFBmpCache.find( + aBitmapEx.GetChecksum()); + if ( rCacheEntry != m_aPDFBmpCache.end() ) + { + m_rOuterFace.DrawJPGBitmap( *rCacheEntry->second, true, aSizePixel, + tools::Rectangle( aPoint, aSize ), aAlphaMask, i_Graphic ); + return; + } + } + sal_uInt32 nZippedFileSize = 0; // sj: we will calculate the filesize of a zipped bitmap + if ( !bIsJpeg ) // to determine if jpeg compression is useful + { + SvMemoryStream aTemp; + aTemp.SetCompressMode( aTemp.GetCompressMode() | SvStreamCompressFlags::ZBITMAP ); + aTemp.SetVersion( SOFFICE_FILEFORMAT_40 ); // sj: up from version 40 our bitmap stream operator + WriteDIBBitmapEx(aBitmapEx, aTemp); // is capable of zlib stream compression + nZippedFileSize = aTemp.TellEnd(); + } + if ( aBitmapEx.IsAlpha() ) + aAlphaMask = aBitmapEx.GetAlphaMask(); + Graphic aGraphic(BitmapEx(aBitmapEx.GetBitmap())); + + Sequence< PropertyValue > aFilterData{ + comphelper::makePropertyValue("Quality", sal_Int32(i_rContext.m_nJPEGQuality)), + comphelper::makePropertyValue("ColorMode", sal_Int32(0)) + }; + + try + { + uno::Reference < io::XStream > xStream = new utl::OStreamWrapper( *pStrm ); + uno::Reference< io::XSeekable > xSeekable( xStream, UNO_QUERY_THROW ); + uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() ); + uno::Reference< graphic::XGraphicProvider > xGraphicProvider( graphic::GraphicProvider::create(xContext) ); + uno::Reference< graphic::XGraphic > xGraphic( aGraphic.GetXGraphic() ); + uno::Reference < io::XOutputStream > xOut( xStream->getOutputStream() ); + uno::Sequence< beans::PropertyValue > aOutMediaProperties{ + comphelper::makePropertyValue("OutputStream", xOut), + comphelper::makePropertyValue("MimeType", OUString("image/jpeg")), + comphelper::makePropertyValue("FilterData", aFilterData) + }; + xGraphicProvider->storeGraphic( xGraphic, aOutMediaProperties ); + xOut->flush(); + if ( !bIsJpeg && xSeekable->getLength() > nZippedFileSize ) + { + bUseJPGCompression = false; + } + else + { + pStrm->Seek( STREAM_SEEK_TO_END ); + + xSeekable->seek( 0 ); + Sequence< PropertyValue > aArgs{ comphelper::makePropertyValue("InputStream", + xStream) }; + uno::Reference< XPropertySet > xPropSet( xGraphicProvider->queryGraphicDescriptor( aArgs ) ); + if ( xPropSet.is() ) + { + sal_Int16 nBitsPerPixel = 24; + if ( xPropSet->getPropertyValue("BitsPerPixel") >>= nBitsPerPixel ) + { + bTrueColorJPG = nBitsPerPixel != 8; + } + } + } + } + catch( uno::Exception& ) + { + bUseJPGCompression = false; + } + } + if ( bUseJPGCompression ) + { + m_rOuterFace.DrawJPGBitmap( *pStrm, bTrueColorJPG, aSizePixel, tools::Rectangle( aPoint, aSize ), aAlphaMask, i_Graphic ); + if (!aBitmapEx.IsAlpha() && bTrueColorJPG) + { + // Cache last jpeg export + m_aPDFBmpCache.insert( + {aBitmapEx.GetChecksum(), pStrm}); + } + } + else if ( aBitmapEx.IsAlpha() ) + m_rOuterFace.DrawBitmapEx( aPoint, aSize, aBitmapEx ); + else + m_rOuterFace.DrawBitmap( aPoint, aSize, aBitmapEx.GetBitmap(), i_Graphic ); + +} + +void PDFWriterImpl::playMetafile( const GDIMetaFile& i_rMtf, vcl::PDFExtOutDevData* i_pOutDevData, const vcl::PDFWriter::PlayMetafileContext& i_rContext, VirtualDevice* pDummyVDev ) +{ + bool bAssertionFired( false ); + + ScopedVclPtr<VirtualDevice> xPrivateDevice; + if( ! pDummyVDev ) + { + xPrivateDevice.disposeAndReset(VclPtr<VirtualDevice>::Create()); + pDummyVDev = xPrivateDevice.get(); + pDummyVDev->EnableOutput( false ); + pDummyVDev->SetMapMode( i_rMtf.GetPrefMapMode() ); + } + const GDIMetaFile& aMtf( i_rMtf ); + + for( sal_uInt32 i = 0, nCount = aMtf.GetActionSize(); i < nCount; ) + { + if ( !i_pOutDevData || !i_pOutDevData->PlaySyncPageAct( m_rOuterFace, i, aMtf ) ) + { + const MetaAction* pAction = aMtf.GetAction( i ); + const MetaActionType nType = pAction->GetType(); + + switch( nType ) + { + case MetaActionType::PIXEL: + { + const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction); + m_rOuterFace.DrawPixel( pA->GetPoint(), pA->GetColor() ); + } + break; + + case MetaActionType::POINT: + { + const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction); + m_rOuterFace.DrawPixel( pA->GetPoint() ); + } + break; + + case MetaActionType::LINE: + { + const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction); + if ( pA->GetLineInfo().IsDefault() ) + m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint() ); + else + m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint(), pA->GetLineInfo() ); + } + break; + + case MetaActionType::RECT: + { + const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction); + m_rOuterFace.DrawRect( pA->GetRect() ); + } + break; + + case MetaActionType::ROUNDRECT: + { + const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction); + m_rOuterFace.DrawRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() ); + } + break; + + case MetaActionType::ELLIPSE: + { + const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction); + m_rOuterFace.DrawEllipse( pA->GetRect() ); + } + break; + + case MetaActionType::ARC: + { + const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction); + m_rOuterFace.DrawArc( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() ); + } + break; + + case MetaActionType::PIE: + { + const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction); + m_rOuterFace.DrawPie( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() ); + } + break; + + case MetaActionType::CHORD: + { + const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction); + m_rOuterFace.DrawChord( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() ); + } + break; + + case MetaActionType::POLYGON: + { + const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pAction); + m_rOuterFace.DrawPolygon( pA->GetPolygon() ); + } + break; + + case MetaActionType::POLYLINE: + { + const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction); + if ( pA->GetLineInfo().IsDefault() ) + m_rOuterFace.DrawPolyLine( pA->GetPolygon() ); + else + m_rOuterFace.DrawPolyLine( pA->GetPolygon(), pA->GetLineInfo() ); + } + break; + + case MetaActionType::POLYPOLYGON: + { + const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction); + m_rOuterFace.DrawPolyPolygon( pA->GetPolyPolygon() ); + } + break; + + case MetaActionType::GRADIENT: + { + const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction); + const Gradient& rGradient = pA->GetGradient(); + if (lcl_canUsePDFAxialShading(rGradient)) + { + m_rOuterFace.DrawGradient( pA->GetRect(), rGradient ); + } + else + { + const tools::PolyPolygon aPolyPoly( pA->GetRect() ); + implWriteGradient( aPolyPoly, rGradient, pDummyVDev, i_rContext ); + } + } + break; + + case MetaActionType::GRADIENTEX: + { + const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction); + const Gradient& rGradient = pA->GetGradient(); + + if (lcl_canUsePDFAxialShading(rGradient)) + m_rOuterFace.DrawGradient( pA->GetPolyPolygon(), rGradient ); + else + implWriteGradient( pA->GetPolyPolygon(), rGradient, pDummyVDev, i_rContext ); + } + break; + + case MetaActionType::HATCH: + { + const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction); + m_rOuterFace.DrawHatch( pA->GetPolyPolygon(), pA->GetHatch() ); + } + break; + + case MetaActionType::Transparent: + { + const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction); + m_rOuterFace.DrawTransparent( pA->GetPolyPolygon(), pA->GetTransparence() ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction); + + GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); + const Point& rPos = pA->GetPoint(); + const Size& rSize= pA->GetSize(); + const Gradient& rTransparenceGradient = pA->GetGradient(); + + // special case constant alpha value + if( rTransparenceGradient.GetStartColor() == rTransparenceGradient.GetEndColor() ) + { + const Color aTransCol( rTransparenceGradient.GetStartColor() ); + const sal_uInt16 nTransPercent = aTransCol.GetLuminance() * 100 / 255; + m_rOuterFace.BeginTransparencyGroup(); + + // tdf#138826 adjust the aTmpMtf to start at rPos (see also #i112076#) + Point aMtfOrigin(aTmpMtf.GetPrefMapMode().GetOrigin()); + if (rPos != aMtfOrigin) + aTmpMtf.Move(rPos.X() - aMtfOrigin.X(), rPos.Y() - aMtfOrigin.Y()); + + playMetafile( aTmpMtf, nullptr, i_rContext, pDummyVDev ); + m_rOuterFace.EndTransparencyGroup( tools::Rectangle( rPos, rSize ), nTransPercent ); + } + else + { + const Size aDstSizeTwip( pDummyVDev->PixelToLogic(pDummyVDev->LogicToPixel(rSize), MapMode(MapUnit::MapTwip)) ); + + // i#115962# Always use at least 300 DPI for bitmap conversion of transparence gradients, + // else the quality is not acceptable (see bugdoc as example) + sal_Int32 nMaxBmpDPI(300); + + if( i_rContext.m_nMaxImageResolution > 50 ) + { + if ( nMaxBmpDPI > i_rContext.m_nMaxImageResolution ) + nMaxBmpDPI = i_rContext.m_nMaxImageResolution; + } + const sal_Int32 nPixelX = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in) * nMaxBmpDPI; + const sal_Int32 nPixelY = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in) * nMaxBmpDPI; + if ( nPixelX && nPixelY ) + { + Size aDstSizePixel( nPixelX, nPixelY ); + ScopedVclPtrInstance<VirtualDevice> xVDev(DeviceFormat::WITH_ALPHA); + if( xVDev->SetOutputSizePixel( aDstSizePixel, true, true ) ) + { + Point aPoint; + + MapMode aMapMode( pDummyVDev->GetMapMode() ); + aMapMode.SetOrigin( aPoint ); + xVDev->SetMapMode( aMapMode ); + const bool bVDevOldMap = xVDev->IsMapModeEnabled(); + Size aDstSize( xVDev->PixelToLogic( aDstSizePixel ) ); + + Point aMtfOrigin( aTmpMtf.GetPrefMapMode().GetOrigin() ); + if ( aMtfOrigin.X() || aMtfOrigin.Y() ) + aTmpMtf.Move( -aMtfOrigin.X(), -aMtfOrigin.Y() ); + double fScaleX = static_cast<double>(aDstSize.Width()) / static_cast<double>(aTmpMtf.GetPrefSize().Width()); + double fScaleY = static_cast<double>(aDstSize.Height()) / static_cast<double>(aTmpMtf.GetPrefSize().Height()); + if( fScaleX != 1.0 || fScaleY != 1.0 ) + aTmpMtf.Scale( fScaleX, fScaleY ); + aTmpMtf.SetPrefMapMode( aMapMode ); + + // create paint bitmap + aTmpMtf.WindStart(); + aTmpMtf.Play(*xVDev, aPoint, aDstSize); + aTmpMtf.WindStart(); + xVDev->EnableMapMode( false ); + BitmapEx aPaint = xVDev->GetBitmapEx(aPoint, xVDev->GetOutputSizePixel()); + xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here! + + // create alpha mask from gradient + xVDev->SetDrawMode( DrawModeFlags::GrayGradient ); + xVDev->DrawGradient( tools::Rectangle( aPoint, aDstSize ), rTransparenceGradient ); + xVDev->SetDrawMode( DrawModeFlags::Default ); + xVDev->EnableMapMode( false ); + + AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel())); + AlphaMask aPaintAlpha(aPaint.GetAlphaMask()); + // The alpha mask is inverted from what is + // expected so invert it again. To test this + // code, export to PDF the transparent shapes, + // gradients, and images in the documents + // attached to the following bug reports: + // https://bugs.documentfoundation.org/show_bug.cgi?id=155912 + // https://bugs.documentfoundation.org/show_bug.cgi?id=156630 + aAlpha.Invert(); // convert to alpha + aAlpha.BlendWith(aPaintAlpha); +#if HAVE_FEATURE_SKIA +#if OSL_DEBUG_LEVEL > 0 + // In release builds, we always invert + // regardless of whether Skia is enabled or not. + // But in debug builds, we can't invert when + // Skia is enabled. + if ( !SkiaHelper::isVCLSkiaEnabled() ) +#endif +#endif + { + // When Skia is disabled, the alpha mask + // must be inverted a second time. To test + // this code, export the following + // document to PDF: + // https://bugs.documentfoundation.org/attachment.cgi?id=188084 + aAlpha.Invert(); // convert to alpha + } + + xVDev.disposeAndClear(); + + Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic(); + implWriteBitmapEx( rPos, rSize, BitmapEx( aPaint.GetBitmap(), aAlpha ), aGraphic, pDummyVDev, i_rContext ); + } + } + } + } + break; + + case MetaActionType::EPS: + { + const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction); + const GDIMetaFile& aSubstitute( pA->GetSubstitute() ); + + m_rOuterFace.Push(); + pDummyVDev->Push(); + + MapMode aMapMode( aSubstitute.GetPrefMapMode() ); + Size aOutSize( OutputDevice::LogicToLogic( pA->GetSize(), pDummyVDev->GetMapMode(), aMapMode ) ); + aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) ); + aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) ); + aMapMode.SetOrigin( OutputDevice::LogicToLogic( pA->GetPoint(), pDummyVDev->GetMapMode(), aMapMode ) ); + + m_rOuterFace.SetMapMode( aMapMode ); + pDummyVDev->SetMapMode( aMapMode ); + playMetafile( aSubstitute, nullptr, i_rContext, pDummyVDev ); + pDummyVDev->Pop(); + m_rOuterFace.Pop(); + } + break; + + case MetaActionType::COMMENT: + if( ! i_rContext.m_bTransparenciesWereRemoved ) + { + const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction); + + if( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN")) + { + const MetaGradientExAction* pGradAction = nullptr; + bool bDone = false; + + while( !bDone && ( ++i < nCount ) ) + { + pAction = aMtf.GetAction( i ); + + if( pAction->GetType() == MetaActionType::GRADIENTEX ) + pGradAction = static_cast<const MetaGradientExAction*>(pAction); + else if( ( pAction->GetType() == MetaActionType::COMMENT ) && + ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END")) ) + { + bDone = true; + } + } + + if( pGradAction ) + { + if (lcl_canUsePDFAxialShading(pGradAction->GetGradient())) + { + m_rOuterFace.DrawGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient() ); + } + else + { + implWriteGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), pDummyVDev, i_rContext ); + } + } + } + else + { + const sal_uInt8* pData = pA->GetData(); + if ( pData ) + { + SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pA->GetDataSize(), StreamMode::READ ); + bool bSkipSequence = false; + OString sSeqEnd; + + if( pA->GetComment() == "XPATHSTROKE_SEQ_BEGIN" ) + { + sSeqEnd = "XPATHSTROKE_SEQ_END"_ostr; + SvtGraphicStroke aStroke; + ReadSvtGraphicStroke( aMemStm, aStroke ); + + tools::Polygon aPath; + aStroke.getPath( aPath ); + + tools::PolyPolygon aStartArrow; + tools::PolyPolygon aEndArrow; + double fTransparency( aStroke.getTransparency() ); + double fStrokeWidth( aStroke.getStrokeWidth() ); + SvtGraphicStroke::DashArray aDashArray; + + aStroke.getStartArrow( aStartArrow ); + aStroke.getEndArrow( aEndArrow ); + aStroke.getDashArray( aDashArray ); + + bSkipSequence = true; + if ( aStartArrow.Count() || aEndArrow.Count() ) + bSkipSequence = false; + if ( !aDashArray.empty() && ( fStrokeWidth != 0.0 ) && ( fTransparency == 0.0 ) ) + bSkipSequence = false; + if ( bSkipSequence ) + { + PDFWriter::ExtLineInfo aInfo; + aInfo.m_fLineWidth = fStrokeWidth; + aInfo.m_fTransparency = fTransparency; + aInfo.m_fMiterLimit = aStroke.getMiterLimit(); + switch( aStroke.getCapType() ) + { + default: + case SvtGraphicStroke::capButt: aInfo.m_eCap = PDFWriter::capButt;break; + case SvtGraphicStroke::capRound: aInfo.m_eCap = PDFWriter::capRound;break; + case SvtGraphicStroke::capSquare: aInfo.m_eCap = PDFWriter::capSquare;break; + } + switch( aStroke.getJoinType() ) + { + default: + case SvtGraphicStroke::joinMiter: aInfo.m_eJoin = PDFWriter::joinMiter;break; + case SvtGraphicStroke::joinRound: aInfo.m_eJoin = PDFWriter::joinRound;break; + case SvtGraphicStroke::joinBevel: aInfo.m_eJoin = PDFWriter::joinBevel;break; + case SvtGraphicStroke::joinNone: + aInfo.m_eJoin = PDFWriter::joinMiter; + aInfo.m_fMiterLimit = 0.0; + break; + } + aInfo.m_aDashArray = aDashArray; + + if(SvtGraphicStroke::joinNone == aStroke.getJoinType() + && fStrokeWidth > 0.0) + { + // emulate no edge rounding by handling single edges + const sal_uInt16 nPoints(aPath.GetSize()); + const bool bCurve(aPath.HasFlags()); + + for(sal_uInt16 a(0); a + 1 < nPoints; a++) + { + if(bCurve + && PolyFlags::Normal != aPath.GetFlags(a + 1) + && a + 2 < nPoints + && PolyFlags::Normal != aPath.GetFlags(a + 2) + && a + 3 < nPoints) + { + const tools::Polygon aSnippet(4, + aPath.GetConstPointAry() + a, + aPath.GetConstFlagAry() + a); + m_rOuterFace.DrawPolyLine( aSnippet, aInfo ); + a += 2; + } + else + { + const tools::Polygon aSnippet(2, + aPath.GetConstPointAry() + a); + m_rOuterFace.DrawPolyLine( aSnippet, aInfo ); + } + } + } + else + { + m_rOuterFace.DrawPolyLine( aPath, aInfo ); + } + } + } + else if ( pA->GetComment() == "XPATHFILL_SEQ_BEGIN" ) + { + sSeqEnd = "XPATHFILL_SEQ_END"_ostr; + SvtGraphicFill aFill; + ReadSvtGraphicFill( aMemStm, aFill ); + + if ( ( aFill.getFillType() == SvtGraphicFill::fillSolid ) && ( aFill.getFillRule() == SvtGraphicFill::fillEvenOdd ) ) + { + double fTransparency = aFill.getTransparency(); + if ( fTransparency == 0.0 ) + { + tools::PolyPolygon aPath; + aFill.getPath( aPath ); + + bSkipSequence = true; + m_rOuterFace.DrawPolyPolygon( aPath ); + } + else if ( fTransparency == 1.0 ) + bSkipSequence = true; + } + } + if ( bSkipSequence ) + { + while( ++i < nCount ) + { + pAction = aMtf.GetAction( i ); + if ( pAction->GetType() == MetaActionType::COMMENT ) + { + OString sComment( static_cast<const MetaCommentAction*>(pAction)->GetComment() ); + if (sComment == sSeqEnd) + break; + } + // #i44496# + // the replacement action for stroke is a filled rectangle + // the set fillcolor of the replacement is part of the graphics + // state and must not be skipped + else if( pAction->GetType() == MetaActionType::FILLCOLOR ) + { + const MetaFillColorAction* pMA = static_cast<const MetaFillColorAction*>(pAction); + if( pMA->IsSetting() ) + m_rOuterFace.SetFillColor( pMA->GetColor() ); + else + m_rOuterFace.SetFillColor(); + } + } + } + } + } + } + break; + + case MetaActionType::BMP: + { + const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction); + BitmapEx aBitmapEx( pA->GetBitmap() ); + Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(), + aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) ); + if( ! ( aSize.Width() && aSize.Height() ) ) + aSize = pDummyVDev->PixelToLogic( aBitmapEx.GetSizePixel() ); + + Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic(); + implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, aGraphic, pDummyVDev, i_rContext ); + } + break; + + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic(); + implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), BitmapEx( pA->GetBitmap() ), aGraphic, pDummyVDev, i_rContext ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction); + BitmapEx aBitmapEx( pA->GetBitmap() ); + aBitmapEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ); + Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic(); + implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext ); + } + break; + + case MetaActionType::BMPEX: + { + const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction); + + // The alpha mask is inverted from what is + // expected so invert it again. To test this + // code, export to PDF the transparent shapes, + // gradients, and images in the documents + // attached to the following bug reports: + // https://bugs.documentfoundation.org/show_bug.cgi?id=155912 + // https://bugs.documentfoundation.org/show_bug.cgi?id=156630 + BitmapEx aBitmapEx( pA->GetBitmapEx() ); + if ( aBitmapEx.IsAlpha()) + { + AlphaMask aAlpha = aBitmapEx.GetAlphaMask(); + aAlpha.Invert(); + aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha); + } + + Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(), + aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) ); + Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic(); + implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, aGraphic, pDummyVDev, i_rContext ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + + // The alpha mask is inverted from what is + // expected so invert it again. To test this + // code, export to PDF the transparent shapes, + // gradients, and images in the documents + // attached to the following bug reports: + // https://bugs.documentfoundation.org/show_bug.cgi?id=155912 + // https://bugs.documentfoundation.org/show_bug.cgi?id=156630 + BitmapEx aBitmapEx( pA->GetBitmapEx() ); + if ( aBitmapEx.IsAlpha()) + { + AlphaMask aAlpha = aBitmapEx.GetAlphaMask(); + aAlpha.Invert(); + aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha); + } + + Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic(); + implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction); + + // The alpha mask is inverted from what is + // expected so invert it again. To test this + // code, export to PDF the transparent shapes, + // gradients, and images in the documents + // attached to the following bug reports: + // https://bugs.documentfoundation.org/show_bug.cgi?id=155912 + // https://bugs.documentfoundation.org/show_bug.cgi?id=156630 + BitmapEx aBitmapEx( pA->GetBitmapEx() ); + if ( aBitmapEx.IsAlpha()) + { + AlphaMask aAlpha = aBitmapEx.GetAlphaMask(); + aAlpha.Invert(); + aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aAlpha); + } + + aBitmapEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ); + Graphic aGraphic = i_pOutDevData ? i_pOutDevData->GetCurrentGraphic() : Graphic(); + implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, aGraphic, pDummyVDev, i_rContext ); + } + break; + + case MetaActionType::MASK: + case MetaActionType::MASKSCALE: + case MetaActionType::MASKSCALEPART: + { + SAL_WARN( "vcl", "MetaMask...Action not supported yet" ); + } + break; + + case MetaActionType::TEXT: + { + const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction); + m_rOuterFace.DrawText( pA->GetPoint(), pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ) ); + } + break; + + case MetaActionType::TEXTRECT: + { + const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction); + m_rOuterFace.DrawText( pA->GetRect(), pA->GetText(), pA->GetStyle() ); + } + break; + + case MetaActionType::TEXTARRAY: + { + const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction); + m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetKashidaArray(), pA->GetIndex(), pA->GetLen() ); + } + break; + + case MetaActionType::STRETCHTEXT: + { + const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction); + m_rOuterFace.DrawStretchText( pA->GetPoint(), pA->GetWidth(), pA->GetText(), pA->GetIndex(), pA->GetLen() ); + } + break; + + case MetaActionType::TEXTLINE: + { + const MetaTextLineAction* pA = static_cast<const MetaTextLineAction*>(pAction); + m_rOuterFace.DrawTextLine( pA->GetStartPoint(), pA->GetWidth(), pA->GetStrikeout(), pA->GetUnderline(), pA->GetOverline() ); + + } + break; + + case MetaActionType::CLIPREGION: + { + const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pAction); + + if( pA->IsClipping() ) + { + if( pA->GetRegion().IsEmpty() ) + m_rOuterFace.SetClipRegion( basegfx::B2DPolyPolygon() ); + else + { + const vcl::Region& aReg( pA->GetRegion() ); + m_rOuterFace.SetClipRegion( aReg.GetAsB2DPolyPolygon() ); + } + } + else + m_rOuterFace.SetClipRegion(); + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction); + m_rOuterFace.IntersectClipRegion( pA->GetRect() ); + } + break; + + case MetaActionType::ISECTREGIONCLIPREGION: + { + const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pAction); + const vcl::Region& aReg( pA->GetRegion() ); + m_rOuterFace.IntersectClipRegion( aReg.GetAsB2DPolyPolygon() ); + } + break; + + case MetaActionType::MOVECLIPREGION: + { + const MetaMoveClipRegionAction* pA = static_cast<const MetaMoveClipRegionAction*>(pAction); + m_rOuterFace.MoveClipRegion( pA->GetHorzMove(), pA->GetVertMove() ); + } + break; + + case MetaActionType::MAPMODE: + { + const_cast< MetaAction* >( pAction )->Execute( pDummyVDev ); + m_rOuterFace.SetMapMode( pDummyVDev->GetMapMode() ); + } + break; + + case MetaActionType::LINECOLOR: + { + const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pAction); + + if( pA->IsSetting() ) + m_rOuterFace.SetLineColor( pA->GetColor() ); + else + m_rOuterFace.SetLineColor(); + } + break; + + case MetaActionType::FILLCOLOR: + { + const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pAction); + + if( pA->IsSetting() ) + m_rOuterFace.SetFillColor( pA->GetColor() ); + else + m_rOuterFace.SetFillColor(); + } + break; + + case MetaActionType::TEXTLINECOLOR: + { + const MetaTextLineColorAction* pA = static_cast<const MetaTextLineColorAction*>(pAction); + + if( pA->IsSetting() ) + m_rOuterFace.SetTextLineColor( pA->GetColor() ); + else + m_rOuterFace.SetTextLineColor(); + } + break; + + case MetaActionType::OVERLINECOLOR: + { + const MetaOverlineColorAction* pA = static_cast<const MetaOverlineColorAction*>(pAction); + + if( pA->IsSetting() ) + m_rOuterFace.SetOverlineColor( pA->GetColor() ); + else + m_rOuterFace.SetOverlineColor(); + } + break; + + case MetaActionType::TEXTFILLCOLOR: + { + const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pAction); + + if( pA->IsSetting() ) + m_rOuterFace.SetTextFillColor( pA->GetColor() ); + else + m_rOuterFace.SetTextFillColor(); + } + break; + + case MetaActionType::TEXTCOLOR: + { + const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pAction); + m_rOuterFace.SetTextColor( pA->GetColor() ); + } + break; + + case MetaActionType::TEXTALIGN: + { + const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pAction); + m_rOuterFace.SetTextAlign( pA->GetTextAlign() ); + } + break; + + case MetaActionType::FONT: + { + const MetaFontAction* pA = static_cast<const MetaFontAction*>(pAction); + m_rOuterFace.SetFont( pA->GetFont() ); + } + break; + + case MetaActionType::PUSH: + { + const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction); + + pDummyVDev->Push( pA->GetFlags() ); + m_rOuterFace.Push( pA->GetFlags() ); + } + break; + + case MetaActionType::POP: + { + pDummyVDev->Pop(); + m_rOuterFace.Pop(); + } + break; + + case MetaActionType::LAYOUTMODE: + { + const MetaLayoutModeAction* pA = static_cast<const MetaLayoutModeAction*>(pAction); + m_rOuterFace.SetLayoutMode( pA->GetLayoutMode() ); + } + break; + + case MetaActionType::TEXTLANGUAGE: + { + const MetaTextLanguageAction* pA = static_cast<const MetaTextLanguageAction*>(pAction); + m_rOuterFace.SetDigitLanguage( pA->GetTextLanguage() ); + } + break; + + case MetaActionType::WALLPAPER: + { + const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pAction); + m_rOuterFace.DrawWallpaper( pA->GetRect(), pA->GetWallpaper() ); + } + break; + + case MetaActionType::RASTEROP: + { + // !!! >>> we don't want to support this actions + } + break; + + case MetaActionType::REFPOINT: + { + // !!! >>> we don't want to support this actions + } + break; + + default: + // #i24604# Made assertion fire only once per + // metafile. The asserted actions here are all + // deprecated + if( !bAssertionFired ) + { + bAssertionFired = true; + SAL_WARN( "vcl", "PDFExport::ImplWriteActions: deprecated and unsupported MetaAction encountered " << static_cast<int>(nType) ); + } + break; + } + i++; + } + } +} + +// Encryption methods + +/* a crutch to transport a ::comphelper::Hash safely though UNO API + this is needed for the PDF export dialog, which otherwise would have to pass + clear text passwords down till they can be used in PDFWriter. Unfortunately + the MD5 sum of the password (which is needed to create the PDF encryption key) + is not sufficient, since an MD5 digest cannot be created in an arbitrary state + which would be needed in PDFWriterImpl::computeEncryptionKey. +*/ +class EncHashTransporter : public cppu::WeakImplHelper < css::beans::XMaterialHolder > +{ + ::std::unique_ptr<::comphelper::Hash> m_pDigest; + sal_IntPtr maID; + std::vector< sal_uInt8 > maOValue; + + static std::map< sal_IntPtr, EncHashTransporter* > sTransporters; +public: + EncHashTransporter() + : m_pDigest(new ::comphelper::Hash(::comphelper::HashType::MD5)) + { + maID = reinterpret_cast< sal_IntPtr >(this); + while( sTransporters.find( maID ) != sTransporters.end() ) // paranoia mode + maID++; + sTransporters[ maID ] = this; + } + + virtual ~EncHashTransporter() override + { + sTransporters.erase( maID ); + SAL_INFO( "vcl", "EncHashTransporter freed" ); + } + + ::comphelper::Hash* getUDigest() { return m_pDigest.get(); }; + std::vector< sal_uInt8 >& getOValue() { return maOValue; } + void invalidate() + { + m_pDigest.reset(); + } + + // XMaterialHolder + virtual uno::Any SAL_CALL getMaterial() override + { + return uno::Any( sal_Int64(maID) ); + } + + static EncHashTransporter* getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& ); + +}; + +std::map< sal_IntPtr, EncHashTransporter* > EncHashTransporter::sTransporters; + +EncHashTransporter* EncHashTransporter::getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& xRef ) +{ + EncHashTransporter* pResult = nullptr; + if( xRef.is() ) + { + uno::Any aMat( xRef->getMaterial() ); + sal_Int64 nMat = 0; + if( aMat >>= nMat ) + { + std::map< sal_IntPtr, EncHashTransporter* >::iterator it = sTransporters.find( static_cast<sal_IntPtr>(nMat) ); + if( it != sTransporters.end() ) + pResult = it->second; + } + } + return pResult; +} + +void PDFWriterImpl::checkAndEnableStreamEncryption( sal_Int32 nObject ) +{ + if( !m_aContext.Encryption.Encrypt() ) + return; + + m_bEncryptThisStream = true; + sal_Int32 i = m_nKeyLength; + m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>(nObject); + m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 8 ); + m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 16 ); + // the other location of m_nEncryptionKey is already set to 0, our fixed generation number + // do the MD5 hash + ::std::vector<unsigned char> const nMD5Sum(::comphelper::Hash::calculateHash( + m_aContext.Encryption.EncryptionKey.data(), i+2, ::comphelper::HashType::MD5)); + // the i+2 to take into account the generation number, always zero + // initialize the RC4 with the key + // key length: see algorithm 3.1, step 4: (N+5) max 16 + rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_nRC4KeyLength, nullptr, 0 ); +} + +void PDFWriterImpl::enableStringEncryption( sal_Int32 nObject ) +{ + if( !m_aContext.Encryption.Encrypt() ) + return; + + sal_Int32 i = m_nKeyLength; + m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>(nObject); + m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 8 ); + m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 16 ); + // the other location of m_nEncryptionKey is already set to 0, our fixed generation number + // do the MD5 hash + // the i+2 to take into account the generation number, always zero + ::std::vector<unsigned char> const nMD5Sum(::comphelper::Hash::calculateHash( + m_aContext.Encryption.EncryptionKey.data(), i+2, ::comphelper::HashType::MD5)); + // initialize the RC4 with the key + // key length: see algorithm 3.1, step 4: (N+5) max 16 + rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_nRC4KeyLength, nullptr, 0 ); +} + +/* init the encryption engine +1. init the document id, used both for building the document id and for building the encryption key(s) +2. build the encryption key following algorithms described in the PDF specification + */ +uno::Reference< beans::XMaterialHolder > PDFWriterImpl::initEncryption( const OUString& i_rOwnerPassword, + const OUString& i_rUserPassword + ) +{ + uno::Reference< beans::XMaterialHolder > xResult; + if( !i_rOwnerPassword.isEmpty() || !i_rUserPassword.isEmpty() ) + { + rtl::Reference<EncHashTransporter> pTransporter = new EncHashTransporter; + xResult = pTransporter; + + // get padded passwords + sal_uInt8 aPadUPW[ENCRYPTED_PWD_SIZE], aPadOPW[ENCRYPTED_PWD_SIZE]; + padPassword( i_rOwnerPassword.isEmpty() ? i_rUserPassword : i_rOwnerPassword, aPadOPW ); + padPassword( i_rUserPassword, aPadUPW ); + + if( computeODictionaryValue( aPadOPW, aPadUPW, pTransporter->getOValue(), SECUR_128BIT_KEY ) ) + { + pTransporter->getUDigest()->update(aPadUPW, ENCRYPTED_PWD_SIZE); + } + else + xResult.clear(); + + // trash temporary padded cleartext PWDs + rtl_secureZeroMemory (aPadOPW, sizeof(aPadOPW)); + rtl_secureZeroMemory (aPadUPW, sizeof(aPadUPW)); + } + return xResult; +} + +bool PDFWriterImpl::prepareEncryption( const uno::Reference< beans::XMaterialHolder >& xEnc ) +{ + bool bSuccess = false; + EncHashTransporter* pTransporter = EncHashTransporter::getEncHashTransporter( xEnc ); + if( pTransporter ) + { + sal_Int32 nKeyLength = 0, nRC4KeyLength = 0; + sal_Int32 nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, nKeyLength, nRC4KeyLength ); + m_aContext.Encryption.OValue = pTransporter->getOValue(); + bSuccess = computeUDictionaryValue( pTransporter, m_aContext.Encryption, nKeyLength, nAccessPermissions ); + } + if( ! bSuccess ) + { + m_aContext.Encryption.OValue.clear(); + m_aContext.Encryption.UValue.clear(); + m_aContext.Encryption.EncryptionKey.clear(); + } + return bSuccess; +} + +sal_Int32 PDFWriterImpl::computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties, + sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength ) +{ + /* + 2) compute the access permissions, in numerical form + + the default value depends on the revision 2 (40 bit) or 3 (128 bit security): + - for 40 bit security the unused bit must be set to 1, since they are not used + - for 128 bit security the same bit must be preset to 0 and set later if needed + according to the table 3.15, pdf v 1.4 */ + sal_Int32 nAccessPermissions = 0xfffff0c0; + + o_rKeyLength = SECUR_128BIT_KEY; + o_rRC4KeyLength = 16; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16, + // thus maximum permitted value is 16 + + nAccessPermissions |= ( i_rProperties.CanPrintTheDocument ) ? 1 << 2 : 0; + nAccessPermissions |= ( i_rProperties.CanModifyTheContent ) ? 1 << 3 : 0; + nAccessPermissions |= ( i_rProperties.CanCopyOrExtract ) ? 1 << 4 : 0; + nAccessPermissions |= ( i_rProperties.CanAddOrModify ) ? 1 << 5 : 0; + nAccessPermissions |= ( i_rProperties.CanFillInteractive ) ? 1 << 8 : 0; + nAccessPermissions |= ( i_rProperties.CanExtractForAccessibility ) ? 1 << 9 : 0; + nAccessPermissions |= ( i_rProperties.CanAssemble ) ? 1 << 10 : 0; + nAccessPermissions |= ( i_rProperties.CanPrintFull ) ? 1 << 11 : 0; + return nAccessPermissions; +} + +/************************************************************* +begin i12626 methods + +Implements Algorithm 3.2, step 1 only +*/ +void PDFWriterImpl::padPassword( std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW ) +{ + // get ansi-1252 version of the password string CHECKIT ! i12626 + OString aString( OUStringToOString( i_rPassword, RTL_TEXTENCODING_MS_1252 ) ); + + //copy the string to the target + sal_Int32 nToCopy = ( aString.getLength() < ENCRYPTED_PWD_SIZE ) ? aString.getLength() : ENCRYPTED_PWD_SIZE; + sal_Int32 nCurrentChar; + + for( nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++ ) + o_pPaddedPW[nCurrentChar] = static_cast<sal_uInt8>( aString[nCurrentChar] ); + + //pad it with standard byte string + sal_Int32 i,y; + for( i = nCurrentChar, y = 0 ; i < ENCRYPTED_PWD_SIZE; i++, y++ ) + o_pPaddedPW[i] = s_nPadString[y]; +} + +/********************************** +Algorithm 3.2 Compute the encryption key used + +step 1 should already be done before calling, the paThePaddedPassword parameter should contain +the padded password and must be 32 byte long, the encryption key is returned into the paEncryptionKey parameter, +it will be 16 byte long for 128 bit security; for 40 bit security only the first 5 bytes are used + +TODO: in pdf ver 1.5 and 1.6 the step 6 is different, should be implemented. See spec. + +*/ +bool PDFWriterImpl::computeEncryptionKey( EncHashTransporter* i_pTransporter, vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, sal_Int32 i_nAccessPermissions ) +{ + bool bSuccess = true; + ::std::vector<unsigned char> nMD5Sum; + + // transporter contains an MD5 digest with the padded user password already + ::comphelper::Hash *const pDigest = i_pTransporter->getUDigest(); + if (pDigest) + { + //step 3 + if( ! io_rProperties.OValue.empty() ) + pDigest->update(io_rProperties.OValue.data(), io_rProperties.OValue.size()); + else + bSuccess = false; + //Step 4 + sal_uInt8 nPerm[4]; + + nPerm[0] = static_cast<sal_uInt8>(i_nAccessPermissions); + nPerm[1] = static_cast<sal_uInt8>( i_nAccessPermissions >> 8 ); + nPerm[2] = static_cast<sal_uInt8>( i_nAccessPermissions >> 16 ); + nPerm[3] = static_cast<sal_uInt8>( i_nAccessPermissions >> 24 ); + + pDigest->update(nPerm, sizeof(nPerm)); + + //step 5, get the document ID, binary form + pDigest->update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size()); + //get the digest + nMD5Sum = pDigest->finalize(); + + //step 6, only if 128 bit + for (sal_Int32 i = 0; i < 50; i++) + { + nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), ::comphelper::HashType::MD5); + } + } + else + bSuccess = false; + + i_pTransporter->invalidate(); + + //Step 7 + if( bSuccess ) + { + io_rProperties.EncryptionKey.resize( MAXIMUM_RC4_KEY_LENGTH ); + for( sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++ ) + io_rProperties.EncryptionKey[i] = nMD5Sum[i]; + } + else + io_rProperties.EncryptionKey.clear(); + + return bSuccess; +} + +/********************************** +Algorithm 3.3 Compute the encryption dictionary /O value, save into the class data member +the step numbers down here correspond to the ones in PDF v.1.4 specification +*/ +bool PDFWriterImpl::computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword, + const sal_uInt8* i_pPaddedUserPassword, + std::vector< sal_uInt8 >& io_rOValue, + sal_Int32 i_nKeyLength + ) +{ + bool bSuccess = true; + + io_rOValue.resize( ENCRYPTED_PWD_SIZE ); + + rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); + if (aCipher) + { + //step 1 already done, data is in i_pPaddedOwnerPassword + //step 2 + + ::std::vector<unsigned char> nMD5Sum(::comphelper::Hash::calculateHash( + i_pPaddedOwnerPassword, ENCRYPTED_PWD_SIZE, ::comphelper::HashType::MD5)); + //step 3, only if 128 bit + if (i_nKeyLength == SECUR_128BIT_KEY) + { + sal_Int32 i; + for (i = 0; i < 50; i++) + { + nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), ::comphelper::HashType::MD5); + } + } + //Step 4, the key is in nMD5Sum + //step 5 already done, data is in i_pPaddedUserPassword + //step 6 + if (rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, + nMD5Sum.data(), i_nKeyLength , nullptr, 0 ) + == rtl_Cipher_E_None) + { + // encrypt the user password using the key set above + rtl_cipher_encodeARCFOUR( aCipher, i_pPaddedUserPassword, ENCRYPTED_PWD_SIZE, // the data to be encrypted + io_rOValue.data(), sal_Int32(io_rOValue.size()) ); //encrypted data + //Step 7, only if 128 bit + if( i_nKeyLength == SECUR_128BIT_KEY ) + { + sal_uInt32 i; + size_t y; + sal_uInt8 nLocalKey[ SECUR_128BIT_KEY ]; // 16 = 128 bit key + + for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1 + { + for( y = 0; y < sizeof( nLocalKey ); y++ ) + nLocalKey[y] = static_cast<sal_uInt8>( nMD5Sum[y] ^ i ); + + if (rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, + nLocalKey, SECUR_128BIT_KEY, nullptr, 0 ) //destination data area, on init can be NULL + != rtl_Cipher_E_None) + { + bSuccess = false; + break; + } + rtl_cipher_encodeARCFOUR( aCipher, io_rOValue.data(), sal_Int32(io_rOValue.size()), // the data to be encrypted + io_rOValue.data(), sal_Int32(io_rOValue.size()) ); // encrypted data, can be the same as the input, encrypt "in place" + //step 8, store in class data member + } + } + } + else + bSuccess = false; + } + else + bSuccess = false; + + if( aCipher ) + rtl_cipher_destroyARCFOUR( aCipher ); + + if( ! bSuccess ) + io_rOValue.clear(); + return bSuccess; +} + +/********************************** +Algorithms 3.4 and 3.5 Compute the encryption dictionary /U value, save into the class data member, revision 2 (40 bit) or 3 (128 bit) +*/ +bool PDFWriterImpl::computeUDictionaryValue( EncHashTransporter* i_pTransporter, + vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, + sal_Int32 i_nKeyLength, + sal_Int32 i_nAccessPermissions + ) +{ + bool bSuccess = true; + + io_rProperties.UValue.resize( ENCRYPTED_PWD_SIZE ); + + ::comphelper::Hash aDigest(::comphelper::HashType::MD5); + rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); + if (aCipher) + { + //step 1, common to both 3.4 and 3.5 + if( computeEncryptionKey( i_pTransporter, io_rProperties, i_nAccessPermissions ) ) + { + // prepare encryption key for object + for( sal_Int32 i = i_nKeyLength, y = 0; y < 5 ; y++ ) + io_rProperties.EncryptionKey[i++] = 0; + + //or 3.5, for 128 bit security + //step6, initialize the last 16 bytes of the encrypted user password to 0 + for(sal_uInt32 i = MD5_DIGEST_SIZE; i < sal_uInt32(io_rProperties.UValue.size()); i++) + io_rProperties.UValue[i] = 0; + //steps 2 and 3 + aDigest.update(s_nPadString, sizeof(s_nPadString)); + aDigest.update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size()); + + ::std::vector<unsigned char> const nMD5Sum(aDigest.finalize()); + //Step 4 + rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, + io_rProperties.EncryptionKey.data(), SECUR_128BIT_KEY, nullptr, 0 ); //destination data area + rtl_cipher_encodeARCFOUR( aCipher, nMD5Sum.data(), nMD5Sum.size(), // the data to be encrypted + io_rProperties.UValue.data(), SECUR_128BIT_KEY ); //encrypted data, stored in class data member + //step 5 + sal_uInt32 i; + size_t y; + sal_uInt8 nLocalKey[SECUR_128BIT_KEY]; + + for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1 + { + for( y = 0; y < sizeof( nLocalKey ) ; y++ ) + nLocalKey[y] = static_cast<sal_uInt8>( io_rProperties.EncryptionKey[y] ^ i ); + + rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, + nLocalKey, SECUR_128BIT_KEY, // key and key length + nullptr, 0 ); //destination data area, on init can be NULL + rtl_cipher_encodeARCFOUR( aCipher, io_rProperties.UValue.data(), SECUR_128BIT_KEY, // the data to be encrypted + io_rProperties.UValue.data(), SECUR_128BIT_KEY ); // encrypted data, can be the same as the input, encrypt "in place" + } + } + else + bSuccess = false; + } + else + bSuccess = false; + + if( aCipher ) + rtl_cipher_destroyARCFOUR( aCipher ); + + if( ! bSuccess ) + io_rProperties.UValue.clear(); + return bSuccess; +} + +/* end i12626 methods */ + +const tools::Long unsetRun[256] = +{ + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 - 0xff */ +}; + +const tools::Long setRun[256] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 - 0x8f */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 - 0x9f */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xa0 - 0xaf */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xb0 - 0xbf */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 - 0xcf */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xd0 - 0xdf */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 - 0xef */ + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, /* 0xf0 - 0xff */ +}; + +static bool isSet( const Scanline i_pLine, tools::Long i_nIndex ) +{ + return (i_pLine[ i_nIndex/8 ] & (0x80 >> (i_nIndex&7))) != 0; +} + +static tools::Long findBitRunImpl( const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW, bool i_bSet ) +{ + tools::Long nIndex = i_nStartIndex; + if( nIndex < i_nW ) + { + const sal_uInt8 * pByte = i_pLine + (nIndex/8); + sal_uInt8 nByte = *pByte; + + // run up to byte boundary + tools::Long nBitInByte = (nIndex & 7); + if( nBitInByte ) + { + sal_uInt8 nMask = 0x80 >> nBitInByte; + while( nBitInByte != 8 ) + { + if( (nByte & nMask) != (i_bSet ? nMask : 0) ) + return std::min(nIndex, i_nW); + nMask = nMask >> 1; + nBitInByte++; + nIndex++; + } + if( nIndex < i_nW ) + { + pByte++; + nByte = *pByte; + } + } + + sal_uInt8 nRunByte; + const tools::Long* pRunTable; + if( i_bSet ) + { + nRunByte = 0xff; + pRunTable = setRun; + } + else + { + nRunByte = 0; + pRunTable = unsetRun; + } + + if( nIndex < i_nW ) + { + while( nByte == nRunByte ) + { + nIndex += 8; + + if (nIndex >= i_nW) + break; + + pByte++; + nByte = *pByte; + } + } + + if( nIndex < i_nW ) + { + nIndex += pRunTable[nByte]; + } + } + return std::min(nIndex, i_nW); +} + +static tools::Long findBitRun(const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW, bool i_bSet) +{ + if (i_nStartIndex < 0) + return i_nW; + + return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, i_bSet); +} + +static tools::Long findBitRun(const Scanline i_pLine, tools::Long i_nStartIndex, tools::Long i_nW) +{ + if (i_nStartIndex < 0) + return i_nW; + + const bool bSet = i_nStartIndex < i_nW && isSet(i_pLine, i_nStartIndex); + + return findBitRunImpl(i_pLine, i_nStartIndex, i_nW, bSet); +} + +struct BitStreamState +{ + sal_uInt8 mnBuffer; + sal_uInt32 mnNextBitPos; + + BitStreamState() + : mnBuffer( 0 ) + , mnNextBitPos( 8 ) + { + } + + const sal_uInt8& getByte() const { return mnBuffer; } + void flush() { mnNextBitPos = 8; mnBuffer = 0; } +}; + +void PDFWriterImpl::putG4Bits( sal_uInt32 i_nLength, sal_uInt32 i_nCode, BitStreamState& io_rState ) +{ + while( i_nLength > io_rState.mnNextBitPos ) + { + io_rState.mnBuffer |= static_cast<sal_uInt8>( i_nCode >> (i_nLength - io_rState.mnNextBitPos) ); + i_nLength -= io_rState.mnNextBitPos; + writeBufferBytes( &io_rState.getByte(), 1 ); + io_rState.flush(); + } + assert(i_nLength < 9); + static const unsigned int msbmask[9] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; + io_rState.mnBuffer |= static_cast<sal_uInt8>( (i_nCode & msbmask[i_nLength]) << (io_rState.mnNextBitPos - i_nLength) ); + io_rState.mnNextBitPos -= i_nLength; + if( io_rState.mnNextBitPos == 0 ) + { + writeBufferBytes( &io_rState.getByte(), 1 ); + io_rState.flush(); + } +} + +namespace { + +struct PixelCode +{ + sal_uInt32 mnEncodedPixels; + sal_uInt32 mnCodeBits; + sal_uInt32 mnCode; +}; + +} + +const PixelCode WhitePixelCodes[] = +{ + { 0, 8, 0x35 }, // 0011 0101 + { 1, 6, 0x7 }, // 0001 11 + { 2, 4, 0x7 }, // 0111 + { 3, 4, 0x8 }, // 1000 + { 4, 4, 0xB }, // 1011 + { 5, 4, 0xC }, // 1100 + { 6, 4, 0xE }, // 1110 + { 7, 4, 0xF }, // 1111 + { 8, 5, 0x13 }, // 1001 1 + { 9, 5, 0x14 }, // 1010 0 + { 10, 5, 0x7 }, // 0011 1 + { 11, 5, 0x8 }, // 0100 0 + { 12, 6, 0x8 }, // 0010 00 + { 13, 6, 0x3 }, // 0000 11 + { 14, 6, 0x34 }, // 1101 00 + { 15, 6, 0x35 }, // 1101 01 + { 16, 6, 0x2A }, // 1010 10 + { 17, 6, 0x2B }, // 1010 11 + { 18, 7, 0x27 }, // 0100 111 + { 19, 7, 0xC }, // 0001 100 + { 20, 7, 0x8 }, // 0001 000 + { 21, 7, 0x17 }, // 0010 111 + { 22, 7, 0x3 }, // 0000 011 + { 23, 7, 0x4 }, // 0000 100 + { 24, 7, 0x28 }, // 0101 000 + { 25, 7, 0x2B }, // 0101 011 + { 26, 7, 0x13 }, // 0010 011 + { 27, 7, 0x24 }, // 0100 100 + { 28, 7, 0x18 }, // 0011 000 + { 29, 8, 0x2 }, // 0000 0010 + { 30, 8, 0x3 }, // 0000 0011 + { 31, 8, 0x1A }, // 0001 1010 + { 32, 8, 0x1B }, // 0001 1011 + { 33, 8, 0x12 }, // 0001 0010 + { 34, 8, 0x13 }, // 0001 0011 + { 35, 8, 0x14 }, // 0001 0100 + { 36, 8, 0x15 }, // 0001 0101 + { 37, 8, 0x16 }, // 0001 0110 + { 38, 8, 0x17 }, // 0001 0111 + { 39, 8, 0x28 }, // 0010 1000 + { 40, 8, 0x29 }, // 0010 1001 + { 41, 8, 0x2A }, // 0010 1010 + { 42, 8, 0x2B }, // 0010 1011 + { 43, 8, 0x2C }, // 0010 1100 + { 44, 8, 0x2D }, // 0010 1101 + { 45, 8, 0x4 }, // 0000 0100 + { 46, 8, 0x5 }, // 0000 0101 + { 47, 8, 0xA }, // 0000 1010 + { 48, 8, 0xB }, // 0000 1011 + { 49, 8, 0x52 }, // 0101 0010 + { 50, 8, 0x53 }, // 0101 0011 + { 51, 8, 0x54 }, // 0101 0100 + { 52, 8, 0x55 }, // 0101 0101 + { 53, 8, 0x24 }, // 0010 0100 + { 54, 8, 0x25 }, // 0010 0101 + { 55, 8, 0x58 }, // 0101 1000 + { 56, 8, 0x59 }, // 0101 1001 + { 57, 8, 0x5A }, // 0101 1010 + { 58, 8, 0x5B }, // 0101 1011 + { 59, 8, 0x4A }, // 0100 1010 + { 60, 8, 0x4B }, // 0100 1011 + { 61, 8, 0x32 }, // 0011 0010 + { 62, 8, 0x33 }, // 0011 0011 + { 63, 8, 0x34 }, // 0011 0100 + { 64, 5, 0x1B }, // 1101 1 + { 128, 5, 0x12 }, // 1001 0 + { 192, 6, 0x17 }, // 0101 11 + { 256, 7, 0x37 }, // 0110 111 + { 320, 8, 0x36 }, // 0011 0110 + { 384, 8, 0x37 }, // 0011 0111 + { 448, 8, 0x64 }, // 0110 0100 + { 512, 8, 0x65 }, // 0110 0101 + { 576, 8, 0x68 }, // 0110 1000 + { 640, 8, 0x67 }, // 0110 0111 + { 704, 9, 0xCC }, // 0110 0110 0 + { 768, 9, 0xCD }, // 0110 0110 1 + { 832, 9, 0xD2 }, // 0110 1001 0 + { 896, 9, 0xD3 }, // 0110 1001 1 + { 960, 9, 0xD4 }, // 0110 1010 0 + { 1024, 9, 0xD5 }, // 0110 1010 1 + { 1088, 9, 0xD6 }, // 0110 1011 0 + { 1152, 9, 0xD7 }, // 0110 1011 1 + { 1216, 9, 0xD8 }, // 0110 1100 0 + { 1280, 9, 0xD9 }, // 0110 1100 1 + { 1344, 9, 0xDA }, // 0110 1101 0 + { 1408, 9, 0xDB }, // 0110 1101 1 + { 1472, 9, 0x98 }, // 0100 1100 0 + { 1536, 9, 0x99 }, // 0100 1100 1 + { 1600, 9, 0x9A }, // 0100 1101 0 + { 1664, 6, 0x18 }, // 0110 00 + { 1728, 9, 0x9B }, // 0100 1101 1 + { 1792, 11, 0x8 }, // 0000 0001 000 + { 1856, 11, 0xC }, // 0000 0001 100 + { 1920, 11, 0xD }, // 0000 0001 101 + { 1984, 12, 0x12 }, // 0000 0001 0010 + { 2048, 12, 0x13 }, // 0000 0001 0011 + { 2112, 12, 0x14 }, // 0000 0001 0100 + { 2176, 12, 0x15 }, // 0000 0001 0101 + { 2240, 12, 0x16 }, // 0000 0001 0110 + { 2304, 12, 0x17 }, // 0000 0001 0111 + { 2368, 12, 0x1C }, // 0000 0001 1100 + { 2432, 12, 0x1D }, // 0000 0001 1101 + { 2496, 12, 0x1E }, // 0000 0001 1110 + { 2560, 12, 0x1F } // 0000 0001 1111 +}; + +const PixelCode BlackPixelCodes[] = +{ + { 0, 10, 0x37 }, // 0000 1101 11 + { 1, 3, 0x2 }, // 010 + { 2, 2, 0x3 }, // 11 + { 3, 2, 0x2 }, // 10 + { 4, 3, 0x3 }, // 011 + { 5, 4, 0x3 }, // 0011 + { 6, 4, 0x2 }, // 0010 + { 7, 5, 0x3 }, // 0001 1 + { 8, 6, 0x5 }, // 0001 01 + { 9, 6, 0x4 }, // 0001 00 + { 10, 7, 0x4 }, // 0000 100 + { 11, 7, 0x5 }, // 0000 101 + { 12, 7, 0x7 }, // 0000 111 + { 13, 8, 0x4 }, // 0000 0100 + { 14, 8, 0x7 }, // 0000 0111 + { 15, 9, 0x18 }, // 0000 1100 0 + { 16, 10, 0x17 }, // 0000 0101 11 + { 17, 10, 0x18 }, // 0000 0110 00 + { 18, 10, 0x8 }, // 0000 0010 00 + { 19, 11, 0x67 }, // 0000 1100 111 + { 20, 11, 0x68 }, // 0000 1101 000 + { 21, 11, 0x6C }, // 0000 1101 100 + { 22, 11, 0x37 }, // 0000 0110 111 + { 23, 11, 0x28 }, // 0000 0101 000 + { 24, 11, 0x17 }, // 0000 0010 111 + { 25, 11, 0x18 }, // 0000 0011 000 + { 26, 12, 0xCA }, // 0000 1100 1010 + { 27, 12, 0xCB }, // 0000 1100 1011 + { 28, 12, 0xCC }, // 0000 1100 1100 + { 29, 12, 0xCD }, // 0000 1100 1101 + { 30, 12, 0x68 }, // 0000 0110 1000 + { 31, 12, 0x69 }, // 0000 0110 1001 + { 32, 12, 0x6A }, // 0000 0110 1010 + { 33, 12, 0x6B }, // 0000 0110 1011 + { 34, 12, 0xD2 }, // 0000 1101 0010 + { 35, 12, 0xD3 }, // 0000 1101 0011 + { 36, 12, 0xD4 }, // 0000 1101 0100 + { 37, 12, 0xD5 }, // 0000 1101 0101 + { 38, 12, 0xD6 }, // 0000 1101 0110 + { 39, 12, 0xD7 }, // 0000 1101 0111 + { 40, 12, 0x6C }, // 0000 0110 1100 + { 41, 12, 0x6D }, // 0000 0110 1101 + { 42, 12, 0xDA }, // 0000 1101 1010 + { 43, 12, 0xDB }, // 0000 1101 1011 + { 44, 12, 0x54 }, // 0000 0101 0100 + { 45, 12, 0x55 }, // 0000 0101 0101 + { 46, 12, 0x56 }, // 0000 0101 0110 + { 47, 12, 0x57 }, // 0000 0101 0111 + { 48, 12, 0x64 }, // 0000 0110 0100 + { 49, 12, 0x65 }, // 0000 0110 0101 + { 50, 12, 0x52 }, // 0000 0101 0010 + { 51, 12, 0x53 }, // 0000 0101 0011 + { 52, 12, 0x24 }, // 0000 0010 0100 + { 53, 12, 0x37 }, // 0000 0011 0111 + { 54, 12, 0x38 }, // 0000 0011 1000 + { 55, 12, 0x27 }, // 0000 0010 0111 + { 56, 12, 0x28 }, // 0000 0010 1000 + { 57, 12, 0x58 }, // 0000 0101 1000 + { 58, 12, 0x59 }, // 0000 0101 1001 + { 59, 12, 0x2B }, // 0000 0010 1011 + { 60, 12, 0x2C }, // 0000 0010 1100 + { 61, 12, 0x5A }, // 0000 0101 1010 + { 62, 12, 0x66 }, // 0000 0110 0110 + { 63, 12, 0x67 }, // 0000 0110 0111 + { 64, 10, 0xF }, // 0000 0011 11 + { 128, 12, 0xC8 }, // 0000 1100 1000 + { 192, 12, 0xC9 }, // 0000 1100 1001 + { 256, 12, 0x5B }, // 0000 0101 1011 + { 320, 12, 0x33 }, // 0000 0011 0011 + { 384, 12, 0x34 }, // 0000 0011 0100 + { 448, 12, 0x35 }, // 0000 0011 0101 + { 512, 13, 0x6C }, // 0000 0011 0110 0 + { 576, 13, 0x6D }, // 0000 0011 0110 1 + { 640, 13, 0x4A }, // 0000 0010 0101 0 + { 704, 13, 0x4B }, // 0000 0010 0101 1 + { 768, 13, 0x4C }, // 0000 0010 0110 0 + { 832, 13, 0x4D }, // 0000 0010 0110 1 + { 896, 13, 0x72 }, // 0000 0011 1001 0 + { 960, 13, 0x73 }, // 0000 0011 1001 1 + { 1024, 13, 0x74 }, // 0000 0011 1010 0 + { 1088, 13, 0x75 }, // 0000 0011 1010 1 + { 1152, 13, 0x76 }, // 0000 0011 1011 0 + { 1216, 13, 0x77 }, // 0000 0011 1011 1 + { 1280, 13, 0x52 }, // 0000 0010 1001 0 + { 1344, 13, 0x53 }, // 0000 0010 1001 1 + { 1408, 13, 0x54 }, // 0000 0010 1010 0 + { 1472, 13, 0x55 }, // 0000 0010 1010 1 + { 1536, 13, 0x5A }, // 0000 0010 1101 0 + { 1600, 13, 0x5B }, // 0000 0010 1101 1 + { 1664, 13, 0x64 }, // 0000 0011 0010 0 + { 1728, 13, 0x65 }, // 0000 0011 0010 1 + { 1792, 11, 0x8 }, // 0000 0001 000 + { 1856, 11, 0xC }, // 0000 0001 100 + { 1920, 11, 0xD }, // 0000 0001 101 + { 1984, 12, 0x12 }, // 0000 0001 0010 + { 2048, 12, 0x13 }, // 0000 0001 0011 + { 2112, 12, 0x14 }, // 0000 0001 0100 + { 2176, 12, 0x15 }, // 0000 0001 0101 + { 2240, 12, 0x16 }, // 0000 0001 0110 + { 2304, 12, 0x17 }, // 0000 0001 0111 + { 2368, 12, 0x1C }, // 0000 0001 1100 + { 2432, 12, 0x1D }, // 0000 0001 1101 + { 2496, 12, 0x1E }, // 0000 0001 1110 + { 2560, 12, 0x1F } // 0000 0001 1111 +}; + +void PDFWriterImpl::putG4Span( tools::Long i_nSpan, bool i_bWhitePixel, BitStreamState& io_rState ) +{ + const PixelCode* pTable = i_bWhitePixel ? WhitePixelCodes : BlackPixelCodes; + // maximum encoded span is 2560 consecutive pixels + while( i_nSpan > 2623 ) + { + // write 2560 bits, that is entry (63 + (2560 >> 6)) == 103 in the appropriate table + putG4Bits( pTable[103].mnCodeBits, pTable[103].mnCode, io_rState ); + i_nSpan -= pTable[103].mnEncodedPixels; + } + // write multiples of 64 pixels up to 2560 + if( i_nSpan > 63 ) + { + sal_uInt32 nTabIndex = 63 + (i_nSpan >> 6); + OSL_ASSERT( pTable[nTabIndex].mnEncodedPixels == static_cast<sal_uInt32>(64*(i_nSpan >> 6)) ); + putG4Bits( pTable[nTabIndex].mnCodeBits, pTable[nTabIndex].mnCode, io_rState ); + i_nSpan -= pTable[nTabIndex].mnEncodedPixels; + } + putG4Bits( pTable[i_nSpan].mnCodeBits, pTable[i_nSpan].mnCode, io_rState ); +} + +void PDFWriterImpl::writeG4Stream( BitmapReadAccess const * i_pBitmap ) +{ + tools::Long nW = i_pBitmap->Width(); + tools::Long nH = i_pBitmap->Height(); + if( nW <= 0 || nH <= 0 ) + return; + if( i_pBitmap->GetBitCount() != 1 ) + return; + + BitStreamState aBitState; + + // the first reference line is virtual and completely empty + std::unique_ptr<sal_uInt8[]> pFirstRefLine(new sal_uInt8[nW/8 + 1]); + memset(pFirstRefLine.get(), 0, nW/8 + 1); + Scanline pRefLine = pFirstRefLine.get(); + for( tools::Long nY = 0; nY < nH; nY++ ) + { + const Scanline pCurLine = i_pBitmap->GetScanline( nY ); + tools::Long nLineIndex = 0; + bool bRunSet = (*pCurLine & 0x80) != 0; + bool bRefSet = (*pRefLine & 0x80) != 0; + tools::Long nRunIndex1 = bRunSet ? 0 : findBitRun( pCurLine, 0, nW, bRunSet ); + tools::Long nRefIndex1 = bRefSet ? 0 : findBitRun( pRefLine, 0, nW, bRefSet ); + for( ; nLineIndex < nW; ) + { + tools::Long nRefIndex2 = findBitRun( pRefLine, nRefIndex1, nW ); + if( nRefIndex2 >= nRunIndex1 ) + { + tools::Long nDiff = nRefIndex1 - nRunIndex1; + if( -3 <= nDiff && nDiff <= 3 ) + { // vertical coding + static const struct + { + sal_uInt32 mnCodeBits; + sal_uInt32 mnCode; + } VerticalCodes[7] = { + { 7, 0x03 }, // 0000 011 + { 6, 0x03 }, // 0000 11 + { 3, 0x03 }, // 011 + { 1, 0x1 }, // 1 + { 3, 0x2 }, // 010 + { 6, 0x02 }, // 0000 10 + { 7, 0x02 } // 0000 010 + }; + // convert to index + nDiff += 3; + + // emit diff code + putG4Bits( VerticalCodes[nDiff].mnCodeBits, VerticalCodes[nDiff].mnCode, aBitState ); + nLineIndex = nRunIndex1; + } + else + { // difference too large, horizontal coding + // emit horz code 001 + putG4Bits( 3, 0x1, aBitState ); + tools::Long nRunIndex2 = findBitRun( pCurLine, nRunIndex1, nW ); + bool bWhiteFirst = ( nLineIndex + nRunIndex1 == 0 || ! isSet( pCurLine, nLineIndex ) ); + putG4Span( nRunIndex1 - nLineIndex, bWhiteFirst, aBitState ); + putG4Span( nRunIndex2 - nRunIndex1, ! bWhiteFirst, aBitState ); + nLineIndex = nRunIndex2; + } + } + else + { // emit pass code 0001 + putG4Bits( 4, 0x1, aBitState ); + nLineIndex = nRefIndex2; + } + if( nLineIndex < nW ) + { + bool bSet = isSet( pCurLine, nLineIndex ); + nRunIndex1 = findBitRun( pCurLine, nLineIndex, nW, bSet ); + nRefIndex1 = findBitRun( pRefLine, nLineIndex, nW, ! bSet ); + nRefIndex1 = findBitRun( pRefLine, nRefIndex1, nW, bSet ); + } + } + + // the current line is the reference for the next line + pRefLine = pCurLine; + } + // terminate strip with EOFB + putG4Bits( 12, 1, aBitState ); + putG4Bits( 12, 1, aBitState ); + if( aBitState.mnNextBitPos != 8 ) + { + writeBufferBytes( &aBitState.getByte(), 1 ); + aBitState.flush(); + } +} + +void PDFWriterImpl::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint) +{ + drawLine(rStartPoint, rEndPoint); +} + +static bool lcl_canUsePDFAxialShading(const Gradient& rGradient) { + switch (rGradient.GetStyle()) + { + case css::awt::GradientStyle_LINEAR: + case css::awt::GradientStyle_AXIAL: + break; + default: + return false; + } + + // TODO: handle step count + return rGradient.GetSteps() <= 0; +} + +void PDFWriterImpl::ImplClearFontData(bool bNewFontLists) +{ + VirtualDevice::ImplClearFontData(bNewFontLists); + if (bNewFontLists && AcquireGraphics()) + { + ReleaseFontCollection(); + ReleaseFontCache(); + } +} + +void PDFWriterImpl::ImplRefreshFontData(bool bNewFontLists) +{ + if (bNewFontLists && AcquireGraphics()) + { + SetFontCollectionFromSVData(); + ResetNewFontCache(); + } +} + +vcl::Region PDFWriterImpl::ClipToDeviceBounds(vcl::Region aRegion) const +{ + return aRegion; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/print.cxx b/vcl/source/gdi/print.cxx new file mode 100644 index 0000000000..eff94a9211 --- /dev/null +++ b/vcl/source/gdi/print.cxx @@ -0,0 +1,1676 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/types.h> +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <o3tl/safeint.hxx> +#include <tools/debug.hxx> +#include <tools/helpers.hxx> + +#include <vcl/QueueInfo.hxx> +#include <vcl/event.hxx> +#include <vcl/virdev.hxx> +#include <vcl/print.hxx> +#include <vcl/printer/Options.hxx> + +#include <jobset.h> +#include <print.h> +#include <ImplOutDevData.hxx> +#include <font/PhysicalFontCollection.hxx> +#include <font/PhysicalFontFaceCollection.hxx> +#include <font/fontsubstitution.hxx> +#include <impfontcache.hxx> +#include <print.hrc> +#include <salgdi.hxx> +#include <salinst.hxx> +#include <salprn.hxx> +#include <salptype.hxx> +#include <salvd.hxx> +#include <svdata.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/uno/Sequence.h> + +int nImplSysDialog = 0; + +namespace +{ + Paper ImplGetPaperFormat( tools::Long nWidth100thMM, tools::Long nHeight100thMM ) + { + PaperInfo aInfo(nWidth100thMM, nHeight100thMM); + aInfo.doSloppyFit(); + return aInfo.getPaper(); + } + + const PaperInfo& ImplGetEmptyPaper() + { + static PaperInfo aInfo(PAPER_USER); + return aInfo; + } +} + +void ImplUpdateJobSetupPaper( JobSetup& rJobSetup ) +{ + const ImplJobSetup& rConstData = rJobSetup.ImplGetConstData(); + + if ( !rConstData.GetPaperWidth() || !rConstData.GetPaperHeight() ) + { + if ( rConstData.GetPaperFormat() != PAPER_USER ) + { + PaperInfo aInfo(rConstData.GetPaperFormat()); + + ImplJobSetup& rData = rJobSetup.ImplGetData(); + rData.SetPaperWidth( aInfo.getWidth() ); + rData.SetPaperHeight( aInfo.getHeight() ); + } + } + else if ( rConstData.GetPaperFormat() == PAPER_USER ) + { + Paper ePaper = ImplGetPaperFormat( rConstData.GetPaperWidth(), rConstData.GetPaperHeight() ); + if ( ePaper != PAPER_USER ) + rJobSetup.ImplGetData().SetPaperFormat(ePaper); + } +} + +void Printer::ImplPrintTransparent( const Bitmap& rBmp, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + Point aDestPt( LogicToPixel( rDestPt ) ); + Size aDestSz( LogicToPixel( rDestSize ) ); + tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel ); + + aSrcRect.Normalize(); + + if( rBmp.IsEmpty() || !aSrcRect.GetWidth() || !aSrcRect.GetHeight() || !aDestSz.Width() || !aDestSz.Height() ) + return; + + Bitmap aPaint( rBmp ); + BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE; + + // mirrored horizontally + if( aDestSz.Width() < 0 ) + { + aDestSz.setWidth( -aDestSz.Width() ); + aDestPt.AdjustX( -( aDestSz.Width() - 1 ) ); + nMirrFlags |= BmpMirrorFlags::Horizontal; + } + + // mirrored vertically + if( aDestSz.Height() < 0 ) + { + aDestSz.setHeight( -aDestSz.Height() ); + aDestPt.AdjustY( -( aDestSz.Height() - 1 ) ); + nMirrFlags |= BmpMirrorFlags::Vertical; + } + + // source cropped? + if( aSrcRect != tools::Rectangle( Point(), aPaint.GetSizePixel() ) ) + { + aPaint.Crop( aSrcRect ); + } + + // destination mirrored + if( nMirrFlags != BmpMirrorFlags::NONE ) + { + aPaint.Mirror( nMirrFlags ); + } + + // we always want to have a mask + AlphaMask aAlphaMask(aSrcRect.GetSize()); + aAlphaMask.Erase( 0 ); + + // do painting + const tools::Long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight(); + tools::Long nX, nY; // , nWorkX, nWorkY, nWorkWidth, nWorkHeight; + std::unique_ptr<tools::Long[]> pMapX(new tools::Long[ nSrcWidth + 1 ]); + std::unique_ptr<tools::Long[]> pMapY(new tools::Long[ nSrcHeight + 1 ]); + const bool bOldMap = mbMap; + + mbMap = false; + + // create forward mapping tables + for( nX = 0; nX <= nSrcWidth; nX++ ) + pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth ); + + for( nY = 0; nY <= nSrcHeight; nY++ ) + pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight ); + + tools::Rectangle rectangle { Point(0,0), aSrcRect.GetSize() }; + const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]); + const Size aMapSz( pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1 + pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y + Bitmap aBandBmp(aPaint); + + DrawBitmap(aMapPt, aMapSz, Point(), aBandBmp.GetSizePixel(), aBandBmp); + + mbMap = bOldMap; +} + +bool Printer::DrawTransformBitmapExDirect( + const basegfx::B2DHomMatrix& /*aFullTransform*/, + const BitmapEx& /*rBitmapEx*/, + double /*fAlpha*/) +{ + // printers can't draw bitmaps directly + return false; +} + +bool Printer::TransformAndReduceBitmapExToTargetRange( + const basegfx::B2DHomMatrix& /*aFullTransform*/, + basegfx::B2DRange& /*aVisibleRange*/, + double& /*fMaximumArea*/) +{ + // deliberately do nothing - you can't reduce the + // target range for a printer at all + return true; +} + +void Printer::DrawDeviceBitmapEx( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + BitmapEx& rBmpEx ) +{ + if( rBmpEx.IsAlpha() ) + { + // #107169# For true alpha bitmaps, no longer masking the + // bitmap, but perform a full alpha blend against a white + // background here. + Bitmap aBmp( rBmpEx.GetBitmap() ); + aBmp.Blend( rBmpEx.GetAlphaMask(), COL_WHITE ); + DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp ); + } + else + { + Bitmap aBmp( rBmpEx.GetBitmap() ); + ImplPrintTransparent( aBmp, rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel ); + } +} + +void Printer::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly, + sal_uInt16 nTransparencePercent ) +{ + // #110958# Disable alpha VDev, we perform the necessary + VirtualDevice* pOldAlphaVDev = mpAlphaVDev; + + // operation explicitly further below. + if( mpAlphaVDev ) + mpAlphaVDev = nullptr; + + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + + mpMetaFile = pOldMetaFile; + + // #110958# Restore disabled alpha VDev + mpAlphaVDev = pOldAlphaVDev; + + tools::Rectangle aPolyRect( LogicToPixel( rPolyPoly ).GetBoundRect() ); + const Size aDPISize( LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)) ); + const tools::Long nBaseExtent = std::max<tools::Long>( FRound( aDPISize.Width() / 300. ), 1 ); + tools::Long nMove; + const sal_uInt16 nTrans = ( nTransparencePercent < 13 ) ? 0 : + ( nTransparencePercent < 38 ) ? 25 : + ( nTransparencePercent < 63 ) ? 50 : + ( nTransparencePercent < 88 ) ? 75 : 100; + + switch( nTrans ) + { + case 25: nMove = nBaseExtent * 3; break; + case 50: nMove = nBaseExtent * 4; break; + case 75: nMove = nBaseExtent * 6; break; + + // #i112959# very transparent (88 < nTransparencePercent <= 99) + case 100: nMove = nBaseExtent * 8; break; + + // #i112959# not transparent (nTransparencePercent < 13) + default: nMove = 0; break; + } + + Push( vcl::PushFlags::CLIPREGION | vcl::PushFlags::LINECOLOR ); + IntersectClipRegion(vcl::Region(rPolyPoly)); + SetLineColor( GetFillColor() ); + const bool bOldMap = mbMap; + EnableMapMode( false ); + + if(nMove) + { + tools::Rectangle aRect( aPolyRect.TopLeft(), Size( aPolyRect.GetWidth(), nBaseExtent ) ); + while( aRect.Top() <= aPolyRect.Bottom() ) + { + DrawRect( aRect ); + aRect.Move( 0, nMove ); + } + + aRect = tools::Rectangle( aPolyRect.TopLeft(), Size( nBaseExtent, aPolyRect.GetHeight() ) ); + while( aRect.Left() <= aPolyRect.Right() ) + { + DrawRect( aRect ); + aRect.Move( nMove, 0 ); + } + } + else + { + // #i112959# if not transparent, draw full rectangle in clip region + DrawRect( aPolyRect ); + } + + EnableMapMode( bOldMap ); + Pop(); + + mpMetaFile = pOldMetaFile; + + // #110958# Restore disabled alpha VDev + mpAlphaVDev = pOldAlphaVDev; +} + +void Printer::DrawOutDev( const Point& /*rDestPt*/, const Size& /*rDestSize*/, + const Point& /*rSrcPt*/, const Size& /*rSrcSize*/ ) +{ + SAL_WARN( "vcl.gdi", "Don't use OutputDevice::DrawOutDev(...) with printer devices!" ); +} + +void Printer::DrawOutDev( const Point& /*rDestPt*/, const Size& /*rDestSize*/, + const Point& /*rSrcPt*/, const Size& /*rSrcSize*/, + const OutputDevice& /*rOutDev*/ ) +{ + SAL_WARN( "vcl.gdi", "Don't use OutputDevice::DrawOutDev(...) with printer devices!" ); +} + +void Printer::CopyArea( const Point& /*rDestPt*/, + const Point& /*rSrcPt*/, const Size& /*rSrcSize*/, + bool /*bWindowInvalidate*/ ) +{ + SAL_WARN( "vcl.gdi", "Don't use OutputDevice::CopyArea(...) with printer devices!" ); +} + +tools::Rectangle Printer::GetBackgroundComponentBounds() const +{ + Point aPageOffset = Point( 0, 0 ) - this->GetPageOffsetPixel(); + Size aSize = this->GetPaperSizePixel(); + return tools::Rectangle( aPageOffset, aSize ); +} + +void Printer::SetPrinterOptions( const vcl::printer::Options& i_rOptions ) +{ + *mpPrinterOptions = i_rOptions; +} + +bool Printer::HasMirroredGraphics() const +{ + // due to a "hotfix" for AOO bug i55719, this needs to return false + return false; +} + +SalPrinterQueueInfo::SalPrinterQueueInfo() +{ + mnStatus = PrintQueueFlags::NONE; + mnJobs = QUEUE_JOBS_DONTKNOW; +} + +SalPrinterQueueInfo::~SalPrinterQueueInfo() +{ +} + +ImplPrnQueueList::~ImplPrnQueueList() +{ +} + +void ImplPrnQueueList::Add( std::unique_ptr<SalPrinterQueueInfo> pData ) +{ + std::unordered_map< OUString, sal_Int32 >::iterator it = + m_aNameToIndex.find( pData->maPrinterName ); + if( it == m_aNameToIndex.end() ) + { + m_aNameToIndex[ pData->maPrinterName ] = m_aQueueInfos.size(); + m_aPrinterList.push_back( pData->maPrinterName ); + m_aQueueInfos.push_back( ImplPrnQueueData() ); + m_aQueueInfos.back().mpQueueInfo = nullptr; + m_aQueueInfos.back().mpSalQueueInfo = std::move(pData); + } + else // this should not happen, but ... + { + ImplPrnQueueData& rData = m_aQueueInfos[ it->second ]; + rData.mpQueueInfo.reset(); + rData.mpSalQueueInfo = std::move(pData); + } +} + +ImplPrnQueueData* ImplPrnQueueList::Get( const OUString& rPrinter ) +{ + ImplPrnQueueData* pData = nullptr; + std::unordered_map<OUString,sal_Int32>::iterator it = + m_aNameToIndex.find( rPrinter ); + if( it != m_aNameToIndex.end() ) + pData = &m_aQueueInfos[it->second]; + return pData; +} + +static void ImplInitPrnQueueList() +{ + ImplSVData* pSVData = ImplGetSVData(); + + pSVData->maGDIData.mpPrinterQueueList.reset(new ImplPrnQueueList); + + static const char* pEnv = getenv( "SAL_DISABLE_PRINTERLIST" ); + if( !pEnv || !*pEnv ) + pSVData->mpDefInst->GetPrinterQueueInfo( pSVData->maGDIData.mpPrinterQueueList.get() ); +} + +void ImplDeletePrnQueueList() +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maGDIData.mpPrinterQueueList.reset(); +} + +const std::vector<OUString>& Printer::GetPrinterQueues() +{ + ImplSVData* pSVData = ImplGetSVData(); + if ( !pSVData->maGDIData.mpPrinterQueueList ) + ImplInitPrnQueueList(); + assert(pSVData->maGDIData.mpPrinterQueueList && "mpPrinterQueueList exists by now"); + return pSVData->maGDIData.mpPrinterQueueList->m_aPrinterList; +} + +const QueueInfo* Printer::GetQueueInfo( const OUString& rPrinterName, bool bStatusUpdate ) +{ + ImplSVData* pSVData = ImplGetSVData(); + + if ( !pSVData->maGDIData.mpPrinterQueueList ) + ImplInitPrnQueueList(); + + if ( !pSVData->maGDIData.mpPrinterQueueList ) + return nullptr; + + ImplPrnQueueData* pInfo = pSVData->maGDIData.mpPrinterQueueList->Get( rPrinterName ); + if( pInfo ) + { + if( !pInfo->mpQueueInfo || bStatusUpdate ) + pSVData->mpDefInst->GetPrinterQueueState( pInfo->mpSalQueueInfo.get() ); + + if ( !pInfo->mpQueueInfo ) + pInfo->mpQueueInfo.reset(new QueueInfo); + + pInfo->mpQueueInfo->maPrinterName = pInfo->mpSalQueueInfo->maPrinterName; + pInfo->mpQueueInfo->maDriver = pInfo->mpSalQueueInfo->maDriver; + pInfo->mpQueueInfo->maLocation = pInfo->mpSalQueueInfo->maLocation; + pInfo->mpQueueInfo->maComment = pInfo->mpSalQueueInfo->maComment; + pInfo->mpQueueInfo->mnStatus = pInfo->mpSalQueueInfo->mnStatus; + pInfo->mpQueueInfo->mnJobs = pInfo->mpSalQueueInfo->mnJobs; + return pInfo->mpQueueInfo.get(); + } + return nullptr; +} + +OUString Printer::GetDefaultPrinterName() +{ + static const char* pEnv = getenv( "SAL_DISABLE_DEFAULTPRINTER" ); + if( !pEnv || !*pEnv ) + { + ImplSVData* pSVData = ImplGetSVData(); + + return pSVData->mpDefInst->GetDefaultPrinter(); + } + return OUString(); +} + +void Printer::ImplInitData() +{ + mbDevOutput = false; + mbDefPrinter = false; + mnError = ERRCODE_NONE; + mnPageQueueSize = 0; + mnCopyCount = 1; + mbCollateCopy = false; + mbPrinting = false; + mbJobActive = false; + mbPrintFile = false; + mbInPrintPage = false; + mbNewJobSetup = false; + mbSinglePrintJobs = false; + mpInfoPrinter = nullptr; + mpPrinter = nullptr; + mpDisplayDev = nullptr; + mpPrinterOptions.reset(new vcl::printer::Options); + + // Add printer to the list + ImplSVData* pSVData = ImplGetSVData(); + mpNext = pSVData->maGDIData.mpFirstPrinter; + mpPrev = nullptr; + if ( mpNext ) + mpNext->mpPrev = this; + pSVData->maGDIData.mpFirstPrinter = this; +} + +bool Printer::AcquireGraphics() const +{ + DBG_TESTSOLARMUTEX(); + + if ( mpGraphics ) + return true; + + mbInitLineColor = true; + mbInitFillColor = true; + mbInitFont = true; + mbInitTextColor = true; + mbInitClipRegion = true; + + ImplSVData* pSVData = ImplGetSVData(); + + if ( mpJobGraphics ) + mpGraphics = mpJobGraphics; + else if ( mpDisplayDev ) + { + const VirtualDevice* pVirDev = mpDisplayDev; + mpGraphics = pVirDev->mpVirDev->AcquireGraphics(); + // if needed retry after releasing least recently used virtual device graphics + while ( !mpGraphics ) + { + if ( !pSVData->maGDIData.mpLastVirGraphics ) + break; + pSVData->maGDIData.mpLastVirGraphics->ReleaseGraphics(); + mpGraphics = pVirDev->mpVirDev->AcquireGraphics(); + } + // update global LRU list of virtual device graphics + if ( mpGraphics ) + { + mpNextGraphics = pSVData->maGDIData.mpFirstVirGraphics; + pSVData->maGDIData.mpFirstVirGraphics = const_cast<Printer*>(this); + if ( mpNextGraphics ) + mpNextGraphics->mpPrevGraphics = const_cast<Printer*>(this); + if ( !pSVData->maGDIData.mpLastVirGraphics ) + pSVData->maGDIData.mpLastVirGraphics = const_cast<Printer*>(this); + } + } + else + { + mpGraphics = mpInfoPrinter->AcquireGraphics(); + // if needed retry after releasing least recently used printer graphics + while ( !mpGraphics ) + { + if ( !pSVData->maGDIData.mpLastPrnGraphics ) + break; + pSVData->maGDIData.mpLastPrnGraphics->ReleaseGraphics(); + mpGraphics = mpInfoPrinter->AcquireGraphics(); + } + // update global LRU list of printer graphics + if ( mpGraphics ) + { + mpNextGraphics = pSVData->maGDIData.mpFirstPrnGraphics; + pSVData->maGDIData.mpFirstPrnGraphics = const_cast<Printer*>(this); + if ( mpNextGraphics ) + mpNextGraphics->mpPrevGraphics = const_cast<Printer*>(this); + if ( !pSVData->maGDIData.mpLastPrnGraphics ) + pSVData->maGDIData.mpLastPrnGraphics = const_cast<Printer*>(this); + } + } + + if ( mpGraphics ) + { + mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp ); + mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable)); + } + + return mpGraphics != nullptr; +} + +void Printer::ImplReleaseFonts() +{ + mpGraphics->ReleaseFonts(); + mbNewFont = true; + mbInitFont = true; + + mpFontInstance.clear(); + mpFontFaceCollection.reset(); +} + +void Printer::ImplReleaseGraphics(bool bRelease) +{ + DBG_TESTSOLARMUTEX(); + + if ( !mpGraphics ) + return; + + // release the fonts of the physically released graphics device + if( bRelease ) + ImplReleaseFonts(); + + ImplSVData* pSVData = ImplGetSVData(); + + Printer* pPrinter = this; + + if ( !pPrinter->mpJobGraphics ) + { + if ( pPrinter->mpDisplayDev ) + { + VirtualDevice* pVirDev = pPrinter->mpDisplayDev; + if ( bRelease ) + pVirDev->mpVirDev->ReleaseGraphics( mpGraphics ); + // remove from global LRU list of virtual device graphics + if ( mpPrevGraphics ) + mpPrevGraphics->mpNextGraphics = mpNextGraphics; + else + pSVData->maGDIData.mpFirstVirGraphics = mpNextGraphics; + if ( mpNextGraphics ) + mpNextGraphics->mpPrevGraphics = mpPrevGraphics; + else + pSVData->maGDIData.mpLastVirGraphics = mpPrevGraphics; + } + else + { + if ( bRelease ) + pPrinter->mpInfoPrinter->ReleaseGraphics( mpGraphics ); + // remove from global LRU list of printer graphics + if ( mpPrevGraphics ) + mpPrevGraphics->mpNextGraphics = mpNextGraphics; + else + pSVData->maGDIData.mpFirstPrnGraphics = static_cast<Printer*>(mpNextGraphics.get()); + if ( mpNextGraphics ) + mpNextGraphics->mpPrevGraphics = mpPrevGraphics; + else + pSVData->maGDIData.mpLastPrnGraphics = static_cast<Printer*>(mpPrevGraphics.get()); + } + } + + mpGraphics = nullptr; + mpPrevGraphics = nullptr; + mpNextGraphics = nullptr; +} + +void Printer::ReleaseGraphics(bool bRelease) +{ + ImplReleaseGraphics(bRelease); +} + +void Printer::ImplInit( SalPrinterQueueInfo* pInfo ) +{ + ImplSVData* pSVData = ImplGetSVData(); + // #i74084# update info for this specific SalPrinterQueueInfo + pSVData->mpDefInst->GetPrinterQueueState( pInfo ); + + // Test whether the driver actually matches the JobSetup + ImplJobSetup& rData = maJobSetup.ImplGetData(); + if ( rData.GetDriverData() ) + { + if ( rData.GetPrinterName() != pInfo->maPrinterName || + rData.GetDriver() != pInfo->maDriver ) + { + rData.SetDriverData(nullptr, 0); + } + } + + // Remember printer name + maPrinterName = pInfo->maPrinterName; + maDriver = pInfo->maDriver; + + // Add printer name to JobSetup + rData.SetPrinterName( maPrinterName ); + rData.SetDriver( maDriver ); + + mpInfoPrinter = pSVData->mpDefInst->CreateInfoPrinter( pInfo, &rData ); + mpPrinter = nullptr; + mpJobGraphics = nullptr; + ImplUpdateJobSetupPaper( maJobSetup ); + + if ( !mpInfoPrinter ) + { + ImplInitDisplay(); + return; + } + + // we need a graphics + if ( !AcquireGraphics() ) + { + ImplInitDisplay(); + return; + } + + // Init data + ImplUpdatePageData(); + mxFontCollection = std::make_shared<vcl::font::PhysicalFontCollection>(); + mxFontCache = std::make_shared<ImplFontCache>(); + mpGraphics->GetDevFontList(mxFontCollection.get()); +} + +void Printer::ImplInitDisplay() +{ + ImplSVData* pSVData = ImplGetSVData(); + + mpInfoPrinter = nullptr; + mpPrinter = nullptr; + mpJobGraphics = nullptr; + + mpDisplayDev = VclPtr<VirtualDevice>::Create(); + mxFontCollection = pSVData->maGDIData.mxScreenFontList; + mxFontCache = pSVData->maGDIData.mxScreenFontCache; + mnDPIX = mpDisplayDev->mnDPIX; + mnDPIY = mpDisplayDev->mnDPIY; +} + +void Printer::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + Point aDestPt( LogicToPixel( rDestPt ) ); + Size aDestSz( LogicToPixel( rDestSize ) ); + tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel ); + + aSrcRect.Normalize(); + + if( !(!rMask.IsEmpty() && aSrcRect.GetWidth() && aSrcRect.GetHeight() && aDestSz.Width() && aDestSz.Height()) ) + return; + + Bitmap aMask( rMask ); + BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE; + + if (aMask.getPixelFormat() >= vcl::PixelFormat::N8_BPP) + aMask.Convert( BmpConversion::N1BitThreshold ); + + // mirrored horizontally + if( aDestSz.Width() < 0 ) + { + aDestSz.setWidth( -aDestSz.Width() ); + aDestPt.AdjustX( -( aDestSz.Width() - 1 ) ); + nMirrFlags |= BmpMirrorFlags::Horizontal; + } + + // mirrored vertically + if( aDestSz.Height() < 0 ) + { + aDestSz.setHeight( -aDestSz.Height() ); + aDestPt.AdjustY( -( aDestSz.Height() - 1 ) ); + nMirrFlags |= BmpMirrorFlags::Vertical; + } + + // source cropped? + if( aSrcRect != tools::Rectangle( Point(), aMask.GetSizePixel() ) ) + aMask.Crop( aSrcRect ); + + // destination mirrored + if( nMirrFlags != BmpMirrorFlags::NONE) + aMask.Mirror( nMirrFlags ); + + // do painting + const tools::Long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight(); + tools::Long nX, nY; //, nWorkX, nWorkY, nWorkWidth, nWorkHeight; + std::unique_ptr<tools::Long[]> pMapX( new tools::Long[ nSrcWidth + 1 ] ); + std::unique_ptr<tools::Long[]> pMapY( new tools::Long[ nSrcHeight + 1 ] ); + GDIMetaFile* pOldMetaFile = mpMetaFile; + const bool bOldMap = mbMap; + + mpMetaFile = nullptr; + mbMap = false; + Push( vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR ); + SetLineColor( rMaskColor ); + SetFillColor( rMaskColor ); + InitLineColor(); + InitFillColor(); + + // create forward mapping tables + for( nX = 0; nX <= nSrcWidth; nX++ ) + pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth ); + + for( nY = 0; nY <= nSrcHeight; nY++ ) + pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight ); + + // walk through all rectangles of mask + const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel()))); + RectangleVector aRectangles; + aWorkRgn.GetRegionRectangles(aRectangles); + + for (auto const& rectangle : aRectangles) + { + const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]); + const Size aMapSz( + pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1 + pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y + + DrawRect(tools::Rectangle(aMapPt, aMapSz)); + } + + Pop(); + mbMap = bOldMap; + mpMetaFile = pOldMetaFile; +} + +SalPrinterQueueInfo* Printer::ImplGetQueueInfo( const OUString& rPrinterName, + const OUString* pDriver ) +{ + ImplSVData* pSVData = ImplGetSVData(); + if ( !pSVData->maGDIData.mpPrinterQueueList ) + ImplInitPrnQueueList(); + + ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList.get(); + if ( pPrnList && !pPrnList->m_aQueueInfos.empty() ) + { + // first search for the printer name directly + ImplPrnQueueData* pInfo = pPrnList->Get( rPrinterName ); + if( pInfo ) + return pInfo->mpSalQueueInfo.get(); + + // then search case insensitive + for(const ImplPrnQueueData & rQueueInfo : pPrnList->m_aQueueInfos) + { + if( rQueueInfo.mpSalQueueInfo->maPrinterName.equalsIgnoreAsciiCase( rPrinterName ) ) + return rQueueInfo.mpSalQueueInfo.get(); + } + + // then search for driver name + if ( pDriver ) + { + for(const ImplPrnQueueData & rQueueInfo : pPrnList->m_aQueueInfos) + { + if( rQueueInfo.mpSalQueueInfo->maDriver == *pDriver ) + return rQueueInfo.mpSalQueueInfo.get(); + } + } + + // then the default printer + pInfo = pPrnList->Get( GetDefaultPrinterName() ); + if( pInfo ) + return pInfo->mpSalQueueInfo.get(); + + // last chance: the first available printer + return pPrnList->m_aQueueInfos[0].mpSalQueueInfo.get(); + } + + return nullptr; +} + +void Printer::ImplUpdatePageData() +{ + // we need a graphics + if ( !AcquireGraphics() ) + return; + + mpGraphics->GetResolution( mnDPIX, mnDPIY ); + mpInfoPrinter->GetPageInfo( &maJobSetup.ImplGetConstData(), + mnOutWidth, mnOutHeight, + maPageOffset, + maPaperSize ); +} + +void Printer::ImplUpdateFontList() +{ + ImplUpdateFontData(); +} + +tools::Long Printer::GetGradientStepCount( tools::Long nMinRect ) +{ + // use display-equivalent step size calculation + tools::Long nInc = (nMinRect < 800) ? 10 : 20; + + return nInc; +} + +Printer::Printer() + : OutputDevice(OUTDEV_PRINTER) +{ + ImplInitData(); + SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( GetDefaultPrinterName(), nullptr ); + if ( pInfo ) + { + ImplInit( pInfo ); + if ( !IsDisplayPrinter() ) + mbDefPrinter = true; + } + else + ImplInitDisplay(); +} + +Printer::Printer( const JobSetup& rJobSetup ) + : OutputDevice(OUTDEV_PRINTER) + , maJobSetup(rJobSetup) +{ + ImplInitData(); + const ImplJobSetup& rConstData = rJobSetup.ImplGetConstData(); + OUString aDriver = rConstData.GetDriver(); + SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rConstData.GetPrinterName(), + &aDriver ); + if ( pInfo ) + { + ImplInit( pInfo ); + SetJobSetup( rJobSetup ); + } + else + { + ImplInitDisplay(); + maJobSetup = JobSetup(); + } +} + +Printer::Printer( const QueueInfo& rQueueInfo ) + : OutputDevice(OUTDEV_PRINTER) +{ + ImplInitData(); + SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rQueueInfo.GetPrinterName(), + &rQueueInfo.GetDriver() ); + if ( pInfo ) + ImplInit( pInfo ); + else + ImplInitDisplay(); +} + +Printer::Printer( const OUString& rPrinterName ) + : OutputDevice(OUTDEV_PRINTER) +{ + ImplInitData(); + SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rPrinterName, nullptr ); + if ( pInfo ) + ImplInit( pInfo ); + else + ImplInitDisplay(); +} + +Printer::~Printer() +{ + disposeOnce(); +} + +void Printer::dispose() +{ + SAL_WARN_IF( IsPrinting(), "vcl.gdi", "Printer::~Printer() - Job is printing" ); + SAL_WARN_IF( IsJobActive(), "vcl.gdi", "Printer::~Printer() - Job is active" ); + + mpPrinterOptions.reset(); + + ImplReleaseGraphics(); + if ( mpInfoPrinter ) + ImplGetSVData()->mpDefInst->DestroyInfoPrinter( mpInfoPrinter ); + if ( mpDisplayDev ) + mpDisplayDev.disposeAndClear(); + else + { + // OutputDevice Dtor is trying the same thing; that why we need to set + // the FontEntry to NULL here + // TODO: consolidate duplicate cleanup by Printer and OutputDevice + mpFontInstance.clear(); + mpFontFaceCollection.reset(); + mxFontCache.reset(); + // font list deleted by OutputDevice dtor + } + + // Add printer from the list + ImplSVData* pSVData = ImplGetSVData(); + if ( mpPrev ) + mpPrev->mpNext = mpNext; + else + pSVData->maGDIData.mpFirstPrinter = mpNext; + if ( mpNext ) + mpNext->mpPrev = mpPrev; + + mpPrev.clear(); + mpNext.clear(); + OutputDevice::dispose(); +} + +Size Printer::GetButtonBorderSize() +{ + Size aBrdSize(LogicToPixel(Size(20, 20), MapMode(MapUnit::Map100thMM))); + + if (!aBrdSize.Width()) + aBrdSize.setWidth(1); + + if (!aBrdSize.Height()) + aBrdSize.setHeight(1); + + return aBrdSize; +} + +sal_uInt32 Printer::GetCapabilities( PrinterCapType nType ) const +{ + if ( IsDisplayPrinter() ) + return 0; + + if( mpInfoPrinter ) + return mpInfoPrinter->GetCapabilities( &maJobSetup.ImplGetConstData(), nType ); + else + return 0; +} + +bool Printer::HasSupport( PrinterSupport eFeature ) const +{ + switch ( eFeature ) + { + case PrinterSupport::SetOrientation: + return GetCapabilities( PrinterCapType::SetOrientation ) != 0; + case PrinterSupport::SetPaperSize: + return GetCapabilities( PrinterCapType::SetPaperSize ) != 0; + case PrinterSupport::SetPaper: + return GetCapabilities( PrinterCapType::SetPaper ) != 0; + case PrinterSupport::CollateCopy: + return (GetCapabilities( PrinterCapType::CollateCopies ) != 0); + case PrinterSupport::SetupDialog: + return GetCapabilities( PrinterCapType::SupportDialog ) != 0; + } + + return true; +} + +bool Printer::SetJobSetup( const JobSetup& rSetup ) +{ + if ( IsDisplayPrinter() || mbInPrintPage ) + return false; + + JobSetup aJobSetup = rSetup; + + ReleaseGraphics(); + if ( mpInfoPrinter->SetPrinterData( &aJobSetup.ImplGetData() ) ) + { + ImplUpdateJobSetupPaper( aJobSetup ); + mbNewJobSetup = true; + maJobSetup = aJobSetup; + ImplUpdatePageData(); + ImplUpdateFontList(); + return true; + } + + return false; +} + +bool Printer::Setup(weld::Window* pWindow, PrinterSetupMode eMode) +{ + if ( IsDisplayPrinter() ) + return false; + + if ( IsJobActive() || IsPrinting() ) + return false; + + JobSetup aJobSetup = maJobSetup; + ImplJobSetup& rData = aJobSetup.ImplGetData(); + rData.SetPrinterSetupMode( eMode ); + // TODO: orig page size + + if (!pWindow) + { + vcl::Window* pDefWin = ImplGetDefaultWindow(); + pWindow = pDefWin ? pDefWin->GetFrameWeld() : nullptr; + } + if( !pWindow ) + return false; + + ReleaseGraphics(); + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maAppData.mnModalMode++; + nImplSysDialog++; + bool bSetup = mpInfoPrinter->Setup(pWindow, &rData); + pSVData->maAppData.mnModalMode--; + nImplSysDialog--; + if ( bSetup ) + { + ImplUpdateJobSetupPaper( aJobSetup ); + mbNewJobSetup = true; + maJobSetup = aJobSetup; + ImplUpdatePageData(); + ImplUpdateFontList(); + return true; + } + return false; +} + +bool Printer::SetPrinterProps( const Printer* pPrinter ) +{ + if ( IsJobActive() || IsPrinting() ) + return false; + + ImplSVData* pSVData = ImplGetSVData(); + + mbDefPrinter = pPrinter->mbDefPrinter; + maPrintFile = pPrinter->maPrintFile; + mbPrintFile = pPrinter->mbPrintFile; + mnCopyCount = pPrinter->mnCopyCount; + mbCollateCopy = pPrinter->mbCollateCopy; + mnPageQueueSize = pPrinter->mnPageQueueSize; + *mpPrinterOptions = *pPrinter->mpPrinterOptions; + + if ( pPrinter->IsDisplayPrinter() ) + { + // Destroy old printer + if ( !IsDisplayPrinter() ) + { + ReleaseGraphics(); + pSVData->mpDefInst->DestroyInfoPrinter( mpInfoPrinter ); + mpFontInstance.clear(); + mpFontFaceCollection.reset(); + // clean up font list + mxFontCache.reset(); + mxFontCollection.reset(); + + mbInitFont = true; + mbNewFont = true; + mpInfoPrinter = nullptr; + } + + // Construct new printer + ImplInitDisplay(); + return true; + } + + // Destroy old printer? + if ( GetName() != pPrinter->GetName() ) + { + ReleaseGraphics(); + if ( mpDisplayDev ) + { + mpDisplayDev.disposeAndClear(); + } + else + { + pSVData->mpDefInst->DestroyInfoPrinter( mpInfoPrinter ); + + mpFontInstance.clear(); + mpFontFaceCollection.reset(); + mxFontCache.reset(); + mxFontCollection.reset(); + mbInitFont = true; + mbNewFont = true; + mpInfoPrinter = nullptr; + } + + // Construct new printer + OUString aDriver = pPrinter->GetDriverName(); + SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( pPrinter->GetName(), &aDriver ); + if ( pInfo ) + { + ImplInit( pInfo ); + SetJobSetup( pPrinter->GetJobSetup() ); + } + else + ImplInitDisplay(); + } + else + SetJobSetup( pPrinter->GetJobSetup() ); + + return false; +} + +bool Printer::SetOrientation( Orientation eOrientation ) +{ + if ( mbInPrintPage ) + return false; + + if ( maJobSetup.ImplGetConstData().GetOrientation() != eOrientation ) + { + JobSetup aJobSetup = maJobSetup; + ImplJobSetup& rData = aJobSetup.ImplGetData(); + + rData.SetOrientation(eOrientation); + + if ( IsDisplayPrinter() ) + { + mbNewJobSetup = true; + maJobSetup = aJobSetup; + return true; + } + + ReleaseGraphics(); + if ( mpInfoPrinter->SetData( JobSetFlags::ORIENTATION, &rData ) ) + { + ImplUpdateJobSetupPaper( aJobSetup ); + mbNewJobSetup = true; + maJobSetup = aJobSetup; + ImplUpdatePageData(); + ImplUpdateFontList(); + return true; + } + else + return false; + } + + return true; +} + +Orientation Printer::GetOrientation() const +{ + return maJobSetup.ImplGetConstData().GetOrientation(); +} + +bool Printer::SetPaperBin( sal_uInt16 nPaperBin ) +{ + if ( mbInPrintPage ) + return false; + + if ( maJobSetup.ImplGetConstData().GetPaperBin() != nPaperBin && + nPaperBin < GetPaperBinCount() ) + { + JobSetup aJobSetup = maJobSetup; + ImplJobSetup& rData = aJobSetup.ImplGetData(); + rData.SetPaperBin(nPaperBin); + + if ( IsDisplayPrinter() ) + { + mbNewJobSetup = true; + maJobSetup = aJobSetup; + return true; + } + + ReleaseGraphics(); + if ( mpInfoPrinter->SetData( JobSetFlags::PAPERBIN, &rData ) ) + { + ImplUpdateJobSetupPaper( aJobSetup ); + mbNewJobSetup = true; + maJobSetup = aJobSetup; + ImplUpdatePageData(); + ImplUpdateFontList(); + return true; + } + else + return false; + } + + return true; +} + +sal_uInt16 Printer::GetPaperBin() const +{ + return maJobSetup.ImplGetConstData().GetPaperBin(); +} + +bool Printer::GetPrinterSettingsPreferred() const +{ + return maJobSetup.ImplGetConstData().GetPapersizeFromSetup(); +} + +// dear loplugins, DO NOT REMOVE this code +// it will be used in follow-up commits +void Printer::SetPrinterSettingsPreferred( bool bPaperSizeFromSetup) +{ + if ( maJobSetup.ImplGetConstData().GetPapersizeFromSetup() != bPaperSizeFromSetup ) + { + JobSetup aJobSetup = maJobSetup; + ImplJobSetup& rData = aJobSetup.ImplGetData(); + rData.SetPapersizeFromSetup(bPaperSizeFromSetup); + + mbNewJobSetup = true; + maJobSetup = aJobSetup; + } +} + +// Map user paper format to an available printer paper format +void Printer::ImplFindPaperFormatForUserSize( JobSetup& aJobSetup ) +{ + ImplJobSetup& rData = aJobSetup.ImplGetData(); + + // The angle that a landscape page will be turned counterclockwise wrt to portrait. + int nLandscapeAngle = mpInfoPrinter ? mpInfoPrinter->GetLandscapeAngle( &maJobSetup.ImplGetConstData() ) : 900; + int nPaperCount = GetPaperInfoCount(); + PaperInfo aInfo(rData.GetPaperWidth(), rData.GetPaperHeight()); + + // Compare all paper formats and get the appropriate one + for ( int i = 0; i < nPaperCount; i++ ) + { + const PaperInfo& rPaperInfo = GetPaperInfo( i ); + + if ( aInfo.sloppyEqual(rPaperInfo) ) + { + rData.SetPaperFormat( + ImplGetPaperFormat( rPaperInfo.getWidth(), + rPaperInfo.getHeight() )); + rData.SetOrientation( Orientation::Portrait ); + return; + } + } + + // If the printer supports landscape orientation, check paper sizes again + // with landscape orientation. This is necessary as a printer driver provides + // all paper sizes with portrait orientation only!! + if ( !(rData.GetPaperFormat() == PAPER_USER && + nLandscapeAngle != 0 && + HasSupport( PrinterSupport::SetOrientation ))) + return; + + const tools::Long nRotatedWidth = rData.GetPaperHeight(); + const tools::Long nRotatedHeight = rData.GetPaperWidth(); + PaperInfo aRotatedInfo(nRotatedWidth, nRotatedHeight); + + for ( int i = 0; i < nPaperCount; i++ ) + { + const PaperInfo& rPaperInfo = GetPaperInfo( i ); + + if ( aRotatedInfo.sloppyEqual( rPaperInfo ) ) + { + rData.SetPaperFormat( + ImplGetPaperFormat( rPaperInfo.getWidth(), + rPaperInfo.getHeight() )); + rData.SetOrientation( Orientation::Landscape ); + return; + } + } +} + +void Printer::SetPaper( Paper ePaper ) +{ + if ( mbInPrintPage ) + return; + + if ( maJobSetup.ImplGetConstData().GetPaperFormat() == ePaper ) + return; + + JobSetup aJobSetup = maJobSetup; + ImplJobSetup& rData = aJobSetup.ImplGetData(); + + rData.SetPaperFormat( ePaper ); + if ( ePaper != PAPER_USER ) + { + PaperInfo aInfo(ePaper); + rData.SetPaperWidth( aInfo.getWidth() ); + rData.SetPaperHeight( aInfo.getHeight() ); + } + + if ( IsDisplayPrinter() ) + { + mbNewJobSetup = true; + maJobSetup = aJobSetup; + return; + } + + ReleaseGraphics(); + if ( ePaper == PAPER_USER ) + ImplFindPaperFormatForUserSize( aJobSetup ); + if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData )) + { + ImplUpdateJobSetupPaper( aJobSetup ); + mbNewJobSetup = true; + maJobSetup = aJobSetup; + ImplUpdatePageData(); + ImplUpdateFontList(); + } +} + +bool Printer::SetPaperSizeUser( const Size& rSize ) +{ + if ( mbInPrintPage ) + return false; + + const Size aPixSize = LogicToPixel( rSize ); + const Size aPageSize = PixelToLogic(aPixSize, MapMode(MapUnit::Map100thMM)); + bool bNeedToChange(maJobSetup.ImplGetConstData().GetPaperWidth() != aPageSize.Width() || + maJobSetup.ImplGetConstData().GetPaperHeight() != aPageSize.Height()); + + if(!bNeedToChange) + { + // #i122984# only need to change when Paper is different from PAPER_USER and + // the mapped Paper which will created below in the call to ImplFindPaperFormatForUserSize + // and will replace maJobSetup.ImplGetConstData()->GetPaperFormat(). This leads to + // unnecessary JobSetups, e.g. when printing a multi-page fax, but also with + // normal print + const Paper aPaper = ImplGetPaperFormat(aPageSize.Width(), aPageSize.Height()); + + bNeedToChange = maJobSetup.ImplGetConstData().GetPaperFormat() != PAPER_USER && + maJobSetup.ImplGetConstData().GetPaperFormat() != aPaper; + } + + if(bNeedToChange) + { + JobSetup aJobSetup = maJobSetup; + ImplJobSetup& rData = aJobSetup.ImplGetData(); + rData.SetPaperFormat( PAPER_USER ); + rData.SetPaperWidth( aPageSize.Width() ); + rData.SetPaperHeight( aPageSize.Height() ); + rData.SetOrientation( Orientation::Portrait ); + + if ( IsDisplayPrinter() ) + { + mbNewJobSetup = true; + maJobSetup = aJobSetup; + return true; + } + + ReleaseGraphics(); + ImplFindPaperFormatForUserSize( aJobSetup ); + + // Changing the paper size can also change the orientation! + if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData )) + { + ImplUpdateJobSetupPaper( aJobSetup ); + mbNewJobSetup = true; + maJobSetup = aJobSetup; + ImplUpdatePageData(); + ImplUpdateFontList(); + return true; + } + else + return false; + } + + return true; +} + +int Printer::GetPaperInfoCount() const +{ + if( ! mpInfoPrinter ) + return 0; + if( ! mpInfoPrinter->m_bPapersInit ) + mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() ); + return mpInfoPrinter->m_aPaperFormats.size(); +} + +OUString Printer::GetPaperName( Paper ePaper ) +{ + ImplSVData* pSVData = ImplGetSVData(); + if( pSVData->maPaperNames.empty() ) + { + // This array must (probably) match exactly the enum Paper in <i18nutil/paper.hxx> + static const int PaperIndex[] = + { + PAPER_A0, PAPER_A1, PAPER_A2, PAPER_A3, PAPER_A4, PAPER_A5, PAPER_B4_ISO, PAPER_B5_ISO, + PAPER_LETTER, PAPER_LEGAL, PAPER_TABLOID, PAPER_USER, PAPER_B6_ISO, PAPER_ENV_C4, PAPER_ENV_C5, + PAPER_ENV_C6, PAPER_ENV_C65, PAPER_ENV_DL, PAPER_SLIDE_DIA, PAPER_SCREEN_4_3, PAPER_C, PAPER_D, + PAPER_E, PAPER_EXECUTIVE, PAPER_FANFOLD_LEGAL_DE, PAPER_ENV_MONARCH, PAPER_ENV_PERSONAL, PAPER_ENV_9, + PAPER_ENV_10, PAPER_ENV_11, PAPER_ENV_12, PAPER_KAI16, PAPER_KAI32, PAPER_KAI32BIG, PAPER_B4_JIS, + PAPER_B5_JIS, PAPER_B6_JIS, PAPER_LEDGER, PAPER_STATEMENT, PAPER_QUARTO, PAPER_10x14, PAPER_ENV_14, + PAPER_ENV_C3, PAPER_ENV_ITALY, PAPER_FANFOLD_US, PAPER_FANFOLD_DE, PAPER_POSTCARD_JP, PAPER_9x11, + PAPER_10x11, PAPER_15x11, PAPER_ENV_INVITE, PAPER_A_PLUS, PAPER_B_PLUS, PAPER_LETTER_PLUS, PAPER_A4_PLUS, + PAPER_DOUBLEPOSTCARD_JP, PAPER_A6, PAPER_12x11, PAPER_A7, PAPER_A8, PAPER_A9, PAPER_A10, PAPER_B0_ISO, + PAPER_B1_ISO, PAPER_B2_ISO, PAPER_B3_ISO, PAPER_B7_ISO, PAPER_B8_ISO, PAPER_B9_ISO, PAPER_B10_ISO, + PAPER_ENV_C2, PAPER_ENV_C7, PAPER_ENV_C8, PAPER_ARCHA, PAPER_ARCHB, PAPER_ARCHC, PAPER_ARCHD, + PAPER_ARCHE, PAPER_SCREEN_16_9, PAPER_SCREEN_16_10, PAPER_16K_195x270, PAPER_16K_197x273, + PAPER_WIDESCREEN, PAPER_ONSCREENSHOW_4_3, PAPER_ONSCREENSHOW_16_9, PAPER_ONSCREENSHOW_16_10 + }; + static_assert(SAL_N_ELEMENTS(PaperIndex) == SAL_N_ELEMENTS(RID_STR_PAPERNAMES), "localized paper name count wrong"); + for (size_t i = 0; i < SAL_N_ELEMENTS(PaperIndex); ++i) + pSVData->maPaperNames[PaperIndex[i]] = VclResId(RID_STR_PAPERNAMES[i]); + } + + std::unordered_map<int,OUString>::const_iterator it = pSVData->maPaperNames.find( static_cast<int>(ePaper) ); + return (it != pSVData->maPaperNames.end()) ? it->second : OUString(); +} + +const PaperInfo& Printer::GetPaperInfo( int nPaper ) const +{ + if( ! mpInfoPrinter ) + return ImplGetEmptyPaper(); + if( ! mpInfoPrinter->m_bPapersInit ) + mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() ); + if( mpInfoPrinter->m_aPaperFormats.empty() || nPaper < 0 || o3tl::make_unsigned(nPaper) >= mpInfoPrinter->m_aPaperFormats.size() ) + return ImplGetEmptyPaper(); + return mpInfoPrinter->m_aPaperFormats[nPaper]; +} + +Size Printer::GetPaperSize( int nPaper ) const +{ + PaperInfo aInfo = GetPaperInfo( nPaper ); + return PixelToLogic( Size( aInfo.getWidth(), aInfo.getHeight() ) ); +} + +void Printer::SetDuplexMode( DuplexMode eDuplex ) +{ + if ( mbInPrintPage ) + return; + + if ( maJobSetup.ImplGetConstData().GetDuplexMode() == eDuplex ) + return; + + JobSetup aJobSetup = maJobSetup; + ImplJobSetup& rData = aJobSetup.ImplGetData(); + + rData.SetDuplexMode( eDuplex ); + + if ( IsDisplayPrinter() ) + { + mbNewJobSetup = true; + maJobSetup = aJobSetup; + return; + } + + ReleaseGraphics(); + if ( mpInfoPrinter->SetData( JobSetFlags::DUPLEXMODE, &rData ) ) + { + ImplUpdateJobSetupPaper( aJobSetup ); + mbNewJobSetup = true; + maJobSetup = aJobSetup; + ImplUpdatePageData(); + ImplUpdateFontList(); + } +} + +DuplexMode Printer::GetDuplexMode() const +{ + return maJobSetup.ImplGetConstData().GetDuplexMode(); +} + +Paper Printer::GetPaper() const +{ + return maJobSetup.ImplGetConstData().GetPaperFormat(); +} + +Size Printer::GetSizeOfPaper() const +{ + return Size(maJobSetup.ImplGetConstData().GetPaperWidth(), maJobSetup.ImplGetConstData().GetPaperHeight()); +} + +sal_uInt16 Printer::GetPaperBinCount() const +{ + if ( IsDisplayPrinter() ) + return 0; + + return mpInfoPrinter->GetPaperBinCount( &maJobSetup.ImplGetConstData() ); +} + +OUString Printer::GetPaperBinName( sal_uInt16 nPaperBin ) const +{ + if ( IsDisplayPrinter() ) + return OUString(); + + if ( nPaperBin < GetPaperBinCount() ) + return mpInfoPrinter->GetPaperBinName( &maJobSetup.ImplGetConstData(), nPaperBin ); + else + return OUString(); +} + +void Printer::SetCopyCount( sal_uInt16 nCopy, bool bCollate ) +{ + mnCopyCount = nCopy; + mbCollateCopy = bCollate; +} + +ErrCode Printer::ImplSalPrinterErrorCodeToVCL( SalPrinterError nError ) +{ + ErrCode nVCLError; + switch ( nError ) + { + case SalPrinterError::NONE: + nVCLError = ERRCODE_NONE; + break; + case SalPrinterError::Abort: + nVCLError = PRINTER_ABORT; + break; + default: + nVCLError = PRINTER_GENERALERROR; + break; + } + + return nVCLError; +} + +void Printer::EndJob() +{ + if ( !IsJobActive() ) + return; + + SAL_WARN_IF( mbInPrintPage, "vcl.gdi", "Printer::EndJob() - StartPage() without EndPage() called" ); + + mbJobActive = false; + + if ( mpPrinter ) + { + ReleaseGraphics(); + + mbPrinting = false; + + mbDevOutput = false; + mpPrinter->EndJob(); + mpPrinter.reset(); + } +} + +void Printer::ImplStartPage() +{ + if ( !IsJobActive() ) + return; + + if ( !mpPrinter ) + return; + + SalGraphics* pGraphics = mpPrinter->StartPage( &maJobSetup.ImplGetData(), + mbNewJobSetup ); + if ( pGraphics ) + { + ReleaseGraphics(); + mpJobGraphics = pGraphics; + } + mbDevOutput = true; + + // PrintJob not aborted ??? + if ( IsJobActive() ) + mbInPrintPage = true; +} + +void Printer::ImplEndPage() +{ + if ( !IsJobActive() ) + return; + + mbInPrintPage = false; + + if ( mpPrinter ) + { + ReleaseGraphics(); + mpPrinter->EndPage(); + mbDevOutput = false; + + mpJobGraphics = nullptr; + mbNewJobSetup = false; + } +} + +void Printer::updatePrinters() +{ + ImplSVData* pSVData = ImplGetSVData(); + ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList.get(); + + if ( !pPrnList ) + return; + + std::unique_ptr<ImplPrnQueueList> pNewList(new ImplPrnQueueList); + pSVData->mpDefInst->GetPrinterQueueInfo( pNewList.get() ); + + bool bChanged = pPrnList->m_aQueueInfos.size() != pNewList->m_aQueueInfos.size(); + for( decltype(pPrnList->m_aQueueInfos)::size_type i = 0; ! bChanged && i < pPrnList->m_aQueueInfos.size(); i++ ) + { + ImplPrnQueueData& rInfo = pPrnList->m_aQueueInfos[i]; + ImplPrnQueueData& rNewInfo = pNewList->m_aQueueInfos[i]; + if( ! rInfo.mpSalQueueInfo || ! rNewInfo.mpSalQueueInfo || // sanity check + rInfo.mpSalQueueInfo->maPrinterName != rNewInfo.mpSalQueueInfo->maPrinterName ) + { + bChanged = true; + } + } + if( !bChanged ) + return; + + ImplDeletePrnQueueList(); + pSVData->maGDIData.mpPrinterQueueList = std::move(pNewList); + + Application* pApp = GetpApp(); + if( pApp ) + { + DataChangedEvent aDCEvt( DataChangedEventType::PRINTER ); + Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt); + Application::NotifyAllWindows( aDCEvt ); + } +} + +bool Printer::UsePolyPolygonForComplexGradient() +{ + return true; +} + +void Printer::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly ) +{ + const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); + + Push( vcl::PushFlags::CLIPREGION ); + IntersectClipRegion(vcl::Region(rPolyPoly)); + DrawGradient( aBoundRect, rGradient ); + Pop(); +} + +void Printer::SetFontOrientation( LogicalFontInstance* const pFontEntry ) const +{ + pFontEntry->mnOrientation = pFontEntry->mxFontMetric->GetOrientation(); +} + +vcl::Region Printer::ClipToDeviceBounds(vcl::Region aRegion) const +{ + return aRegion; +} + +Bitmap Printer::GetBitmap( const Point& rSrcPt, const Size& rSize ) const +{ + SAL_WARN("vcl.gdi", "GetBitmap(): This should never be called on by a Printer instance"); + + return OutputDevice::GetBitmap( rSrcPt, rSize ); +} + +css::awt::DeviceInfo Printer::GetDeviceInfo() const +{ + Size aDevSz = GetPaperSizePixel(); + css::awt::DeviceInfo aInfo = GetCommonDeviceInfo(aDevSz); + Size aOutSz = GetOutputSizePixel(); + Point aOffset = GetPageOffset(); + aInfo.LeftInset = aOffset.X(); + aInfo.TopInset = aOffset.Y(); + aInfo.RightInset = aDevSz.Width() - aOutSz.Width() - aOffset.X(); + aInfo.BottomInset = aDevSz.Height() - aOutSz.Height() - aOffset.Y(); + aInfo.Capabilities = 0; + + return aInfo; +} + +void Printer::SetWaveLineColors(Color const& rColor, tools::Long) +{ + if (mbLineColor || mbInitLineColor) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + + mpGraphics->SetFillColor(rColor); + mbInitFillColor = true; +} + +Size Printer::GetWaveLineSize(tools::Long nLineWidth) const +{ + // FIXME - do we have a bug here? If the linewidth is 0, then we will return + // Size(0, 0) - is this correct? + return Size(nLineWidth, ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY); +} + +void Printer::SetSystemTextColor(SystemTextColorFlags, bool) +{ + SetTextColor(COL_BLACK); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/gdi/print2.cxx b/vcl/source/gdi/print2.cxx new file mode 100644 index 0000000000..85b39c683d --- /dev/null +++ b/vcl/source/gdi/print2.cxx @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/log.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/print.hxx> +#include <vcl/printer/Options.hxx> + +#include <utility> +#include <vector> + +void Printer::DrawGradientEx( OutputDevice* pOut, const tools::Rectangle& rRect, const Gradient& rGradient ) +{ + const vcl::printer::Options& rPrinterOptions = GetPrinterOptions(); + + if( rPrinterOptions.IsReduceGradients() ) + { + if( vcl::printer::GradientMode::Stripes == rPrinterOptions.GetReducedGradientMode() ) + { + if( !rGradient.GetSteps() || ( rGradient.GetSteps() > rPrinterOptions.GetReducedGradientStepCount() ) ) + { + Gradient aNewGradient( rGradient ); + + aNewGradient.SetSteps( rPrinterOptions.GetReducedGradientStepCount() ); + pOut->DrawGradient( rRect, aNewGradient ); + } + else + pOut->DrawGradient( rRect, rGradient ); + } + else + { + const Color& rStartColor = rGradient.GetStartColor(); + const Color& rEndColor = rGradient.GetEndColor(); + const tools::Long nR = ( ( static_cast<tools::Long>(rStartColor.GetRed()) * rGradient.GetStartIntensity() ) / 100 + + ( static_cast<tools::Long>(rEndColor.GetRed()) * rGradient.GetEndIntensity() ) / 100 ) >> 1; + const tools::Long nG = ( ( static_cast<tools::Long>(rStartColor.GetGreen()) * rGradient.GetStartIntensity() ) / 100 + + ( static_cast<tools::Long>(rEndColor.GetGreen()) * rGradient.GetEndIntensity() ) / 100 ) >> 1; + const tools::Long nB = ( ( static_cast<tools::Long>(rStartColor.GetBlue()) * rGradient.GetStartIntensity() ) / 100 + + ( static_cast<tools::Long>(rEndColor.GetBlue()) * rGradient.GetEndIntensity() ) / 100 ) >> 1; + const Color aColor( static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG), static_cast<sal_uInt8>(nB) ); + + pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); + pOut->SetLineColor( aColor ); + pOut->SetFillColor( aColor ); + pOut->DrawRect( rRect ); + pOut->Pop(); + } + } + else + pOut->DrawGradient( rRect, rGradient ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/print3.cxx b/vcl/source/gdi/print3.cxx new file mode 100644 index 0000000000..1ebab3b481 --- /dev/null +++ b/vcl/source/gdi/print3.cxx @@ -0,0 +1,2156 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <o3tl/safeint.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> + +#include <utility> +#include <vcl/metaact.hxx> +#include <vcl/print.hxx> +#include <vcl/printer/Options.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <configsettings.hxx> +#include <printdlg.hxx> +#include <salinst.hxx> +#include <salprn.hxx> +#include <strings.hrc> +#include <svdata.hxx> + +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/ui/dialogs/FilePicker.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/view/DuplexMode.hpp> + +#include <unordered_map> +#include <unordered_set> + +using namespace vcl; + +namespace { + +class ImplPageCache +{ + struct CacheEntry + { + GDIMetaFile aPage; + PrinterController::PageSize aSize; + }; + + std::vector< CacheEntry > maPages; + std::vector< sal_Int32 > maPageNumbers; + std::vector< sal_Int32 > maCacheRanking; + + static const sal_Int32 nCacheSize = 6; + + void updateRanking( sal_Int32 nLastHit ) + { + if( maCacheRanking[0] != nLastHit ) + { + for( sal_Int32 i = nCacheSize-1; i > 0; i-- ) + maCacheRanking[i] = maCacheRanking[i-1]; + maCacheRanking[0] = nLastHit; + } + } + +public: + ImplPageCache() + : maPages( nCacheSize ) + , maPageNumbers( nCacheSize, -1 ) + , maCacheRanking( nCacheSize ) + { + for( sal_Int32 i = 0; i < nCacheSize; i++ ) + maCacheRanking[i] = nCacheSize - i - 1; + } + + // caution: does not ensure uniqueness + void insert( sal_Int32 i_nPageNo, const GDIMetaFile& i_rPage, const PrinterController::PageSize& i_rSize ) + { + sal_Int32 nReplacePage = maCacheRanking.back(); + maPages[ nReplacePage ].aPage = i_rPage; + maPages[ nReplacePage ].aSize = i_rSize; + maPageNumbers[ nReplacePage ] = i_nPageNo; + // cache insertion means in our case, the page was just queried + // so update the ranking + updateRanking( nReplacePage ); + } + + // caution: bad algorithm; should there ever be reason to increase the cache size beyond 6 + // this needs to be urgently rewritten. However do NOT increase the cache size lightly, + // whole pages can be rather memory intensive + bool get( sal_Int32 i_nPageNo, GDIMetaFile& o_rPageFile, PrinterController::PageSize& o_rSize ) + { + for( sal_Int32 i = 0; i < nCacheSize; ++i ) + { + if( maPageNumbers[i] == i_nPageNo ) + { + updateRanking( i ); + o_rPageFile = maPages[i].aPage; + o_rSize = maPages[i].aSize; + return true; + } + } + return false; + } + + void invalidate() + { + for( sal_Int32 i = 0; i < nCacheSize; ++i ) + { + maPageNumbers[i] = -1; + maPages[i].aPage.Clear(); + maCacheRanking[i] = nCacheSize - i - 1; + } + } +}; + +} + +class vcl::ImplPrinterControllerData +{ +public: + struct ControlDependency + { + OUString maDependsOnName; + sal_Int32 mnDependsOnEntry; + + ControlDependency() : mnDependsOnEntry( -1 ) {} + }; + + typedef std::unordered_map< OUString, size_t > PropertyToIndexMap; + typedef std::unordered_map< OUString, ControlDependency > ControlDependencyMap; + typedef std::unordered_map< OUString, css::uno::Sequence< sal_Bool > > ChoiceDisableMap; + + VclPtr< Printer > mxPrinter; + weld::Window* mpWindow; + css::uno::Sequence< css::beans::PropertyValue > maUIOptions; + std::vector< css::beans::PropertyValue > maUIProperties; + std::vector< bool > maUIPropertyEnabled; + PropertyToIndexMap maPropertyToIndex; + ControlDependencyMap maControlDependencies; + ChoiceDisableMap maChoiceDisableMap; + bool mbFirstPage; + bool mbLastPage; + bool mbReversePageOrder; + bool mbPapersizeFromSetup; + bool mbPapersizeFromUser; + bool mbOrientationFromUser; + bool mbPrinterModified; + css::view::PrintableState meJobState; + + vcl::PrinterController::MultiPageSetup maMultiPage; + + std::shared_ptr<vcl::PrintProgressDialog> mxProgress; + + ImplPageCache maPageCache; + + // set by user through printer properties subdialog of printer settings dialog + Size maDefaultPageSize; + // set by user through print dialog + Size maUserPageSize; + // set by user through print dialog + Orientation meUserOrientation; + // set by user through printer properties subdialog of printer settings dialog + sal_Int32 mnDefaultPaperBin; + // Set by user through printer properties subdialog of print dialog. + // Overrides application-set tray for a page. + sal_Int32 mnFixedPaperBin; + + // N.B. Apparently we have three levels of paper tray settings + // (latter overrides former): + // 1. default tray + // 2. tray set for a concrete page by an application, e.g., writer + // allows setting a printer tray (for the default printer) for a + // page style. This setting can be overridden by user by selecting + // "Use only paper tray from printer preferences" on the Options + // page in the print dialog, in which case the default tray is + // used for all pages. + // 3. tray set in printer properties the printer dialog + // I'm not quite sure why 1. and 3. are distinct, but the commit + // history suggests this is intentional... + + ImplPrinterControllerData() : + mpWindow( nullptr ), + mbFirstPage( true ), + mbLastPage( false ), + mbReversePageOrder( false ), + mbPapersizeFromSetup( false ), + mbPapersizeFromUser( false ), + mbOrientationFromUser( false ), + mbPrinterModified( false ), + meJobState( css::view::PrintableState_JOB_STARTED ), + meUserOrientation( Orientation::Portrait ), + mnDefaultPaperBin( -1 ), + mnFixedPaperBin( -1 ) + {} + + ~ImplPrinterControllerData() + { + if (mxProgress) + { + mxProgress->response(RET_CANCEL); + mxProgress.reset(); + } + } + + Size getRealPaperSize( const Size& i_rPageSize, bool bNoNUP ) const + { + Size size; + if ( mbPapersizeFromUser ) + size = maUserPageSize; + else if( mbPapersizeFromSetup ) + size = maDefaultPageSize; + else if( maMultiPage.nRows * maMultiPage.nColumns > 1 && ! bNoNUP ) + size = maMultiPage.aPaperSize; + else + size = i_rPageSize; + if(mbOrientationFromUser) + { + if ( (meUserOrientation == Orientation::Portrait && size.Width() > size.Height()) || + (meUserOrientation == Orientation::Landscape && size.Width() < size.Height()) ) + { + // coverity[swapped_arguments : FALSE] - this is in the correct order + size = Size( size.Height(), size.Width() ); + } + } + return size; + } + PrinterController::PageSize modifyJobSetup( const css::uno::Sequence< css::beans::PropertyValue >& i_rProps ); + void resetPaperToLastConfigured(); +}; + +PrinterController::PrinterController(const VclPtr<Printer>& i_xPrinter, weld::Window* i_pWindow) + : mpImplData( new ImplPrinterControllerData ) +{ + mpImplData->mxPrinter = i_xPrinter; + mpImplData->mpWindow = i_pWindow; +} + +static OUString queryFile( Printer const * pPrinter ) +{ + OUString aResult; + + css::uno::Reference< css::uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + css::uno::Reference< css::ui::dialogs::XFilePicker3 > xFilePicker = css::ui::dialogs::FilePicker::createWithMode(xContext, css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION); + + try + { +#ifdef UNX + // add PostScript and PDF + bool bPS = true, bPDF = true; + if( pPrinter ) + { + if( pPrinter->GetCapabilities( PrinterCapType::PDF ) ) + bPS = false; + else + bPDF = false; + } + if( bPS ) + xFilePicker->appendFilter( "PostScript", "*.ps" ); + if( bPDF ) + xFilePicker->appendFilter( "Portable Document Format", "*.pdf" ); +#elif defined _WIN32 + (void)pPrinter; + xFilePicker->appendFilter( "*.PRN", "*.prn" ); +#endif + // add arbitrary files + xFilePicker->appendFilter(VclResId(SV_STDTEXT_ALLFILETYPES), "*.*"); + } + catch (const css::lang::IllegalArgumentException&) + { + TOOLS_WARN_EXCEPTION( "vcl.gdi", "caught IllegalArgumentException when registering filter" ); + } + + if( xFilePicker->execute() == css::ui::dialogs::ExecutableDialogResults::OK ) + { + css::uno::Sequence< OUString > aPathSeq( xFilePicker->getSelectedFiles() ); + INetURLObject aObj( aPathSeq[0] ); + aResult = aObj.PathToFileName(); + } + return aResult; +} + +namespace { + +struct PrintJobAsync +{ + std::shared_ptr<PrinterController> mxController; + JobSetup maInitSetup; + + PrintJobAsync(std::shared_ptr<PrinterController> i_xController, + const JobSetup& i_rInitSetup) + : mxController(std::move( i_xController )), maInitSetup( i_rInitSetup ) + {} + + DECL_LINK( ExecJob, void*, void ); +}; + +} + +IMPL_LINK_NOARG(PrintJobAsync, ExecJob, void*, void) +{ + Printer::ImplPrintJob(mxController, maInitSetup); + + // clean up, do not access members after this + delete this; +} + +void Printer::PrintJob(const std::shared_ptr<PrinterController>& i_xController, + const JobSetup& i_rInitSetup) +{ + bool bSynchronous = false; + css::beans::PropertyValue* pVal = i_xController->getValue( "Wait" ); + if( pVal ) + pVal->Value >>= bSynchronous; + + if( bSynchronous ) + ImplPrintJob(i_xController, i_rInitSetup); + else + { + PrintJobAsync* pAsync = new PrintJobAsync(i_xController, i_rInitSetup); + Application::PostUserEvent( LINK( pAsync, PrintJobAsync, ExecJob ) ); + } +} + +bool Printer::PreparePrintJob(std::shared_ptr<PrinterController> xController, + const JobSetup& i_rInitSetup) +{ + // check if there is a default printer; if not, show an error box (if appropriate) + if( GetDefaultPrinterName().isEmpty() ) + { + if (xController->isShowDialogs()) + { + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(xController->getWindow(), "vcl/ui/errornoprinterdialog.ui")); + std::unique_ptr<weld::MessageDialog> xBox(xBuilder->weld_message_dialog("ErrorNoPrinterDialog")); + xBox->run(); + } + xController->setValue( "IsDirect", + css::uno::Any( false ) ); + } + + // setup printer + + // #i114306# changed behavior back from persistence + // if no specific printer is already set, create the default printer + if (!xController->getPrinter()) + { + OUString aPrinterName( i_rInitSetup.GetPrinterName() ); + VclPtrInstance<Printer> xPrinter( aPrinterName ); + xPrinter->SetJobSetup(i_rInitSetup); + xController->setPrinter(xPrinter); + xController->setPapersizeFromSetup(xPrinter->GetPrinterSettingsPreferred()); + } + + // reset last page property + xController->setLastPage(false); + + // update "PageRange" property inferring from other properties: + // case 1: "Pages" set from UNO API -> + // setup "Print Selection" and insert "PageRange" attribute + // case 2: "All pages" is selected + // update "Page range" attribute to have a sensible default, + // but leave "All" as selected + + // "Pages" attribute from API is now equivalent to "PageRange" + // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1 + // Argh ! That sure needs cleaning up + css::beans::PropertyValue* pContentVal = xController->getValue("PrintRange"); + if( ! pContentVal ) + pContentVal = xController->getValue("PrintContent"); + + // case 1: UNO API has set "Pages" + css::beans::PropertyValue* pPagesVal = xController->getValue("Pages"); + if( pPagesVal ) + { + OUString aPagesVal; + pPagesVal->Value >>= aPagesVal; + if( !aPagesVal.isEmpty() ) + { + // "Pages" attribute from API is now equivalent to "PageRange" + // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1 + // Argh ! That sure needs cleaning up + if( pContentVal ) + { + pContentVal->Value <<= sal_Int32( 1 ); + xController->setValue("PageRange", pPagesVal->Value); + } + } + } + // case 2: is "All" selected ? + else if( pContentVal ) + { + sal_Int32 nContent = -1; + if( pContentVal->Value >>= nContent ) + { + if( nContent == 0 ) + { + // do not overwrite PageRange if it is already set + css::beans::PropertyValue* pRangeVal = xController->getValue("PageRange"); + OUString aRange; + if( pRangeVal ) + pRangeVal->Value >>= aRange; + if( aRange.isEmpty() ) + { + sal_Int32 nPages = xController->getPageCount(); + if( nPages > 0 ) + { + OUStringBuffer aBuf( 32 ); + aBuf.append( "1" ); + if( nPages > 1 ) + { + aBuf.append( "-" + OUString::number( nPages ) ); + } + xController->setValue("PageRange", css::uno::Any(aBuf.makeStringAndClear())); + } + } + } + } + } + + css::beans::PropertyValue* pReverseVal = xController->getValue("PrintReverse"); + if( pReverseVal ) + { + bool bReverse = false; + pReverseVal->Value >>= bReverse; + xController->setReversePrint( bReverse ); + } + + css::beans::PropertyValue* pPapersizeFromSetupVal = xController->getValue("PapersizeFromSetup"); + if( pPapersizeFromSetupVal ) + { + bool bPapersizeFromSetup = false; + pPapersizeFromSetupVal->Value >>= bPapersizeFromSetup; + xController->setPapersizeFromSetup(bPapersizeFromSetup); + } + + // setup NUp printing from properties + sal_Int32 nRows = xController->getIntProperty("NUpRows", 1); + sal_Int32 nCols = xController->getIntProperty("NUpColumns", 1); + if( nRows > 1 || nCols > 1 ) + { + PrinterController::MultiPageSetup aMPS; + aMPS.nRows = std::max<sal_Int32>(nRows, 1); + aMPS.nColumns = std::max<sal_Int32>(nCols, 1); + sal_Int32 nValue = xController->getIntProperty("NUpPageMarginLeft", aMPS.nLeftMargin); + if( nValue >= 0 ) + aMPS.nLeftMargin = nValue; + nValue = xController->getIntProperty("NUpPageMarginRight", aMPS.nRightMargin); + if( nValue >= 0 ) + aMPS.nRightMargin = nValue; + nValue = xController->getIntProperty( "NUpPageMarginTop", aMPS.nTopMargin ); + if( nValue >= 0 ) + aMPS.nTopMargin = nValue; + nValue = xController->getIntProperty( "NUpPageMarginBottom", aMPS.nBottomMargin ); + if( nValue >= 0 ) + aMPS.nBottomMargin = nValue; + nValue = xController->getIntProperty( "NUpHorizontalSpacing", aMPS.nHorizontalSpacing ); + if( nValue >= 0 ) + aMPS.nHorizontalSpacing = nValue; + nValue = xController->getIntProperty( "NUpVerticalSpacing", aMPS.nVerticalSpacing ); + if( nValue >= 0 ) + aMPS.nVerticalSpacing = nValue; + aMPS.bDrawBorder = xController->getBoolProperty( "NUpDrawBorder", aMPS.bDrawBorder ); + aMPS.nOrder = static_cast<NupOrderType>(xController->getIntProperty( "NUpSubPageOrder", static_cast<sal_Int32>(aMPS.nOrder) )); + aMPS.aPaperSize = xController->getPrinter()->PixelToLogic( xController->getPrinter()->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) ); + css::beans::PropertyValue* pPgSizeVal = xController->getValue( "NUpPaperSize" ); + css::awt::Size aSizeVal; + if( pPgSizeVal && (pPgSizeVal->Value >>= aSizeVal) ) + { + aMPS.aPaperSize.setWidth( aSizeVal.Width ); + aMPS.aPaperSize.setHeight( aSizeVal.Height ); + } + + xController->setMultipage( aMPS ); + } + + // in direct print case check whether there is anything to print. + // if not, show an errorbox (if appropriate) + if( xController->isShowDialogs() && xController->isDirectPrint() ) + { + if( xController->getFilteredPageCount() == 0 ) + { + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(xController->getWindow(), "vcl/ui/errornocontentdialog.ui")); + std::unique_ptr<weld::MessageDialog> xBox(xBuilder->weld_message_dialog("ErrorNoContentDialog")); + xBox->run(); + return false; + } + } + + // check if the printer brings up its own dialog + // in that case leave the work to that dialog + if( ! xController->getPrinter()->GetCapabilities( PrinterCapType::ExternalDialog ) && + ! xController->isDirectPrint() && + xController->isShowDialogs() + ) + { + try + { + PrintDialog aDlg(xController->getWindow(), xController); + if (!aDlg.run()) + { + xController->abortJob(); + return false; + } + if (aDlg.isPrintToFile()) + { + OUString aFile = queryFile( xController->getPrinter().get() ); + if( aFile.isEmpty() ) + { + xController->abortJob(); + return false; + } + xController->setValue( "LocalFileName", + css::uno::Any( aFile ) ); + } + else if (aDlg.isSingleJobs()) + { + xController->getPrinter()->SetSinglePrintJobs(true); + } + } + catch (const std::bad_alloc&) + { + } + } +#ifdef MACOSX + else + { + // The PrintDialog updates the printer list in its constructor so do + // the same for printers that bring up their own dialog since. Not + // sure if this is needed or not on Windows or X11, so limit only to + // macOS for now. + Printer::updatePrinters(); + } +#endif + + xController->pushPropertiesToPrinter(); + return true; +} + +bool Printer::ExecutePrintJob(const std::shared_ptr<PrinterController>& xController) +{ + OUString aJobName; + css::beans::PropertyValue* pJobNameVal = xController->getValue( "JobName" ); + if( pJobNameVal ) + pJobNameVal->Value >>= aJobName; + + return xController->getPrinter()->StartJob( aJobName, xController ); +} + +void Printer::FinishPrintJob(const std::shared_ptr<PrinterController>& xController) +{ + xController->resetPaperToLastConfigured(); + xController->jobFinished( xController->getJobState() ); +} + +void Printer::ImplPrintJob(const std::shared_ptr<PrinterController>& xController, + const JobSetup& i_rInitSetup) +{ + if (PreparePrintJob(xController, i_rInitSetup)) + { + ExecutePrintJob(xController); + } + FinishPrintJob(xController); +} + +bool Printer::StartJob( const OUString& i_rJobName, std::shared_ptr<vcl::PrinterController> const & i_xController) +{ + mnError = ERRCODE_NONE; + + if ( IsDisplayPrinter() ) + return false; + + if ( IsJobActive() || IsPrinting() ) + return false; + + sal_uInt32 nCopies = mnCopyCount; + bool bCollateCopy = mbCollateCopy; + bool bUserCopy = false; + + if ( nCopies > 1 ) + { + const sal_uInt32 nDevCopy = GetCapabilities( bCollateCopy + ? PrinterCapType::CollateCopies + : PrinterCapType::Copies ); + + // need to do copies by hand ? + if ( nCopies > nDevCopy ) + { + bUserCopy = true; + nCopies = 1; + bCollateCopy = false; + } + } + else + bCollateCopy = false; + + ImplSVData* pSVData = ImplGetSVData(); + mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter ); + + if (!mpPrinter) + return false; + + bool bSinglePrintJobs = i_xController->getPrinter()->IsSinglePrintJobs(); + + css::beans::PropertyValue* pFileValue = i_xController->getValue("LocalFileName"); + if( pFileValue ) + { + OUString aFile; + pFileValue->Value >>= aFile; + if( !aFile.isEmpty() ) + { + mbPrintFile = true; + maPrintFile = aFile; + bSinglePrintJobs = false; + } + } + + OUString* pPrintFile = nullptr; + if ( mbPrintFile ) + pPrintFile = &maPrintFile; + mpPrinterOptions->ReadFromConfig( mbPrintFile ); + + mbPrinting = true; + if( GetCapabilities( PrinterCapType::UsePullModel ) ) + { + mbJobActive = true; + // SAL layer does all necessary page printing + // and also handles showing a dialog + // that also means it must call jobStarted when the dialog is finished + // it also must set the JobState of the Controller + if( mpPrinter->StartJob( pPrintFile, + i_rJobName, + Application::GetDisplayName(), + &maJobSetup.ImplGetData(), + *i_xController) ) + { + EndJob(); + } + else + { + mnError = ImplSalPrinterErrorCodeToVCL(mpPrinter->GetErrorCode()); + if ( !mnError ) + mnError = PRINTER_GENERALERROR; + mbPrinting = false; + mpPrinter.reset(); + mbJobActive = false; + + GDIMetaFile aDummyFile; + i_xController->setLastPage(true); + i_xController->getFilteredPageFile(0, aDummyFile); + + return false; + } + } + else + { + // possibly a dialog has been shown + // now the real job starts + i_xController->setJobState( css::view::PrintableState_JOB_STARTED ); + i_xController->jobStarted(); + + int nJobs = 1; + int nOuterRepeatCount = 1; + int nInnerRepeatCount = 1; + if( bUserCopy ) + { + if( mbCollateCopy ) + nOuterRepeatCount = mnCopyCount; + else + nInnerRepeatCount = mnCopyCount; + } + if( bSinglePrintJobs ) + { + nJobs = mnCopyCount; + nCopies = 1; + nOuterRepeatCount = nInnerRepeatCount = 1; + } + + for( int nJobIteration = 0; nJobIteration < nJobs; nJobIteration++ ) + { + bool bError = false; + if( mpPrinter->StartJob( pPrintFile, + i_rJobName, + Application::GetDisplayName(), + nCopies, + bCollateCopy, + i_xController->isDirectPrint(), + &maJobSetup.ImplGetData() ) ) + { + bool bAborted = false; + mbJobActive = true; + i_xController->createProgressDialog(); + const int nPages = i_xController->getFilteredPageCount(); + // abort job, if no pages will be printed. + if ( nPages == 0 ) + { + i_xController->abortJob(); + bAborted = true; + } + for( int nOuterIteration = 0; nOuterIteration < nOuterRepeatCount && ! bAborted; nOuterIteration++ ) + { + for( int nPage = 0; nPage < nPages && ! bAborted; nPage++ ) + { + for( int nInnerIteration = 0; nInnerIteration < nInnerRepeatCount && ! bAborted; nInnerIteration++ ) + { + if( nPage == nPages-1 && + nOuterIteration == nOuterRepeatCount-1 && + nInnerIteration == nInnerRepeatCount-1 && + nJobIteration == nJobs-1 ) + { + i_xController->setLastPage(true); + } + i_xController->printFilteredPage(nPage); + if (i_xController->isProgressCanceled()) + { + i_xController->abortJob(); + } + if (i_xController->getJobState() == + css::view::PrintableState_JOB_ABORTED) + { + bAborted = true; + } + } + } + // FIXME: duplex ? + } + EndJob(); + + if( nJobIteration < nJobs-1 ) + { + mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter ); + + if ( mpPrinter ) + mbPrinting = true; + else + bError = true; + } + } + else + bError = true; + + if( bError ) + { + mnError = mpPrinter ? ImplSalPrinterErrorCodeToVCL(mpPrinter->GetErrorCode()) : ERRCODE_NONE; + if ( !mnError ) + mnError = PRINTER_GENERALERROR; + i_xController->setJobState( mnError == PRINTER_ABORT + ? css::view::PrintableState_JOB_ABORTED + : css::view::PrintableState_JOB_FAILED ); + mbPrinting = false; + mpPrinter.reset(); + + return false; + } + } + + if (i_xController->getJobState() == css::view::PrintableState_JOB_STARTED) + i_xController->setJobState(css::view::PrintableState_JOB_SPOOLED); + } + + // make last used printer persistent for UI jobs + if (i_xController->isShowDialogs() && !i_xController->isDirectPrint()) + { + SettingsConfigItem* pItem = SettingsConfigItem::get(); + pItem->setValue( "PrintDialog", + "LastPrinterUsed", + GetName() + ); + } + + return true; +} + +PrinterController::~PrinterController() +{ +} + +css::view::PrintableState PrinterController::getJobState() const +{ + return mpImplData->meJobState; +} + +void PrinterController::setJobState( css::view::PrintableState i_eState ) +{ + mpImplData->meJobState = i_eState; +} + +const VclPtr<Printer>& PrinterController::getPrinter() const +{ + return mpImplData->mxPrinter; +} + +weld::Window* PrinterController::getWindow() const +{ + return mpImplData->mpWindow; +} + +void PrinterController::dialogsParentClosing() +{ + mpImplData->mpWindow = nullptr; + if (mpImplData->mxProgress) + { + // close the dialog without doing anything, just get rid of it + mpImplData->mxProgress->response(RET_OK); + mpImplData->mxProgress.reset(); + } +} + +void PrinterController::setPrinter( const VclPtr<Printer>& i_rPrinter ) +{ + VclPtr<Printer> xPrinter = mpImplData->mxPrinter; + + Size aPaperSize; // Save current paper size + Orientation eOrientation = Orientation::Portrait; // Save current paper orientation + bool bSavedSizeOrientation = false; + + // #tdf 126744 Transfer paper size and orientation settings to newly selected printer + if ( xPrinter ) + { + aPaperSize = xPrinter->GetPaperSize(); + eOrientation = xPrinter->GetOrientation(); + bSavedSizeOrientation = true; + } + + mpImplData->mxPrinter = i_rPrinter; + setValue( "Name", + css::uno::Any( i_rPrinter->GetName() ) ); + mpImplData->mnDefaultPaperBin = mpImplData->mxPrinter->GetPaperBin(); + mpImplData->mxPrinter->Push(); + mpImplData->mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM)); + mpImplData->maDefaultPageSize = mpImplData->mxPrinter->GetPaperSize(); + + if ( bSavedSizeOrientation ) + { + mpImplData->mxPrinter->SetPaperSizeUser(aPaperSize); + mpImplData->mxPrinter->SetOrientation(eOrientation); + } + + mpImplData->mbPapersizeFromUser = false; + mpImplData->mbOrientationFromUser = false; + mpImplData->mxPrinter->Pop(); + mpImplData->mnFixedPaperBin = -1; +} + +void PrinterController::resetPrinterOptions( bool i_bFileOutput ) +{ + vcl::printer::Options aOpt; + aOpt.ReadFromConfig( i_bFileOutput ); + mpImplData->mxPrinter->SetPrinterOptions( aOpt ); +} + +void PrinterController::setupPrinter( weld::Window* i_pParent ) +{ + bool bRet = false; + + // Important to hold printer alive while doing setup etc. + VclPtr< Printer > xPrinter = mpImplData->mxPrinter; + + if( !xPrinter ) + return; + + xPrinter->Push(); + xPrinter->SetMapMode(MapMode(MapUnit::Map100thMM)); + + // get current data + Size aPaperSize(xPrinter->GetPaperSize()); + Orientation eOrientation = xPrinter->GetOrientation(); + sal_uInt16 nPaperBin = xPrinter->GetPaperBin(); + + // reset paper size back to last configured size, not + // whatever happens to be the current page + // (but only if the printer config has changed, otherwise + // don't override printer page auto-detection - tdf#91362) + if (getPrinterModified() || getPapersizeFromSetup()) + { + resetPaperToLastConfigured(); + } + + // call driver setup + bRet = xPrinter->Setup( i_pParent, PrinterSetupMode::SingleJob ); + SAL_WARN_IF(xPrinter != mpImplData->mxPrinter, "vcl.gdi", + "Printer changed underneath us during setup"); + xPrinter = mpImplData->mxPrinter; + + Size aNewPaperSize(xPrinter->GetPaperSize()); + if (bRet) + { + bool bInvalidateCache = false; + setPapersizeFromSetup(xPrinter->GetPrinterSettingsPreferred()); + + // was papersize overridden ? if so we need to take action if we're + // configured to use the driver papersize + if (aNewPaperSize != mpImplData->maDefaultPageSize) + { + mpImplData->maDefaultPageSize = aNewPaperSize; + bInvalidateCache = getPapersizeFromSetup(); + } + + // was bin overridden ? if so we need to take action + sal_uInt16 nNewPaperBin = xPrinter->GetPaperBin(); + if (nNewPaperBin != nPaperBin) + { + mpImplData->mnFixedPaperBin = nNewPaperBin; + bInvalidateCache = true; + } + + if (bInvalidateCache) + { + mpImplData->maPageCache.invalidate(); + } + } + else + { + //restore to whatever it was before we entered this method + xPrinter->SetOrientation( eOrientation ); + if (aPaperSize != aNewPaperSize) + xPrinter->SetPaperSizeUser(aPaperSize); + } + xPrinter->Pop(); +} + +PrinterController::PageSize vcl::ImplPrinterControllerData::modifyJobSetup( const css::uno::Sequence< css::beans::PropertyValue >& i_rProps ) +{ + PrinterController::PageSize aPageSize; + aPageSize.aSize = mxPrinter->GetPaperSize(); + css::awt::Size aSetSize, aIsSize; + sal_Int32 nPaperBin = mnDefaultPaperBin; + for( const auto& rProp : i_rProps ) + { + if ( rProp.Name == "PreferredPageSize" ) + { + rProp.Value >>= aSetSize; + } + else if ( rProp.Name == "PageSize" ) + { + rProp.Value >>= aIsSize; + } + else if ( rProp.Name == "PageIncludesNonprintableArea" ) + { + bool bVal = false; + rProp.Value >>= bVal; + aPageSize.bFullPaper = bVal; + } + else if ( rProp.Name == "PrinterPaperTray" ) + { + sal_Int32 nBin = -1; + rProp.Value >>= nBin; + if( nBin >= 0 && o3tl::make_unsigned(nBin) < mxPrinter->GetPaperBinCount() ) + nPaperBin = nBin; + } + } + + Size aCurSize( mxPrinter->GetPaperSize() ); + if( aSetSize.Width && aSetSize.Height ) + { + Size aSetPaperSize( aSetSize.Width, aSetSize.Height ); + Size aRealPaperSize( getRealPaperSize( aSetPaperSize, true/*bNoNUP*/ ) ); + if( aRealPaperSize != aCurSize ) + aIsSize = aSetSize; + } + + if( aIsSize.Width && aIsSize.Height ) + { + aPageSize.aSize.setWidth( aIsSize.Width ); + aPageSize.aSize.setHeight( aIsSize.Height ); + + Size aRealPaperSize( getRealPaperSize( aPageSize.aSize, true/*bNoNUP*/ ) ); + if( aRealPaperSize != aCurSize ) + mxPrinter->SetPaperSizeUser( aRealPaperSize ); + } + + // paper bin set from properties in print dialog overrides + // application default for a page + if ( mnFixedPaperBin != -1 ) + nPaperBin = mnFixedPaperBin; + + if( nPaperBin != -1 && nPaperBin != mxPrinter->GetPaperBin() ) + mxPrinter->SetPaperBin( nPaperBin ); + + return aPageSize; +} + +//fdo#61886 + +//when printing is finished, set the paper size of the printer to either what +//the user explicitly set as the desired paper size, or fallback to whatever +//the printer had before printing started. That way it doesn't contain the last +//paper size of a multiple paper size using document when we are in our normal +//auto accept document paper size mode and end up overwriting the original +//paper size setting for file->printer_settings just by pressing "ok" in the +//print dialog +void vcl::ImplPrinterControllerData::resetPaperToLastConfigured() +{ + mxPrinter->Push(); + mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM)); + Size aCurSize(mxPrinter->GetPaperSize()); + if (aCurSize != maDefaultPageSize) + mxPrinter->SetPaperSizeUser(maDefaultPageSize); + mxPrinter->Pop(); +} + +int PrinterController::getPageCountProtected() const +{ + const MapMode aMapMode( MapUnit::Map100thMM ); + + mpImplData->mxPrinter->Push(); + mpImplData->mxPrinter->SetMapMode( aMapMode ); + int nPages = getPageCount(); + mpImplData->mxPrinter->Pop(); + return nPages; +} + +css::uno::Sequence< css::beans::PropertyValue > PrinterController::getPageParametersProtected( int i_nPage ) const +{ + const MapMode aMapMode( MapUnit::Map100thMM ); + + mpImplData->mxPrinter->Push(); + mpImplData->mxPrinter->SetMapMode( aMapMode ); + css::uno::Sequence< css::beans::PropertyValue > aResult( getPageParameters( i_nPage ) ); + mpImplData->mxPrinter->Pop(); + return aResult; +} + +PrinterController::PageSize PrinterController::getPageFile( int i_nUnfilteredPage, GDIMetaFile& o_rMtf, bool i_bMayUseCache ) +{ + // update progress if necessary + if( mpImplData->mxProgress ) + { + // do nothing if printing is canceled + if( mpImplData->mxProgress->isCanceled() ) + return PrinterController::PageSize(); + mpImplData->mxProgress->tick(); + Application::Reschedule( true ); + } + + if( i_bMayUseCache ) + { + PrinterController::PageSize aPageSize; + if( mpImplData->maPageCache.get( i_nUnfilteredPage, o_rMtf, aPageSize ) ) + { + return aPageSize; + } + } + else + mpImplData->maPageCache.invalidate(); + + o_rMtf.Clear(); + + // get page parameters + css::uno::Sequence< css::beans::PropertyValue > aPageParm( getPageParametersProtected( i_nUnfilteredPage ) ); + const MapMode aMapMode( MapUnit::Map100thMM ); + + mpImplData->mxPrinter->Push(); + mpImplData->mxPrinter->SetMapMode( aMapMode ); + + // modify job setup if necessary + PrinterController::PageSize aPageSize = mpImplData->modifyJobSetup( aPageParm ); + + o_rMtf.SetPrefSize( aPageSize.aSize ); + o_rMtf.SetPrefMapMode( aMapMode ); + + mpImplData->mxPrinter->EnableOutput( false ); + + o_rMtf.Record( mpImplData->mxPrinter.get() ); + + printPage( i_nUnfilteredPage ); + + o_rMtf.Stop(); + o_rMtf.WindStart(); + mpImplData->mxPrinter->Pop(); + + if( i_bMayUseCache ) + mpImplData->maPageCache.insert( i_nUnfilteredPage, o_rMtf, aPageSize ); + + // reset "FirstPage" property to false now we've gotten at least our first one + mpImplData->mbFirstPage = false; + + return aPageSize; +} + +static void appendSubPage( GDIMetaFile& o_rMtf, const tools::Rectangle& i_rClipRect, GDIMetaFile& io_rSubPage, bool i_bDrawBorder ) +{ + // intersect all clipregion actions with our clip rect + io_rSubPage.WindStart(); + io_rSubPage.Clip( i_rClipRect ); + + // save gstate + o_rMtf.AddAction( new MetaPushAction( PushFlags::ALL ) ); + + // clip to page rect + o_rMtf.AddAction( new MetaClipRegionAction( vcl::Region( i_rClipRect ), true ) ); + + // append the subpage + io_rSubPage.WindStart(); + io_rSubPage.Play( o_rMtf ); + + // restore gstate + o_rMtf.AddAction( new MetaPopAction() ); + + // draw a border + if( !i_bDrawBorder ) + return; + + // save gstate + o_rMtf.AddAction( new MetaPushAction( PushFlags::LINECOLOR | PushFlags::FILLCOLOR | PushFlags::CLIPREGION | PushFlags::MAPMODE ) ); + o_rMtf.AddAction( new MetaMapModeAction( MapMode( MapUnit::Map100thMM ) ) ); + + tools::Rectangle aBorderRect( i_rClipRect ); + o_rMtf.AddAction( new MetaLineColorAction( COL_BLACK, true ) ); + o_rMtf.AddAction( new MetaFillColorAction( COL_TRANSPARENT, false ) ); + o_rMtf.AddAction( new MetaRectAction( aBorderRect ) ); + + // restore gstate + o_rMtf.AddAction( new MetaPopAction() ); +} + +PrinterController::PageSize PrinterController::getFilteredPageFile( int i_nFilteredPage, GDIMetaFile& o_rMtf, bool i_bMayUseCache ) +{ + const MultiPageSetup& rMPS( mpImplData->maMultiPage ); + int nSubPages = rMPS.nRows * rMPS.nColumns; + if( nSubPages < 1 ) + nSubPages = 1; + + // reverse sheet order + if( mpImplData->mbReversePageOrder ) + { + int nDocPages = getFilteredPageCount(); + i_nFilteredPage = nDocPages - 1 - i_nFilteredPage; + } + + // there is no filtering to be done (and possibly the page size of the + // original page is to be set), when N-Up is "neutral" that is there is + // only one subpage and the margins are 0 + if( nSubPages == 1 && + rMPS.nLeftMargin == 0 && rMPS.nRightMargin == 0 && + rMPS.nTopMargin == 0 && rMPS.nBottomMargin == 0 ) + { + PrinterController::PageSize aPageSize = getPageFile( i_nFilteredPage, o_rMtf, i_bMayUseCache ); + if (mpImplData->meJobState != css::view::PrintableState_JOB_STARTED) + { // rhbz#657394: check that we are still printing... + return PrinterController::PageSize(); + } + Size aPaperSize = mpImplData->getRealPaperSize( aPageSize.aSize, true ); + mpImplData->mxPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) ); + mpImplData->mxPrinter->SetPaperSizeUser( aPaperSize ); + if( aPaperSize != aPageSize.aSize ) + { + // user overridden page size, center Metafile + o_rMtf.WindStart(); + tools::Long nDX = (aPaperSize.Width() - aPageSize.aSize.Width()) / 2; + tools::Long nDY = (aPaperSize.Height() - aPageSize.aSize.Height()) / 2; + o_rMtf.Move( nDX, nDY, mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() ); + o_rMtf.WindStart(); + o_rMtf.SetPrefSize( aPaperSize ); + aPageSize.aSize = aPaperSize; + } + return aPageSize; + } + + // set last page property really only on the very last page to be rendered + // that is on the last subpage of a NUp run + bool bIsLastPage = mpImplData->mbLastPage; + mpImplData->mbLastPage = false; + + Size aPaperSize( mpImplData->getRealPaperSize( mpImplData->maMultiPage.aPaperSize, false ) ); + + // multi page area: page size minus margins + one time spacing right and down + // the added spacing is so each subpage can be calculated including its spacing + Size aMPArea( aPaperSize ); + aMPArea.AdjustWidth( -(rMPS.nLeftMargin + rMPS.nRightMargin) ); + aMPArea.AdjustWidth(rMPS.nHorizontalSpacing ); + aMPArea.AdjustHeight( -(rMPS.nTopMargin + rMPS.nBottomMargin) ); + aMPArea.AdjustHeight(rMPS.nVerticalSpacing ); + + // determine offsets + tools::Long nAdvX = aMPArea.Width() / rMPS.nColumns; + tools::Long nAdvY = aMPArea.Height() / rMPS.nRows; + + // determine size of a "cell" subpage, leave a little space around pages + Size aSubPageSize( nAdvX - rMPS.nHorizontalSpacing, nAdvY - rMPS.nVerticalSpacing ); + + o_rMtf.Clear(); + o_rMtf.SetPrefSize( aPaperSize ); + o_rMtf.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) ); + o_rMtf.AddAction( new MetaMapModeAction( MapMode( MapUnit::Map100thMM ) ) ); + + int nDocPages = getPageCountProtected(); + if (mpImplData->meJobState != css::view::PrintableState_JOB_STARTED) + { // rhbz#657394: check that we are still printing... + return PrinterController::PageSize(); + } + for( int nSubPage = 0; nSubPage < nSubPages; nSubPage++ ) + { + // map current sub page to real page + int nPage = i_nFilteredPage * nSubPages + nSubPage; + if( nSubPage == nSubPages-1 || + nPage == nDocPages-1 ) + { + mpImplData->mbLastPage = bIsLastPage; + } + if( nPage >= 0 && nPage < nDocPages ) + { + GDIMetaFile aPageFile; + PrinterController::PageSize aPageSize = getPageFile( nPage, aPageFile, i_bMayUseCache ); + if( aPageSize.aSize.Width() && aPageSize.aSize.Height() ) + { + tools::Long nCellX = 0, nCellY = 0; + switch( rMPS.nOrder ) + { + case NupOrderType::LRTB: + nCellX = (nSubPage % rMPS.nColumns); + nCellY = (nSubPage / rMPS.nColumns); + break; + case NupOrderType::TBLR: + nCellX = (nSubPage / rMPS.nRows); + nCellY = (nSubPage % rMPS.nRows); + break; + case NupOrderType::RLTB: + nCellX = rMPS.nColumns - 1 - (nSubPage % rMPS.nColumns); + nCellY = (nSubPage / rMPS.nColumns); + break; + case NupOrderType::TBRL: + nCellX = rMPS.nColumns - 1 - (nSubPage / rMPS.nRows); + nCellY = (nSubPage % rMPS.nRows); + break; + } + // scale the metafile down to a sub page size + double fScaleX = double(aSubPageSize.Width())/double(aPageSize.aSize.Width()); + double fScaleY = double(aSubPageSize.Height())/double(aPageSize.aSize.Height()); + double fScale = std::min( fScaleX, fScaleY ); + aPageFile.Scale( fScale, fScale ); + aPageFile.WindStart(); + + // move the subpage so it is centered in its "cell" + tools::Long nOffX = (aSubPageSize.Width() - tools::Long(double(aPageSize.aSize.Width()) * fScale)) / 2; + tools::Long nOffY = (aSubPageSize.Height() - tools::Long(double(aPageSize.aSize.Height()) * fScale)) / 2; + tools::Long nX = rMPS.nLeftMargin + nOffX + nAdvX * nCellX; + tools::Long nY = rMPS.nTopMargin + nOffY + nAdvY * nCellY; + aPageFile.Move( nX, nY, mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() ); + aPageFile.WindStart(); + // calculate border rectangle + tools::Rectangle aSubPageRect( Point( nX, nY ), + Size( tools::Long(double(aPageSize.aSize.Width())*fScale), + tools::Long(double(aPageSize.aSize.Height())*fScale) ) ); + + // append subpage to page + appendSubPage( o_rMtf, aSubPageRect, aPageFile, rMPS.bDrawBorder ); + } + } + } + o_rMtf.WindStart(); + + // subsequent getPageFile calls have changed the paper, reset it to current value + mpImplData->mxPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) ); + mpImplData->mxPrinter->SetPaperSizeUser( aPaperSize ); + + return PrinterController::PageSize( aPaperSize, true ); +} + +int PrinterController::getFilteredPageCount() const +{ + int nDiv = mpImplData->maMultiPage.nRows * mpImplData->maMultiPage.nColumns; + if( nDiv < 1 ) + nDiv = 1; + return (getPageCountProtected() + (nDiv-1)) / nDiv; +} + +DrawModeFlags PrinterController::removeTransparencies( GDIMetaFile const & i_rIn, GDIMetaFile& o_rOut ) +{ + DrawModeFlags nRestoreDrawMode = mpImplData->mxPrinter->GetDrawMode(); + sal_Int32 nMaxBmpDPIX = mpImplData->mxPrinter->GetDPIX(); + sal_Int32 nMaxBmpDPIY = mpImplData->mxPrinter->GetDPIY(); + + const vcl::printer::Options& rPrinterOptions = mpImplData->mxPrinter->GetPrinterOptions(); + + static const sal_Int32 OPTIMAL_BMP_RESOLUTION = 300; + static const sal_Int32 NORMAL_BMP_RESOLUTION = 200; + + if( rPrinterOptions.IsReduceBitmaps() ) + { + // calculate maximum resolution for bitmap graphics + if( printer::BitmapMode::Optimal == rPrinterOptions.GetReducedBitmapMode() ) + { + nMaxBmpDPIX = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIX ); + nMaxBmpDPIY = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIY ); + } + else if( printer::BitmapMode::Normal == rPrinterOptions.GetReducedBitmapMode() ) + { + nMaxBmpDPIX = std::min( sal_Int32(NORMAL_BMP_RESOLUTION), nMaxBmpDPIX ); + nMaxBmpDPIY = std::min( sal_Int32(NORMAL_BMP_RESOLUTION), nMaxBmpDPIY ); + } + else + { + nMaxBmpDPIX = std::min( sal_Int32(rPrinterOptions.GetReducedBitmapResolution()), nMaxBmpDPIX ); + nMaxBmpDPIY = std::min( sal_Int32(rPrinterOptions.GetReducedBitmapResolution()), nMaxBmpDPIY ); + } + } + + // convert to greyscales + if( rPrinterOptions.IsConvertToGreyscales() ) + { + mpImplData->mxPrinter->SetDrawMode( mpImplData->mxPrinter->GetDrawMode() | + ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText | + DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) ); + } + + // disable transparency output + if( rPrinterOptions.IsReduceTransparency() && ( vcl::printer::TransparencyMode::NONE == rPrinterOptions.GetReducedTransparencyMode() ) ) + { + mpImplData->mxPrinter->SetDrawMode( mpImplData->mxPrinter->GetDrawMode() | DrawModeFlags::NoTransparency ); + } + + Color aBg( COL_TRANSPARENT ); // default: let RemoveTransparenciesFromMetaFile do its own background logic + if( mpImplData->maMultiPage.nRows * mpImplData->maMultiPage.nColumns > 1 ) + { + // in N-Up printing we have no "page" background operation + // we also have no way to determine the paper color + // so let's go for white, which will kill 99.9% of the real cases + aBg = COL_WHITE; + } + mpImplData->mxPrinter->RemoveTransparenciesFromMetaFile( i_rIn, o_rOut, nMaxBmpDPIX, nMaxBmpDPIY, + rPrinterOptions.IsReduceTransparency(), + rPrinterOptions.GetReducedTransparencyMode() == vcl::printer::TransparencyMode::Auto, + rPrinterOptions.IsReduceBitmaps() && rPrinterOptions.IsReducedBitmapIncludesTransparency(), + aBg + ); + return nRestoreDrawMode; +} + +void PrinterController::printFilteredPage( int i_nPage ) +{ + if( mpImplData->meJobState != css::view::PrintableState_JOB_STARTED ) + return; // rhbz#657394: check that we are still printing... + + GDIMetaFile aPageFile; + PrinterController::PageSize aPageSize = getFilteredPageFile( i_nPage, aPageFile ); + + if( mpImplData->mxProgress ) + { + // do nothing if printing is canceled + if( mpImplData->mxProgress->isCanceled() ) + { + setJobState( css::view::PrintableState_JOB_ABORTED ); + return; + } + } + + // in N-Up printing set the correct page size + mpImplData->mxPrinter->SetMapMode(MapMode(MapUnit::Map100thMM)); + // aPageSize was filtered through mpImplData->getRealPaperSize already by getFilteredPageFile() + mpImplData->mxPrinter->SetPaperSizeUser( aPageSize.aSize ); + if( mpImplData->mnFixedPaperBin != -1 && + mpImplData->mxPrinter->GetPaperBin() != mpImplData->mnFixedPaperBin ) + { + mpImplData->mxPrinter->SetPaperBin( mpImplData->mnFixedPaperBin ); + } + + // if full paper is meant to be used, move the output to accommodate for pageoffset + if( aPageSize.bFullPaper ) + { + Point aPageOffset( mpImplData->mxPrinter->GetPageOffset() ); + aPageFile.WindStart(); + aPageFile.Move( -aPageOffset.X(), -aPageOffset.Y(), mpImplData->mxPrinter->GetDPIX(), mpImplData->mxPrinter->GetDPIY() ); + } + + GDIMetaFile aCleanedFile; + DrawModeFlags nRestoreDrawMode = removeTransparencies( aPageFile, aCleanedFile ); + + mpImplData->mxPrinter->EnableOutput(); + + // actually print the page + mpImplData->mxPrinter->ImplStartPage(); + + mpImplData->mxPrinter->Push(); + aCleanedFile.WindStart(); + aCleanedFile.Play(*mpImplData->mxPrinter); + mpImplData->mxPrinter->Pop(); + + mpImplData->mxPrinter->ImplEndPage(); + + mpImplData->mxPrinter->SetDrawMode( nRestoreDrawMode ); +} + +void PrinterController::jobStarted() +{ +} + +void PrinterController::jobFinished( css::view::PrintableState ) +{ +} + +void PrinterController::abortJob() +{ + setJobState( css::view::PrintableState_JOB_ABORTED ); + // applications (well, sw) depend on a page request with "IsLastPage" = true + // to free resources, else they (well, sw) will crash eventually + setLastPage( true ); + + if (mpImplData->mxProgress) + { + mpImplData->mxProgress->response(RET_CANCEL); + mpImplData->mxProgress.reset(); + } + + GDIMetaFile aMtf; + getPageFile( 0, aMtf ); +} + +void PrinterController::setLastPage( bool i_bLastPage ) +{ + mpImplData->mbLastPage = i_bLastPage; +} + +void PrinterController::setReversePrint( bool i_bReverse ) +{ + mpImplData->mbReversePageOrder = i_bReverse; +} + +void PrinterController::setPapersizeFromSetup( bool i_bPapersizeFromSetup ) +{ + mpImplData->mbPapersizeFromSetup = i_bPapersizeFromSetup; + mpImplData->mxPrinter->SetPrinterSettingsPreferred( i_bPapersizeFromSetup ); + if ( i_bPapersizeFromSetup ) + { + mpImplData->mbPapersizeFromUser = false; + mpImplData->mbOrientationFromUser = false; + } +} + +bool PrinterController::getPapersizeFromSetup() const +{ + return mpImplData->mbPapersizeFromSetup; +} + +void PrinterController::setPaperSizeFromUser( Size i_aUserSize ) +{ + mpImplData->mbPapersizeFromUser = true; + mpImplData->mbPapersizeFromSetup = false; + mpImplData->mxPrinter->SetPrinterSettingsPreferred( false ); + + mpImplData->maUserPageSize = i_aUserSize; +} + +void PrinterController::setOrientationFromUser( Orientation eOrientation, bool set ) +{ + mpImplData->mbOrientationFromUser = set; + mpImplData->meUserOrientation = eOrientation; +} + +void PrinterController::setPrinterModified( bool i_bPrinterModified ) +{ + mpImplData->mbPrinterModified = i_bPrinterModified; +} + +bool PrinterController::getPrinterModified() const +{ + return mpImplData->mbPrinterModified; +} + +css::uno::Sequence< css::beans::PropertyValue > PrinterController::getJobProperties( const css::uno::Sequence< css::beans::PropertyValue >& i_rMergeList ) const +{ + std::unordered_set< OUString > aMergeSet; + size_t nResultLen = size_t(i_rMergeList.getLength()) + mpImplData->maUIProperties.size() + 3; + for( const auto& rPropVal : i_rMergeList ) + aMergeSet.insert( rPropVal.Name ); + + css::uno::Sequence< css::beans::PropertyValue > aResult( nResultLen ); + auto pResult = aResult.getArray(); + std::copy(i_rMergeList.begin(), i_rMergeList.end(), pResult); + int nCur = i_rMergeList.getLength(); + for(const css::beans::PropertyValue & rPropVal : mpImplData->maUIProperties) + { + if( aMergeSet.find( rPropVal.Name ) == aMergeSet.end() ) + pResult[nCur++] = rPropVal; + } + // append IsFirstPage + if( aMergeSet.find( "IsFirstPage" ) == aMergeSet.end() ) + { + css::beans::PropertyValue aVal; + aVal.Name = "IsFirstPage"; + aVal.Value <<= mpImplData->mbFirstPage; + pResult[nCur++] = aVal; + } + // append IsLastPage + if( aMergeSet.find( "IsLastPage" ) == aMergeSet.end() ) + { + css::beans::PropertyValue aVal; + aVal.Name = "IsLastPage"; + aVal.Value <<= mpImplData->mbLastPage; + pResult[nCur++] = aVal; + } + // append IsPrinter + if( aMergeSet.find( "IsPrinter" ) == aMergeSet.end() ) + { + css::beans::PropertyValue aVal; + aVal.Name = "IsPrinter"; + aVal.Value <<= true; + pResult[nCur++] = aVal; + } + aResult.realloc( nCur ); + return aResult; +} + +const css::uno::Sequence< css::beans::PropertyValue >& PrinterController::getUIOptions() const +{ + return mpImplData->maUIOptions; +} + +css::beans::PropertyValue* PrinterController::getValue( const OUString& i_rProperty ) +{ + std::unordered_map< OUString, size_t >::const_iterator it = + mpImplData->maPropertyToIndex.find( i_rProperty ); + return it != mpImplData->maPropertyToIndex.end() ? &mpImplData->maUIProperties[it->second] : nullptr; +} + +const css::beans::PropertyValue* PrinterController::getValue( const OUString& i_rProperty ) const +{ + std::unordered_map< OUString, size_t >::const_iterator it = + mpImplData->maPropertyToIndex.find( i_rProperty ); + return it != mpImplData->maPropertyToIndex.end() ? &mpImplData->maUIProperties[it->second] : nullptr; +} + +void PrinterController::setValue( const OUString& i_rPropertyName, const css::uno::Any& i_rValue ) +{ + css::beans::PropertyValue aVal; + aVal.Name = i_rPropertyName; + aVal.Value = i_rValue; + + setValue( aVal ); +} + +void PrinterController::setValue( const css::beans::PropertyValue& i_rPropertyValue ) +{ + std::unordered_map< OUString, size_t >::const_iterator it = + mpImplData->maPropertyToIndex.find( i_rPropertyValue.Name ); + if( it != mpImplData->maPropertyToIndex.end() ) + mpImplData->maUIProperties[ it->second ] = i_rPropertyValue; + else + { + // insert correct index into property map + mpImplData->maPropertyToIndex[ i_rPropertyValue.Name ] = mpImplData->maUIProperties.size(); + mpImplData->maUIProperties.push_back( i_rPropertyValue ); + mpImplData->maUIPropertyEnabled.push_back( true ); + } +} + +void PrinterController::setUIOptions( const css::uno::Sequence< css::beans::PropertyValue >& i_rOptions ) +{ + SAL_WARN_IF( mpImplData->maUIOptions.hasElements(), "vcl.gdi", "setUIOptions called twice !" ); + + mpImplData->maUIOptions = i_rOptions; + + for( const auto& rOpt : i_rOptions ) + { + css::uno::Sequence< css::beans::PropertyValue > aOptProp; + rOpt.Value >>= aOptProp; + bool bIsEnabled = true; + bool bHaveProperty = false; + OUString aPropName; + vcl::ImplPrinterControllerData::ControlDependency aDep; + css::uno::Sequence< sal_Bool > aChoicesDisabled; + for( const css::beans::PropertyValue& rEntry : std::as_const(aOptProp) ) + { + if ( rEntry.Name == "Property" ) + { + css::beans::PropertyValue aVal; + rEntry.Value >>= aVal; + DBG_ASSERT( mpImplData->maPropertyToIndex.find( aVal.Name ) + == mpImplData->maPropertyToIndex.end(), "duplicate property entry" ); + setValue( aVal ); + aPropName = aVal.Name; + bHaveProperty = true; + } + else if ( rEntry.Name == "Enabled" ) + { + bool bValue = true; + rEntry.Value >>= bValue; + bIsEnabled = bValue; + } + else if ( rEntry.Name == "DependsOnName" ) + { + rEntry.Value >>= aDep.maDependsOnName; + } + else if ( rEntry.Name == "DependsOnEntry" ) + { + rEntry.Value >>= aDep.mnDependsOnEntry; + } + else if ( rEntry.Name == "ChoicesDisabled" ) + { + rEntry.Value >>= aChoicesDisabled; + } + } + if( bHaveProperty ) + { + vcl::ImplPrinterControllerData::PropertyToIndexMap::const_iterator it = + mpImplData->maPropertyToIndex.find( aPropName ); + // sanity check + if( it != mpImplData->maPropertyToIndex.end() ) + { + mpImplData->maUIPropertyEnabled[ it->second ] = bIsEnabled; + } + if( !aDep.maDependsOnName.isEmpty() ) + mpImplData->maControlDependencies[ aPropName ] = aDep; + if( aChoicesDisabled.hasElements() ) + mpImplData->maChoiceDisableMap[ aPropName ] = aChoicesDisabled; + } + } +} + +bool PrinterController::isUIOptionEnabled( const OUString& i_rProperty ) const +{ + bool bEnabled = false; + std::unordered_map< OUString, size_t >::const_iterator prop_it = + mpImplData->maPropertyToIndex.find( i_rProperty ); + if( prop_it != mpImplData->maPropertyToIndex.end() ) + { + bEnabled = mpImplData->maUIPropertyEnabled[prop_it->second]; + + if( bEnabled ) + { + // check control dependencies + vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it = + mpImplData->maControlDependencies.find( i_rProperty ); + if( it != mpImplData->maControlDependencies.end() ) + { + // check if the dependency is enabled + // if the dependency is disabled, we are too + bEnabled = isUIOptionEnabled( it->second.maDependsOnName ); + + if( bEnabled ) + { + // does the dependency have the correct value ? + const css::beans::PropertyValue* pVal = getValue( it->second.maDependsOnName ); + OSL_ENSURE( pVal, "unknown property in dependency" ); + if( pVal ) + { + sal_Int32 nDepVal = 0; + bool bDepVal = false; + if( pVal->Value >>= nDepVal ) + { + bEnabled = (nDepVal == it->second.mnDependsOnEntry) || (it->second.mnDependsOnEntry == -1); + } + else if( pVal->Value >>= bDepVal ) + { + // could be a dependency on a checked boolean + // in this case the dependency is on a non zero for checked value + bEnabled = ( bDepVal && it->second.mnDependsOnEntry != 0) || + ( ! bDepVal && it->second.mnDependsOnEntry == 0); + } + else + { + // if the type does not match something is awry + OSL_FAIL( "strange type in control dependency" ); + bEnabled = false; + } + } + } + } + } + } + return bEnabled; +} + +bool PrinterController::isUIChoiceEnabled( const OUString& i_rProperty, sal_Int32 i_nValue ) const +{ + bool bEnabled = true; + ImplPrinterControllerData::ChoiceDisableMap::const_iterator it = + mpImplData->maChoiceDisableMap.find( i_rProperty ); + if(it != mpImplData->maChoiceDisableMap.end() ) + { + const css::uno::Sequence< sal_Bool >& rDisabled( it->second ); + if( i_nValue >= 0 && i_nValue < rDisabled.getLength() ) + bEnabled = ! rDisabled[i_nValue]; + } + return bEnabled; +} + +OUString PrinterController::makeEnabled( const OUString& i_rProperty ) +{ + OUString aDependency; + + vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it = + mpImplData->maControlDependencies.find( i_rProperty ); + if( it != mpImplData->maControlDependencies.end() ) + { + if( isUIOptionEnabled( it->second.maDependsOnName ) ) + { + aDependency = it->second.maDependsOnName; + const css::beans::PropertyValue* pVal = getValue( aDependency ); + OSL_ENSURE( pVal, "unknown property in dependency" ); + if( pVal ) + { + sal_Int32 nDepVal = 0; + bool bDepVal = false; + if( pVal->Value >>= nDepVal ) + { + if( it->second.mnDependsOnEntry != -1 ) + { + setValue( aDependency, css::uno::Any( sal_Int32( it->second.mnDependsOnEntry ) ) ); + } + } + else if( pVal->Value >>= bDepVal ) + { + setValue( aDependency, css::uno::Any( it->second.mnDependsOnEntry != 0 ) ); + } + else + { + // if the type does not match something is awry + OSL_FAIL( "strange type in control dependency" ); + } + } + } + } + + return aDependency; +} + +void PrinterController::createProgressDialog() +{ + if (!mpImplData->mxProgress) + { + bool bShow = true; + css::beans::PropertyValue* pMonitor = getValue( "MonitorVisible" ); + if( pMonitor ) + pMonitor->Value >>= bShow; + else + { + const css::beans::PropertyValue* pVal = getValue( "IsApi" ); + if( pVal ) + { + bool bApi = false; + pVal->Value >>= bApi; + bShow = ! bApi; + } + } + + if( bShow && ! Application::IsHeadlessModeEnabled() ) + { + mpImplData->mxProgress = std::make_shared<PrintProgressDialog>(getWindow(), getPageCountProtected()); + weld::DialogController::runAsync(mpImplData->mxProgress, [](sal_Int32 /*nResult*/){}); + } + } + else + { + mpImplData->mxProgress->response(RET_CANCEL); + mpImplData->mxProgress.reset(); + } +} + +bool PrinterController::isProgressCanceled() const +{ + return mpImplData->mxProgress && mpImplData->mxProgress->isCanceled(); +} + +void PrinterController::setMultipage( const MultiPageSetup& i_rMPS ) +{ + mpImplData->maMultiPage = i_rMPS; +} + +const PrinterController::MultiPageSetup& PrinterController::getMultipage() const +{ + return mpImplData->maMultiPage; +} + +void PrinterController::resetPaperToLastConfigured() +{ + mpImplData->resetPaperToLastConfigured(); +} + +void PrinterController::pushPropertiesToPrinter() +{ + sal_Int32 nCopyCount = 1; + // set copycount and collate + const css::beans::PropertyValue* pVal = getValue( "CopyCount" ); + if( pVal ) + pVal->Value >>= nCopyCount; + bool bCollate = false; + pVal = getValue( "Collate" ); + if( pVal ) + pVal->Value >>= bCollate; + mpImplData->mxPrinter->SetCopyCount( static_cast<sal_uInt16>(nCopyCount), bCollate ); + + pVal = getValue("SinglePrintJobs"); + bool bSinglePrintJobs = false; + if (pVal) + pVal->Value >>= bSinglePrintJobs; + mpImplData->mxPrinter->SetSinglePrintJobs(bSinglePrintJobs); + + // duplex mode + pVal = getValue( "DuplexMode" ); + if( pVal ) + { + sal_Int16 nDuplex = css::view::DuplexMode::UNKNOWN; + pVal->Value >>= nDuplex; + switch( nDuplex ) + { + case css::view::DuplexMode::OFF: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::Off ); break; + case css::view::DuplexMode::LONGEDGE: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::LongEdge ); break; + case css::view::DuplexMode::SHORTEDGE: mpImplData->mxPrinter->SetDuplexMode( DuplexMode::ShortEdge ); break; + } + } +} + +bool PrinterController::isShowDialogs() const +{ + bool bApi = getBoolProperty( "IsApi", false ); + return ! bApi && ! Application::IsHeadlessModeEnabled(); +} + +bool PrinterController::isDirectPrint() const +{ + bool bDirect = getBoolProperty( "IsDirect", false ); + return bDirect; +} + +bool PrinterController::getBoolProperty( const OUString& i_rProperty, bool i_bFallback ) const +{ + bool bRet = i_bFallback; + const css::beans::PropertyValue* pVal = getValue( i_rProperty ); + if( pVal ) + pVal->Value >>= bRet; + return bRet; +} + +sal_Int32 PrinterController::getIntProperty( const OUString& i_rProperty, sal_Int32 i_nFallback ) const +{ + sal_Int32 nRet = i_nFallback; + const css::beans::PropertyValue* pVal = getValue( i_rProperty ); + if( pVal ) + pVal->Value >>= nRet; + return nRet; +} + +/* + * PrinterOptionsHelper +**/ +css::uno::Any PrinterOptionsHelper::getValue( const OUString& i_rPropertyName ) const +{ + css::uno::Any aRet; + std::unordered_map< OUString, css::uno::Any >::const_iterator it = + m_aPropertyMap.find( i_rPropertyName ); + if( it != m_aPropertyMap.end() ) + aRet = it->second; + return aRet; +} + +bool PrinterOptionsHelper::getBoolValue( const OUString& i_rPropertyName, bool i_bDefault ) const +{ + bool bRet = false; + css::uno::Any aVal( getValue( i_rPropertyName ) ); + return (aVal >>= bRet) ? bRet : i_bDefault; +} + +sal_Int64 PrinterOptionsHelper::getIntValue( const OUString& i_rPropertyName, sal_Int64 i_nDefault ) const +{ + sal_Int64 nRet = 0; + css::uno::Any aVal( getValue( i_rPropertyName ) ); + return (aVal >>= nRet) ? nRet : i_nDefault; +} + +OUString PrinterOptionsHelper::getStringValue( const OUString& i_rPropertyName ) const +{ + OUString aRet; + css::uno::Any aVal( getValue( i_rPropertyName ) ); + return (aVal >>= aRet) ? aRet : OUString(); +} + +bool PrinterOptionsHelper::processProperties( const css::uno::Sequence< css::beans::PropertyValue >& i_rNewProp ) +{ + bool bChanged = false; + + for( const auto& rVal : i_rNewProp ) + { + std::unordered_map< OUString, css::uno::Any >::iterator it = + m_aPropertyMap.find( rVal.Name ); + + bool bElementChanged = (it == m_aPropertyMap.end()) || (it->second != rVal.Value); + if( bElementChanged ) + { + m_aPropertyMap[ rVal.Name ] = rVal.Value; + bChanged = true; + } + } + return bChanged; +} + +void PrinterOptionsHelper::appendPrintUIOptions( css::uno::Sequence< css::beans::PropertyValue >& io_rProps ) const +{ + if( !m_aUIProperties.empty() ) + { + sal_Int32 nIndex = io_rProps.getLength(); + io_rProps.realloc( nIndex+1 ); + io_rProps.getArray()[ nIndex ] = comphelper::makePropertyValue( + "ExtraPrintUIOptions", comphelper::containerToSequence(m_aUIProperties)); + } +} + +css::uno::Any PrinterOptionsHelper::setUIControlOpt(const css::uno::Sequence< OUString >& i_rIDs, + const OUString& i_rTitle, + const css::uno::Sequence< OUString >& i_rHelpIds, + const OUString& i_rType, + const css::beans::PropertyValue* i_pVal, + const PrinterOptionsHelper::UIControlOptions& i_rControlOptions) +{ + sal_Int32 nElements = + 2 // ControlType + ID + + (i_rTitle.isEmpty() ? 0 : 1) // Text + + (i_rHelpIds.hasElements() ? 1 : 0) // HelpId + + (i_pVal ? 1 : 0) // Property + + i_rControlOptions.maAddProps.size() // additional props + + (i_rControlOptions.maGroupHint.isEmpty() ? 0 : 1) // grouping + + (i_rControlOptions.mbInternalOnly ? 1 : 0) // internal hint + + (i_rControlOptions.mbEnabled ? 0 : 1) // enabled + ; + if( !i_rControlOptions.maDependsOnName.isEmpty() ) + { + nElements += 1; + if( i_rControlOptions.mnDependsOnEntry != -1 ) + nElements += 1; + if( i_rControlOptions.mbAttachToDependency ) + nElements += 1; + } + + css::uno::Sequence< css::beans::PropertyValue > aCtrl( nElements ); + auto pCtrl = aCtrl.getArray(); + sal_Int32 nUsed = 0; + if( !i_rTitle.isEmpty() ) + { + pCtrl[nUsed ].Name = "Text"; + pCtrl[nUsed++].Value <<= i_rTitle; + } + if( i_rHelpIds.hasElements() ) + { + pCtrl[nUsed ].Name = "HelpId"; + pCtrl[nUsed++].Value <<= i_rHelpIds; + } + pCtrl[nUsed ].Name = "ControlType"; + pCtrl[nUsed++].Value <<= i_rType; + pCtrl[nUsed ].Name = "ID"; + pCtrl[nUsed++].Value <<= i_rIDs; + if( i_pVal ) + { + pCtrl[nUsed ].Name = "Property"; + pCtrl[nUsed++].Value <<= *i_pVal; + } + if( !i_rControlOptions.maDependsOnName.isEmpty() ) + { + pCtrl[nUsed ].Name = "DependsOnName"; + pCtrl[nUsed++].Value <<= i_rControlOptions.maDependsOnName; + if( i_rControlOptions.mnDependsOnEntry != -1 ) + { + pCtrl[nUsed ].Name = "DependsOnEntry"; + pCtrl[nUsed++].Value <<= i_rControlOptions.mnDependsOnEntry; + } + if( i_rControlOptions.mbAttachToDependency ) + { + pCtrl[nUsed ].Name = "AttachToDependency"; + pCtrl[nUsed++].Value <<= i_rControlOptions.mbAttachToDependency; + } + } + if( !i_rControlOptions.maGroupHint.isEmpty() ) + { + pCtrl[nUsed ].Name = "GroupingHint"; + pCtrl[nUsed++].Value <<= i_rControlOptions.maGroupHint; + } + if( i_rControlOptions.mbInternalOnly ) + { + pCtrl[nUsed ].Name = "InternalUIOnly"; + pCtrl[nUsed++].Value <<= true; + } + if( ! i_rControlOptions.mbEnabled ) + { + pCtrl[nUsed ].Name = "Enabled"; + pCtrl[nUsed++].Value <<= false; + } + + sal_Int32 nAddProps = i_rControlOptions.maAddProps.size(); + for( sal_Int32 i = 0; i < nAddProps; i++ ) + pCtrl[ nUsed++ ] = i_rControlOptions.maAddProps[i]; + + SAL_WARN_IF( nUsed != nElements, "vcl.gdi", "nUsed != nElements, probable heap corruption" ); + + return css::uno::Any( aCtrl ); +} + +css::uno::Any PrinterOptionsHelper::setGroupControlOpt(const OUString& i_rID, + const OUString& i_rTitle, + const OUString& i_rHelpId) +{ + css::uno::Sequence< OUString > aHelpId; + if( !i_rHelpId.isEmpty() ) + { + aHelpId.realloc( 1 ); + *aHelpId.getArray() = i_rHelpId; + } + css::uno::Sequence< OUString > aIds { i_rID }; + return setUIControlOpt(aIds, i_rTitle, aHelpId, "Group"); +} + +css::uno::Any PrinterOptionsHelper::setSubgroupControlOpt(const OUString& i_rID, + const OUString& i_rTitle, + const OUString& i_rHelpId, + const PrinterOptionsHelper::UIControlOptions& i_rControlOptions) +{ + css::uno::Sequence< OUString > aHelpId; + if( !i_rHelpId.isEmpty() ) + { + aHelpId.realloc( 1 ); + *aHelpId.getArray() = i_rHelpId; + } + css::uno::Sequence< OUString > aIds { i_rID }; + return setUIControlOpt(aIds, i_rTitle, aHelpId, "Subgroup", nullptr, i_rControlOptions); +} + +css::uno::Any PrinterOptionsHelper::setBoolControlOpt(const OUString& i_rID, + const OUString& i_rTitle, + const OUString& i_rHelpId, + const OUString& i_rProperty, + bool i_bValue, + const PrinterOptionsHelper::UIControlOptions& i_rControlOptions) +{ + css::uno::Sequence< OUString > aHelpId; + if( !i_rHelpId.isEmpty() ) + { + aHelpId.realloc( 1 ); + *aHelpId.getArray() = i_rHelpId; + } + css::beans::PropertyValue aVal; + aVal.Name = i_rProperty; + aVal.Value <<= i_bValue; + css::uno::Sequence< OUString > aIds { i_rID }; + return setUIControlOpt(aIds, i_rTitle, aHelpId, "Bool", &aVal, i_rControlOptions); +} + +css::uno::Any PrinterOptionsHelper::setChoiceRadiosControlOpt(const css::uno::Sequence< OUString >& i_rIDs, + const OUString& i_rTitle, + const css::uno::Sequence< OUString >& i_rHelpId, + const OUString& i_rProperty, + const css::uno::Sequence< OUString >& i_rChoices, + sal_Int32 i_nValue, + const css::uno::Sequence< sal_Bool >& i_rDisabledChoices, + const PrinterOptionsHelper::UIControlOptions& i_rControlOptions) +{ + UIControlOptions aOpt( i_rControlOptions ); + sal_Int32 nUsed = aOpt.maAddProps.size(); + aOpt.maAddProps.resize( nUsed + 1 + (i_rDisabledChoices.hasElements() ? 1 : 0) ); + aOpt.maAddProps[nUsed].Name = "Choices"; + aOpt.maAddProps[nUsed].Value <<= i_rChoices; + if( i_rDisabledChoices.hasElements() ) + { + aOpt.maAddProps[nUsed+1].Name = "ChoicesDisabled"; + aOpt.maAddProps[nUsed+1].Value <<= i_rDisabledChoices; + } + + css::beans::PropertyValue aVal; + aVal.Name = i_rProperty; + aVal.Value <<= i_nValue; + return setUIControlOpt(i_rIDs, i_rTitle, i_rHelpId, "Radio", &aVal, aOpt); +} + +css::uno::Any PrinterOptionsHelper::setChoiceListControlOpt(const OUString& i_rID, + const OUString& i_rTitle, + const css::uno::Sequence< OUString >& i_rHelpId, + const OUString& i_rProperty, + const css::uno::Sequence< OUString >& i_rChoices, + sal_Int32 i_nValue, + const css::uno::Sequence< sal_Bool >& i_rDisabledChoices, + const PrinterOptionsHelper::UIControlOptions& i_rControlOptions) +{ + UIControlOptions aOpt( i_rControlOptions ); + sal_Int32 nUsed = aOpt.maAddProps.size(); + aOpt.maAddProps.resize( nUsed + 1 + (i_rDisabledChoices.hasElements() ? 1 : 0) ); + aOpt.maAddProps[nUsed].Name = "Choices"; + aOpt.maAddProps[nUsed].Value <<= i_rChoices; + if( i_rDisabledChoices.hasElements() ) + { + aOpt.maAddProps[nUsed+1].Name = "ChoicesDisabled"; + aOpt.maAddProps[nUsed+1].Value <<= i_rDisabledChoices; + } + + css::beans::PropertyValue aVal; + aVal.Name = i_rProperty; + aVal.Value <<= i_nValue; + css::uno::Sequence< OUString > aIds { i_rID }; + return setUIControlOpt(aIds, i_rTitle, i_rHelpId, "List", &aVal, aOpt); +} + +css::uno::Any PrinterOptionsHelper::setRangeControlOpt(const OUString& i_rID, + const OUString& i_rTitle, + const OUString& i_rHelpId, + const OUString& i_rProperty, + sal_Int32 i_nValue, + sal_Int32 i_nMinValue, + sal_Int32 i_nMaxValue, + const PrinterOptionsHelper::UIControlOptions& i_rControlOptions) +{ + UIControlOptions aOpt( i_rControlOptions ); + if( i_nMaxValue >= i_nMinValue ) + { + sal_Int32 nUsed = aOpt.maAddProps.size(); + aOpt.maAddProps.resize( nUsed + 2 ); + aOpt.maAddProps[nUsed ].Name = "MinValue"; + aOpt.maAddProps[nUsed++].Value <<= i_nMinValue; + aOpt.maAddProps[nUsed ].Name = "MaxValue"; + aOpt.maAddProps[nUsed++].Value <<= i_nMaxValue; + } + + css::uno::Sequence< OUString > aHelpId; + if( !i_rHelpId.isEmpty() ) + { + aHelpId.realloc( 1 ); + *aHelpId.getArray() = i_rHelpId; + } + css::beans::PropertyValue aVal; + aVal.Name = i_rProperty; + aVal.Value <<= i_nValue; + css::uno::Sequence< OUString > aIds { i_rID }; + return setUIControlOpt(aIds, i_rTitle, aHelpId, "Range", &aVal, aOpt); +} + +css::uno::Any PrinterOptionsHelper::setEditControlOpt(const OUString& i_rID, + const OUString& i_rTitle, + const OUString& i_rHelpId, + const OUString& i_rProperty, + const OUString& i_rValue, + const PrinterOptionsHelper::UIControlOptions& i_rControlOptions) +{ + css::uno::Sequence< OUString > aHelpId; + if( !i_rHelpId.isEmpty() ) + { + aHelpId.realloc( 1 ); + *aHelpId.getArray() = i_rHelpId; + } + css::beans::PropertyValue aVal; + aVal.Name = i_rProperty; + aVal.Value <<= i_rValue; + css::uno::Sequence< OUString > aIds { i_rID }; + return setUIControlOpt(aIds, i_rTitle, aHelpId, "Edit", &aVal, i_rControlOptions); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/regband.cxx b/vcl/source/gdi/regband.cxx new file mode 100644 index 0000000000..91d292519f --- /dev/null +++ b/vcl/source/gdi/regband.cxx @@ -0,0 +1,886 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/helpers.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <regband.hxx> + +// ImplRegionBand + +// Each band contains all rectangles between upper and lower border. +// For Union, Intersect, Xor and Exclude operations rectangles of +// equal height are evaluated. The borders of the bands should always +// be chosen such that this is possible. + +// If possible, rectangles within the bands are condensed. + +// When converting polygons all points of the polygon are registered +// in the individual bands (for each band they are stored as +// points in a list). After registration of these points they are +// converted to rectangles and the points in the list are deleted. + +ImplRegionBand::ImplRegionBand( tools::Long nTop, tools::Long nBottom ) +{ + // save boundaries + mnYTop = nTop; + mnYBottom = nBottom; + + // initialize lists + mpNextBand = nullptr; + mpPrevBand = nullptr; + mpFirstSep = nullptr; + mpFirstBandPoint = nullptr; + mbTouched = false; +} + +ImplRegionBand::ImplRegionBand( + const ImplRegionBand& rRegionBand, + const bool bIgnorePoints) +{ + // copy boundaries + mnYTop = rRegionBand.mnYTop; + mnYBottom = rRegionBand.mnYBottom; + mbTouched = rRegionBand.mbTouched; + + // initialisation + mpNextBand = nullptr; + mpPrevBand = nullptr; + mpFirstSep = nullptr; + mpFirstBandPoint = nullptr; + + // copy all elements of the list with separations + ImplRegionBandSep* pNewSep; + ImplRegionBandSep* pPrevSep = nullptr; + ImplRegionBandSep* pSep = rRegionBand.mpFirstSep; + while ( pSep ) + { + // create new and copy data + pNewSep = new ImplRegionBandSep; + pNewSep->mnXLeft = pSep->mnXLeft; + pNewSep->mnXRight = pSep->mnXRight; + pNewSep->mbRemoved = pSep->mbRemoved; + pNewSep->mpNextSep = nullptr; + if ( pSep == rRegionBand.mpFirstSep ) + mpFirstSep = pNewSep; + else + pPrevSep->mpNextSep = pNewSep; + + pPrevSep = pNewSep; + pSep = pSep->mpNextSep; + } + + if ( bIgnorePoints) + return; + + // Copy points. + ImplRegionBandPoint* pPoint = rRegionBand.mpFirstBandPoint; + ImplRegionBandPoint* pPrevPointCopy = nullptr; + while (pPoint != nullptr) + { + ImplRegionBandPoint* pPointCopy = new ImplRegionBandPoint; + pPointCopy->mpNextBandPoint = nullptr; + pPointCopy->mnX = pPoint->mnX; + pPointCopy->mnLineId = pPoint->mnLineId; + pPointCopy->mbEndPoint = pPoint->mbEndPoint; + pPointCopy->meLineType = pPoint->meLineType; + + if (pPrevPointCopy != nullptr) + pPrevPointCopy->mpNextBandPoint = pPointCopy; + else + mpFirstBandPoint = pPointCopy; + + pPrevPointCopy = pPointCopy; + pPoint = pPoint->mpNextBandPoint; + } +} + +ImplRegionBand::~ImplRegionBand() +{ + SAL_WARN_IF( mpFirstBandPoint != nullptr, "vcl", "ImplRegionBand::~ImplRegionBand -> pointlist not empty" ); + + // delete elements of the list + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep ) + { + ImplRegionBandSep* pTempSep = pSep->mpNextSep; + delete pSep; + pSep = pTempSep; + } + + // delete elements of the list + ImplRegionBandPoint* pPoint = mpFirstBandPoint; + while ( pPoint ) + { + ImplRegionBandPoint* pTempPoint = pPoint->mpNextBandPoint; + delete pPoint; + pPoint = pTempPoint; + } +} + +// generate separations from lines and process union with existing +// separations + +void ImplRegionBand::ProcessPoints() +{ + // check Pointlist + ImplRegionBandPoint* pRegionBandPoint = mpFirstBandPoint; + while ( pRegionBandPoint ) + { + // within list? + if ( pRegionBandPoint->mpNextBandPoint ) + { + // start/stop? + if ( pRegionBandPoint->mbEndPoint && pRegionBandPoint->mpNextBandPoint->mbEndPoint ) + { + // same direction? -> remove next point! + if ( pRegionBandPoint->meLineType == pRegionBandPoint->mpNextBandPoint->meLineType ) + { + ImplRegionBandPoint* pSaveRegionBandPoint = pRegionBandPoint->mpNextBandPoint; + pRegionBandPoint->mpNextBandPoint = pRegionBandPoint->mpNextBandPoint->mpNextBandPoint; + delete pSaveRegionBandPoint; + } + } + } + + // continue with next element in the list + pRegionBandPoint = pRegionBandPoint->mpNextBandPoint; + } + + pRegionBandPoint = mpFirstBandPoint; + while ( pRegionBandPoint && pRegionBandPoint->mpNextBandPoint ) + { + Union( pRegionBandPoint->mnX, pRegionBandPoint->mpNextBandPoint->mnX ); + + ImplRegionBandPoint* pNextBandPoint = pRegionBandPoint->mpNextBandPoint->mpNextBandPoint; + + // remove already processed points + delete pRegionBandPoint->mpNextBandPoint; + delete pRegionBandPoint; + + // continue with next element in the list + pRegionBandPoint = pNextBandPoint; + } + + // remove last element if necessary + delete pRegionBandPoint; + + // list is now empty + mpFirstBandPoint = nullptr; +} + +// generate separations from lines and process union with existing +// separations + +bool ImplRegionBand::InsertPoint( tools::Long nX, tools::Long nLineId, + bool bEndPoint, LineType eLineType ) +{ + if ( !mpFirstBandPoint ) + { + mpFirstBandPoint = new ImplRegionBandPoint; + mpFirstBandPoint->mnX = nX; + mpFirstBandPoint->mnLineId = nLineId; + mpFirstBandPoint->mbEndPoint = bEndPoint; + mpFirstBandPoint->meLineType = eLineType; + mpFirstBandPoint->mpNextBandPoint = nullptr; + return true; + } + + // look if line already touched the band + ImplRegionBandPoint* pRegionBandPoint = mpFirstBandPoint; + ImplRegionBandPoint* pLastTestedRegionBandPoint = nullptr; + while( pRegionBandPoint ) + { + if ( pRegionBandPoint->mnLineId == nLineId ) + { + if ( bEndPoint ) + { + if( !pRegionBandPoint->mbEndPoint ) + { + // remove old band point + if( !mpFirstBandPoint->mpNextBandPoint ) + { + // if we've only got one point => replace first point + pRegionBandPoint->mnX = nX; + pRegionBandPoint->mbEndPoint = true; + return true; + } + else + { + // remove current point + if( !pLastTestedRegionBandPoint ) + { + // remove and delete old first point + ImplRegionBandPoint* pSaveBandPoint = mpFirstBandPoint; + mpFirstBandPoint = mpFirstBandPoint->mpNextBandPoint; + delete pSaveBandPoint; + } + else + { + // remove and delete current band point + pLastTestedRegionBandPoint->mpNextBandPoint = pRegionBandPoint->mpNextBandPoint; + delete pRegionBandPoint; + } + + break; + } + } + } + else + return false; + } + + // use next element + pLastTestedRegionBandPoint = pRegionBandPoint; + pRegionBandPoint = pRegionBandPoint->mpNextBandPoint; + } + + // search appropriate position and insert point into the list + ImplRegionBandPoint* pNewRegionBandPoint; + + pRegionBandPoint = mpFirstBandPoint; + pLastTestedRegionBandPoint = nullptr; + while ( pRegionBandPoint ) + { + // new point completely left? -> insert as first point + if ( nX <= pRegionBandPoint->mnX ) + { + pNewRegionBandPoint = new ImplRegionBandPoint; + pNewRegionBandPoint->mnX = nX; + pNewRegionBandPoint->mnLineId = nLineId; + pNewRegionBandPoint->mbEndPoint = bEndPoint; + pNewRegionBandPoint->meLineType = eLineType; + pNewRegionBandPoint->mpNextBandPoint = pRegionBandPoint; + + // connections to the new point + if ( !pLastTestedRegionBandPoint ) + mpFirstBandPoint = pNewRegionBandPoint; + else + pLastTestedRegionBandPoint->mpNextBandPoint = pNewRegionBandPoint; + + return true; + } + + // use next element + pLastTestedRegionBandPoint = pRegionBandPoint; + pRegionBandPoint = pRegionBandPoint->mpNextBandPoint; + } + + // not inserted -> add to the end of the list + pNewRegionBandPoint = new ImplRegionBandPoint; + pNewRegionBandPoint->mnX = nX; + pNewRegionBandPoint->mnLineId = nLineId; + pNewRegionBandPoint->mbEndPoint = bEndPoint; + pNewRegionBandPoint->meLineType = eLineType; + pNewRegionBandPoint->mpNextBandPoint = nullptr; + + // connections to the new point + pLastTestedRegionBandPoint->mpNextBandPoint = pNewRegionBandPoint; + + return true; +} + +void ImplRegionBand::MoveX( tools::Long nHorzMove ) +{ + // move all x-separations + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep ) + { + pSep->mnXLeft += nHorzMove; + pSep->mnXRight += nHorzMove; + pSep = pSep->mpNextSep; + } +} + +void ImplRegionBand::ScaleX( double fHorzScale ) +{ + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep ) + { + pSep->mnXLeft = FRound( pSep->mnXLeft * fHorzScale ); + pSep->mnXRight = FRound( pSep->mnXRight * fHorzScale ); + pSep = pSep->mpNextSep; + } +} + +// combine overlapping separations + +void ImplRegionBand::OptimizeBand() +{ + ImplRegionBandSep* pPrevSep = nullptr; + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep ) + { + // remove? + if ( pSep->mbRemoved || (pSep->mnXRight < pSep->mnXLeft) ) + { + ImplRegionBandSep* pOldSep = pSep; + if ( pSep == mpFirstSep ) + mpFirstSep = pSep->mpNextSep; + else + pPrevSep->mpNextSep = pSep->mpNextSep; + pSep = pSep->mpNextSep; + delete pOldSep; + continue; + } + + // overlapping separations? -> combine! + if ( pSep->mpNextSep ) + { + if ( (pSep->mnXRight+1) >= pSep->mpNextSep->mnXLeft ) + { + if ( pSep->mpNextSep->mnXRight > pSep->mnXRight ) + pSep->mnXRight = pSep->mpNextSep->mnXRight; + + ImplRegionBandSep* pOldSep = pSep->mpNextSep; + pSep->mpNextSep = pOldSep->mpNextSep; + delete pOldSep; + continue; + } + } + + pPrevSep = pSep; + pSep = pSep->mpNextSep; + } +} + +void ImplRegionBand::Union( tools::Long nXLeft, tools::Long nXRight ) +{ + SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Union(): nxLeft > nXRight" ); + + // band empty? -> add element + if ( !mpFirstSep ) + { + mpFirstSep = new ImplRegionBandSep; + mpFirstSep->mnXLeft = nXLeft; + mpFirstSep->mnXRight = nXRight; + mpFirstSep->mbRemoved = false; + mpFirstSep->mpNextSep = nullptr; + return; + } + + // process real union + ImplRegionBandSep* pNewSep; + ImplRegionBandSep* pPrevSep = nullptr; + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep ) + { + // new separation completely inside? nothing to do! + if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) ) + return; + + // new separation completely left? -> new separation! + if ( nXRight < pSep->mnXLeft ) + { + pNewSep = new ImplRegionBandSep; + pNewSep->mnXLeft = nXLeft; + pNewSep->mnXRight = nXRight; + pNewSep->mbRemoved = false; + + pNewSep->mpNextSep = pSep; + if ( pSep == mpFirstSep ) + mpFirstSep = pNewSep; + else + pPrevSep->mpNextSep = pNewSep; + break; + } + + // new separation overlapping from left? -> extend boundary + if ( (nXRight >= pSep->mnXLeft) && (nXLeft <= pSep->mnXLeft) ) + pSep->mnXLeft = nXLeft; + + // new separation overlapping from right? -> extend boundary + if ( (nXLeft <= pSep->mnXRight) && (nXRight > pSep->mnXRight) ) + { + pSep->mnXRight = nXRight; + break; + } + + // not inserted, but last element? -> add to the end of the list + if ( !pSep->mpNextSep && (nXLeft > pSep->mnXRight) ) + { + pNewSep = new ImplRegionBandSep; + pNewSep->mnXLeft = nXLeft; + pNewSep->mnXRight = nXRight; + pNewSep->mbRemoved = false; + + pSep->mpNextSep = pNewSep; + pNewSep->mpNextSep = nullptr; + break; + } + + pPrevSep = pSep; + pSep = pSep->mpNextSep; + } + + OptimizeBand(); +} + +void ImplRegionBand::Intersect( tools::Long nXLeft, tools::Long nXRight ) +{ + SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Intersect(): nxLeft > nXRight" ); + + // band has been touched + mbTouched = true; + + // band empty? -> nothing to do + if ( !mpFirstSep ) + return; + + // process real intersection + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep ) + { + // new separation completely outside? -> remove separation + if ( (nXRight < pSep->mnXLeft) || (nXLeft > pSep->mnXRight) ) + // will be removed from the optimizer + pSep->mbRemoved = true; + + // new separation overlapping from left? -> reduce right boundary + if ( (nXLeft <= pSep->mnXLeft) && + (nXRight <= pSep->mnXRight) && + (nXRight >= pSep->mnXLeft) ) + pSep->mnXRight = nXRight; + + // new separation overlapping from right? -> reduce right boundary + if ( (nXLeft >= pSep->mnXLeft) && + (nXLeft <= pSep->mnXRight) && + (nXRight >= pSep->mnXRight) ) + pSep->mnXLeft = nXLeft; + + // new separation within the actual one? -> reduce both boundaries + if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) ) + { + pSep->mnXRight = nXRight; + pSep->mnXLeft = nXLeft; + } + + pSep = pSep->mpNextSep; + } + + OptimizeBand(); +} + +void ImplRegionBand::Exclude( tools::Long nXLeft, tools::Long nXRight ) +{ + SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::Exclude(): nxLeft > nXRight" ); + + // band has been touched + mbTouched = true; + + // band empty? -> nothing to do + if ( !mpFirstSep ) + return; + + // process real exclusion + ImplRegionBandSep* pNewSep; + ImplRegionBandSep* pPrevSep = nullptr; + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep ) + { + bool bSepProcessed = false; + + // new separation completely overlapping? -> remove separation + if ( (nXLeft <= pSep->mnXLeft) && (nXRight >= pSep->mnXRight) ) + { + // will be removed from the optimizer + pSep->mbRemoved = true; + bSepProcessed = true; + } + + // new separation overlapping from left? -> reduce boundary + if ( !bSepProcessed ) + { + if ( (nXRight >= pSep->mnXLeft) && (nXLeft <= pSep->mnXLeft) ) + { + pSep->mnXLeft = nXRight+1; + bSepProcessed = true; + } + } + + // new separation overlapping from right? -> reduce boundary + if ( !bSepProcessed ) + { + if ( (nXLeft <= pSep->mnXRight) && (nXRight > pSep->mnXRight) ) + { + pSep->mnXRight = nXLeft-1; + bSepProcessed = true; + } + } + + // new separation within the actual one? -> reduce boundary + // and add new entry for reminder + if ( !bSepProcessed ) + { + if ( (nXLeft >= pSep->mnXLeft) && (nXRight <= pSep->mnXRight) ) + { + pNewSep = new ImplRegionBandSep; + pNewSep->mnXLeft = pSep->mnXLeft; + pNewSep->mnXRight = nXLeft-1; + pNewSep->mbRemoved = false; + + pSep->mnXLeft = nXRight+1; + + // connections from the new separation + pNewSep->mpNextSep = pSep; + + // connections to the new separation + if ( pSep == mpFirstSep ) + mpFirstSep = pNewSep; + else + pPrevSep->mpNextSep = pNewSep; + } + } + + pPrevSep = pSep; + pSep = pSep->mpNextSep; + } + + OptimizeBand(); +} + +void ImplRegionBand::XOr( tools::Long nXLeft, tools::Long nXRight ) +{ + SAL_WARN_IF( nXLeft > nXRight, "vcl", "ImplRegionBand::XOr(): nxLeft > nXRight" ); + + // #i46602# Reworked rectangle Xor + + // In general, we can distinguish 11 cases of intersection + // (details below). The old implementation explicitly handled 7 + // cases (numbered in the order of appearance, use CVS to get your + // hands on the old version), therefore, I've sticked to that + // order, and added four more cases. The code below references + // those numbers via #1, #2, etc. + + // Num Mnem newX:oldX newY:oldY Description Result Can quit? + + // #1 Empty band - - The band is empty, thus, simply add new bandSep just add Yes + + // #2 apart - - The rectangles are disjunct, add new one as is just add Yes + + // #3 atop == == The rectangles are _exactly_ the same, remove existing just remove Yes + + // #4 around < > The new rectangle extends the old to both sides intersect No + + // #5 left < < The new rectangle is left of the old (but intersects) intersect Yes + + // #5b left-atop < == The new is left of the old, and coincides on the right intersect Yes + + // #6 right > > The new is right of the old (but intersects) intersect No + + // #6b right-atop == > The new is right of the old, and coincides on the left intersect No + + // #7 inside > < The new is fully inside the old intersect Yes + + // #8 inside-right > == The new is fully inside the old, coincides on the right intersect Yes + + // #9 inside-left == < The new is fully inside the old, coincides on the left intersect Yes + + // Then, to correctly perform XOr, the segment that's switched off + // (i.e. the overlapping part of the old and the new segment) must + // be extended by one pixel value at each border: + // 1 1 + // 0 4 0 4 + // 111100000001111 + + // Clearly, the leading band sep now goes from 0 to 3, and the + // trailing band sep from 11 to 14. This mimics the xor look of a + // bitmap operation. + + // band empty? -> add element + if ( !mpFirstSep ) + { + mpFirstSep = new ImplRegionBandSep; + mpFirstSep->mnXLeft = nXLeft; + mpFirstSep->mnXRight = nXRight; + mpFirstSep->mbRemoved = false; + mpFirstSep->mpNextSep = nullptr; + return; + } + + // process real xor + ImplRegionBandSep* pNewSep; + ImplRegionBandSep* pPrevSep = nullptr; + ImplRegionBandSep* pSep = mpFirstSep; + + while ( pSep ) + { + tools::Long nOldLeft( pSep->mnXLeft ); + tools::Long nOldRight( pSep->mnXRight ); + + // did the current segment actually touch the new rect? If + // not, skip all comparisons, go on, loop and try to find + // intersecting bandSep + if( nXLeft <= nOldRight ) + { + if( nXRight < nOldLeft ) + { + // #2 + + // add _before_ current bandSep + pNewSep = new ImplRegionBandSep; + pNewSep->mnXLeft = nXLeft; + pNewSep->mnXRight = nXRight; + pNewSep->mpNextSep = pSep; + pNewSep->mbRemoved = false; + + // connections from the new separation + pNewSep->mpNextSep = pSep; + + // connections to the new separation + if ( pSep == mpFirstSep ) + mpFirstSep = pNewSep; + else + pPrevSep->mpNextSep = pNewSep; + pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop + break; + } + else if( nXLeft == nOldLeft && nXRight == nOldRight ) + { + // #3 + pSep->mbRemoved = true; + pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop + break; + } + else if( nXLeft != nOldLeft && nXRight == nOldRight ) + { + // # 5b, 8 + if( nXLeft < nOldLeft ) + { + nXRight = nOldLeft; // 5b + } + else + { + nXRight = nXLeft; // 8 + nXLeft = nOldLeft; + } + + pSep->mnXLeft = nXLeft; + pSep->mnXRight = nXRight-1; + + pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop + break; + } + else if( nXLeft == nOldLeft && nXRight != nOldRight ) + { + // # 6b, 9 + + if( nXRight > nOldRight ) + { + nXLeft = nOldRight+1; // 6b + + // cannot break here, simply mark segment as removed, + // and go on with adapted nXLeft/nXRight + pSep->mbRemoved = true; + } + else + { + pSep->mnXLeft = nXRight+1; // 9 + + pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop + break; + } + } + else // if( nXLeft != nOldLeft && nXRight != nOldRight ) follows automatically + { + // #4,5,6,7 + SAL_WARN_IF( nXLeft == nOldLeft || nXRight == nOldRight, "vcl", + "ImplRegionBand::XOr(): Case 4,5,6,7 expected all coordinates to be not equal!" ); + + // The plain-jane check would look like this: + + // if( nXLeft < nOldLeft ) + // { + // // #4,5 + // if( nXRight > nOldRight ) + // { + // // #4 + // } + // else + // { + // // #5 done! + // } + // } + // else + // { + // // #6,7 + // if( nXRight > nOldRight ) + // { + // // #6 + // } + // else + // { + // // #7 done! + // } + // } + + // but since we generally don't have to care whether + // it's 4 or 6 (only that we must not stop processing + // here), condensed that in such a way that only the + // coordinates get shuffled into correct ordering. + + if( nXLeft < nOldLeft ) + ::std::swap( nOldLeft, nXLeft ); + + bool bDone( false ); + + if( nXRight < nOldRight ) + { + ::std::swap( nOldRight, nXRight ); + bDone = true; + } + + // now, nOldLeft<nXLeft<=nOldRight<nXRight always + // holds. Note that we need the nXLeft<=nOldRight here, as + // the intersection part might be only one pixel (original + // nXLeft==nXRight) + SAL_WARN_IF( nOldLeft==nXLeft || nXLeft>nOldRight || nOldRight>=nXRight, "vcl", + "ImplRegionBand::XOr(): Case 4,5,6,7 expected coordinates to be ordered now!" ); + + pSep->mnXLeft = nOldLeft; + pSep->mnXRight = nXLeft-1; + + nXLeft = nOldRight+1; + // nxRight is already setup correctly + + if( bDone ) + { + // add behind current bandSep + pNewSep = new ImplRegionBandSep; + + pNewSep->mnXLeft = nXLeft; + pNewSep->mnXRight = nXRight; + pNewSep->mpNextSep = pSep->mpNextSep; + pNewSep->mbRemoved = false; + + // connections from the new separation + pSep->mpNextSep = pNewSep; + + pPrevSep = nullptr; // do not run accidentally into the "right" case when breaking the loop + break; + } + } + } + + pPrevSep = pSep; + pSep = pSep->mpNextSep; + } + + // new separation completely right of existing bandSeps ? + if( pPrevSep && nXLeft >= pPrevSep->mnXRight ) + { + pNewSep = new ImplRegionBandSep; + pNewSep->mnXLeft = nXLeft; + pNewSep->mnXRight = nXRight; + pNewSep->mpNextSep = nullptr; + pNewSep->mbRemoved = false; + + // connections from the new separation + pPrevSep->mpNextSep = pNewSep; + } + + OptimizeBand(); +} + +bool ImplRegionBand::Contains( tools::Long nX ) +{ + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep ) + { + if ( (pSep->mnXLeft <= nX) && (pSep->mnXRight >= nX) ) + return true; + + pSep = pSep->mpNextSep; + } + + return false; +} + +tools::Long ImplRegionBand::GetXLeftBoundary() const +{ + SAL_WARN_IF(mpFirstSep == nullptr, "vcl", "ImplRegionBand::XLeftBoundary -> no separation in band!"); + + return mpFirstSep ? mpFirstSep->mnXLeft : 0; +} + +tools::Long ImplRegionBand::GetXRightBoundary() const +{ + SAL_WARN_IF( mpFirstSep == nullptr, "vcl", "ImplRegionBand::XRightBoundary -> no separation in band!" ); + if (!mpFirstSep) + return 0; + // search last separation + ImplRegionBandSep* pSep = mpFirstSep; + while ( pSep->mpNextSep ) + pSep = pSep->mpNextSep; + return pSep->mnXRight; +} + +bool ImplRegionBand::operator==( const ImplRegionBand& rRegionBand ) const +{ + ImplRegionBandSep* pOwnRectBandSep = mpFirstSep; + ImplRegionBandSep* pSecondRectBandSep = rRegionBand.mpFirstSep; + while ( pOwnRectBandSep && pSecondRectBandSep ) + { + // get boundaries of current rectangle + tools::Long nOwnXLeft = pOwnRectBandSep->mnXLeft; + tools::Long nSecondXLeft = pSecondRectBandSep->mnXLeft; + if ( nOwnXLeft != nSecondXLeft ) + return false; + + tools::Long nOwnXRight = pOwnRectBandSep->mnXRight; + tools::Long nSecondXRight = pSecondRectBandSep->mnXRight; + if ( nOwnXRight != nSecondXRight ) + return false; + + // get next separation from current band + pOwnRectBandSep = pOwnRectBandSep->mpNextSep; + + // get next separation from current band + pSecondRectBandSep = pSecondRectBandSep->mpNextSep; + } + + // different number of separations? + return !(pOwnRectBandSep || pSecondRectBandSep); +} + +ImplRegionBand* ImplRegionBand::SplitBand (const sal_Int32 nY) +{ + OSL_ASSERT(nY>mnYTop); + OSL_ASSERT(nY<=mnYBottom); + + // Create a copy of the given band (we tell the constructor to copy the points together + // with the seps.) + ImplRegionBand* pLowerBand = new ImplRegionBand(*this, false); + + // Adapt vertical coordinates. + mnYBottom = nY-1; + pLowerBand->mnYTop = nY; + + // Insert new band into list of bands. + pLowerBand->mpNextBand = mpNextBand; + mpNextBand = pLowerBand; + pLowerBand->mpPrevBand = this; + if (pLowerBand->mpNextBand != nullptr) + pLowerBand->mpNextBand->mpPrevBand = pLowerBand; + + return pLowerBand; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/region.cxx b/vcl/source/gdi/region.cxx new file mode 100644 index 0000000000..6665b5c48d --- /dev/null +++ b/vcl/source/gdi/region.cxx @@ -0,0 +1,1793 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <tools/vcompat.hxx> +#include <tools/stream.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/region.hxx> +#include <regionband.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/poly.hxx> +#include <unotools/configmgr.hxx> + +namespace +{ + /** Return <TRUE/> when the given polygon is rectilinear and oriented so that + all sides are either horizontal or vertical. + */ + bool ImplIsPolygonRectilinear (const tools::PolyPolygon& rPolyPoly) + { + // Iterate over all polygons. + const sal_uInt16 nPolyCount = rPolyPoly.Count(); + for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly) + { + const tools::Polygon& aPoly = rPolyPoly.GetObject(nPoly); + + // Iterate over all edges of the current polygon. + const sal_uInt16 nSize = aPoly.GetSize(); + + if (nSize < 2) + continue; + Point aPoint (aPoly.GetPoint(0)); + const Point aLastPoint (aPoint); + for (sal_uInt16 nPoint = 1; nPoint < nSize; ++nPoint) + { + const Point aNextPoint (aPoly.GetPoint(nPoint)); + // When there is at least one edge that is neither vertical nor + // horizontal then the entire polygon is not rectilinear (and + // oriented along primary axes.) + if (aPoint.X() != aNextPoint.X() && aPoint.Y() != aNextPoint.Y()) + return false; + + aPoint = aNextPoint; + } + // Compare closing edge. + if (aLastPoint.X() != aPoint.X() && aLastPoint.Y() != aPoint.Y()) + return false; + } + return true; + } + + /** Convert a rectilinear polygon (that is oriented along the primary axes) + to a list of bands. For this special form of polygon we can use an + optimization that prevents the creation of one band per y value. + However, it still is possible that some temporary bands are created that + later can be optimized away. + @param rPolyPolygon + A set of zero, one, or more polygons, nested or not, that are + converted into a list of bands. + @return + A new RegionBand object is returned that contains the bands that + represent the given poly-polygon. + */ + std::shared_ptr<RegionBand> ImplRectilinearPolygonToBands(const tools::PolyPolygon& rPolyPoly) + { + OSL_ASSERT(ImplIsPolygonRectilinear (rPolyPoly)); + + // Create a new RegionBand object as container of the bands. + std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() ); + tools::Long nLineId = 0; + + // Iterate over all polygons. + const sal_uInt16 nPolyCount = rPolyPoly.Count(); + for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly) + { + const tools::Polygon& aPoly = rPolyPoly.GetObject(nPoly); + + // Iterate over all edges of the current polygon. + const sal_uInt16 nSize = aPoly.GetSize(); + if (nSize < 2) + continue; + // Avoid fetching every point twice (each point is the start point + // of one and the end point of another edge.) + Point aStart (aPoly.GetPoint(0)); + Point aEnd; + for (sal_uInt16 nPoint = 1; nPoint <= nSize; ++nPoint, aStart=aEnd) + { + // We take the implicit closing edge into account by mapping + // index nSize to 0. + aEnd = aPoly.GetPoint(nPoint%nSize); + if (aStart.Y() == aEnd.Y()) + { + // Horizontal lines are ignored. + continue; + } + + // At this point the line has to be vertical. + OSL_ASSERT(aStart.X() == aEnd.X()); + + // Sort y-coordinates to simplify the algorithm and store the + // direction separately. The direction is calculated as it is + // in other places (but seems to be the wrong way.) + const tools::Long nTop (::std::min(aStart.Y(), aEnd.Y())); + const tools::Long nBottom (::std::max(aStart.Y(), aEnd.Y())); + const LineType eLineType (aStart.Y() > aEnd.Y() ? LineType::Descending : LineType::Ascending); + + // Make sure that the current line is covered by bands. + pRegionBand->ImplAddMissingBands(nTop,nBottom); + + // Find top-most band that may contain nTop. + ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand(); + while (pBand!=nullptr && pBand->mnYBottom < nTop) + pBand = pBand->mpNextBand; + ImplRegionBand* pTopBand = pBand; + // If necessary split the band at nTop so that nTop is contained + // in the lower band. + if (pBand!=nullptr + // Prevent the current band from becoming 0 pixel high + && pBand->mnYTop<nTop + // this allows the lowest pixel of the band to be split off + && pBand->mnYBottom>=nTop + // do not split a band that is just one pixel high + && pBand->mnYTop<pBand->mnYBottom-1) + { + // Split the top band. + pTopBand = pBand->SplitBand(nTop); + } + + // Advance to band that may contain nBottom. + while (pBand!=nullptr && pBand->mnYBottom < nBottom) + pBand = pBand->mpNextBand; + // The lowest band may have to be split at nBottom so that + // nBottom itself remains in the upper band. + if (pBand!=nullptr + // allow the current band becoming 1 pixel high + && pBand->mnYTop<=nBottom + // prevent splitting off a band that is 0 pixel high + && pBand->mnYBottom>nBottom + // do not split a band that is just one pixel high + && pBand->mnYTop<pBand->mnYBottom-1) + { + // Split the bottom band. + pBand->SplitBand(nBottom+1); + } + + // Note that we remember the top band (in pTopBand) but not the + // bottom band. The later can be determined by comparing y + // coordinates. + + // Add the x-value as point to all bands in the nTop->nBottom range. + for (pBand=pTopBand; pBand!=nullptr&&pBand->mnYTop<=nBottom; pBand=pBand->mpNextBand) + pBand->InsertPoint(aStart.X(), nLineId++, true, eLineType); + } + } + + return pRegionBand; + } + + /** Convert a general polygon (one for which ImplIsPolygonRectilinear() + returns <FALSE/>) to bands. + */ + std::shared_ptr<RegionBand> ImplGeneralPolygonToBands(const tools::PolyPolygon& rPolyPoly, const tools::Rectangle& rPolygonBoundingBox) + { + tools::Long nLineID = 0; + + // initialisation and creation of Bands + std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() ); + pRegionBand->CreateBandRange(rPolygonBoundingBox.Top(), rPolygonBoundingBox.Bottom()); + + // insert polygons + const sal_uInt16 nPolyCount = rPolyPoly.Count(); + + for ( sal_uInt16 nPoly = 0; nPoly < nPolyCount; nPoly++ ) + { + // get reference to current polygon + const tools::Polygon& aPoly = rPolyPoly.GetObject( nPoly ); + const sal_uInt16 nSize = aPoly.GetSize(); + + // not enough points ( <= 2 )? -> nothing to do! + if ( nSize <= 2 ) + continue; + + // band the polygon + for ( sal_uInt16 nPoint = 1; nPoint < nSize; nPoint++ ) + { + pRegionBand->InsertLine( aPoly.GetPoint(nPoint-1), aPoly.GetPoint(nPoint), nLineID++ ); + } + + // close polygon with line from first point to last point, if necessary + const Point rLastPoint = aPoly.GetPoint(nSize-1); + const Point rFirstPoint = aPoly.GetPoint(0); + + if ( rLastPoint != rFirstPoint ) + { + pRegionBand->InsertLine( rLastPoint, rFirstPoint, nLineID++ ); + } + } + + return pRegionBand; + } +} // end of anonymous namespace + +namespace vcl { + +bool vcl::Region::IsEmpty() const +{ + return !mbIsNull && !mpB2DPolyPolygon && !mpPolyPolygon && !mpRegionBand; +} + + +static std::shared_ptr<RegionBand> ImplCreateRegionBandFromPolyPolygon(const tools::PolyPolygon& rPolyPolygon) +{ + std::shared_ptr<RegionBand> pRetval; + + if(rPolyPolygon.Count()) + { + // ensure to subdivide when bezier segments are used, it's going to + // be expanded to rectangles + tools::PolyPolygon aPolyPolygon; + + rPolyPolygon.AdaptiveSubdivide(aPolyPolygon); + + if(aPolyPolygon.Count()) + { + const tools::Rectangle aRect(aPolyPolygon.GetBoundRect()); + + if(!aRect.IsEmpty()) + { + if(ImplIsPolygonRectilinear(aPolyPolygon)) + { + // For rectilinear polygons there is an optimized band conversion. + pRetval = ImplRectilinearPolygonToBands(aPolyPolygon); + } + else + { + pRetval = ImplGeneralPolygonToBands(aPolyPolygon, aRect); + } + + // Convert points into seps. + if(pRetval) + { + pRetval->processPoints(); + + // Optimize list of bands. Adjacent bands with identical lists + // of seps are joined. + if(!pRetval->OptimizeBandList()) + { + pRetval.reset(); + } + } + } + } + } + + return pRetval; +} + +tools::PolyPolygon vcl::Region::ImplCreatePolyPolygonFromRegionBand() const +{ + tools::PolyPolygon aRetval; + + if(getRegionBand()) + { + RectangleVector aRectangles; + GetRegionRectangles(aRectangles); + + for (auto const& rectangle : aRectangles) + { + aRetval.Insert( tools::Polygon(rectangle) ); + } + } + else + { + OSL_ENSURE(false, "Called with no local RegionBand (!)"); + } + + return aRetval; +} + +basegfx::B2DPolyPolygon vcl::Region::ImplCreateB2DPolyPolygonFromRegionBand() const +{ + tools::PolyPolygon aPoly(ImplCreatePolyPolygonFromRegionBand()); + + return aPoly.getB2DPolyPolygon(); +} + +Region::Region(bool bIsNull) +: mbIsNull(bIsNull) +{ +} + +Region::Region(const tools::Rectangle& rRect) +: mbIsNull(false) +{ + if (!rRect.IsEmpty()) + mpRegionBand = std::make_shared<RegionBand>(rRect); +} + +Region::Region(const tools::Polygon& rPolygon) +: mbIsNull(false) +{ + + if(rPolygon.GetSize()) + { + ImplCreatePolyPolyRegion(tools::PolyPolygon(rPolygon)); + } +} + +Region::Region(const tools::PolyPolygon& rPolyPoly) +: mbIsNull(false) +{ + + if(rPolyPoly.Count()) + { + ImplCreatePolyPolyRegion(rPolyPoly); + } +} + +Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly) +: mbIsNull(false) +{ + + if(rPolyPoly.count()) + { + ImplCreatePolyPolyRegion(rPolyPoly); + } +} + +Region::Region(const vcl::Region&) = default; + +Region::Region(vcl::Region&& rRegion) noexcept +: mpB2DPolyPolygon(std::move(rRegion.mpB2DPolyPolygon)), + mpPolyPolygon(std::move(rRegion.mpPolyPolygon)), + mpRegionBand(std::move(rRegion.mpRegionBand)), + mbIsNull(rRegion.mbIsNull) +{ + rRegion.mbIsNull = true; +} + +Region::~Region() = default; + +void vcl::Region::ImplCreatePolyPolyRegion( const tools::PolyPolygon& rPolyPoly ) +{ + const sal_uInt16 nPolyCount = rPolyPoly.Count(); + + if(!nPolyCount) + return; + + // polypolygon empty? -> empty region + const tools::Rectangle aRect(rPolyPoly.GetBoundRect()); + + if(aRect.IsEmpty()) + return; + + // width OR height == 1 ? => Rectangular region + if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect()) + { + mpRegionBand = std::make_shared<RegionBand>(aRect); + } + else + { + mpPolyPolygon = rPolyPoly; + } + + mbIsNull = false; +} + +void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly ) +{ + if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty()) + { + mpB2DPolyPolygon = rPolyPoly; + mbIsNull = false; + } +} + +void vcl::Region::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + if(IsNull() || IsEmpty()) + { + // empty or null need no move + return; + } + + if(!nHorzMove && !nVertMove) + { + // no move defined + return; + } + + if(getB2DPolyPolygon()) + { + basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon()); + + aPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(nHorzMove, nVertMove)); + if (aPoly.count()) + mpB2DPolyPolygon = aPoly; + else + mpB2DPolyPolygon.reset(); + mpPolyPolygon.reset(); + mpRegionBand.reset(); + } + else if(getPolyPolygon()) + { + tools::PolyPolygon aPoly(*getPolyPolygon()); + + aPoly.Move(nHorzMove, nVertMove); + mpB2DPolyPolygon.reset(); + if (aPoly.Count()) + mpPolyPolygon = aPoly; + else + mpPolyPolygon.reset(); + mpRegionBand.reset(); + } + else if(getRegionBand()) + { + std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand()); + + pNew->Move(nHorzMove, nVertMove); + mpB2DPolyPolygon.reset(); + mpPolyPolygon.reset(); + mpRegionBand = std::move(pNew); + } + else + { + OSL_ENSURE(false, "Region::Move error: impossible combination (!)"); + } +} + +void vcl::Region::Scale( double fScaleX, double fScaleY ) +{ + if(IsNull() || IsEmpty()) + { + // empty or null need no scale + return; + } + + if(basegfx::fTools::equalZero(fScaleX) && basegfx::fTools::equalZero(fScaleY)) + { + // no scale defined + return; + } + + if(getB2DPolyPolygon()) + { + basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon()); + + aPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fScaleX, fScaleY)); + if (aPoly.count()) + mpB2DPolyPolygon = aPoly; + else + mpB2DPolyPolygon.reset(); + mpPolyPolygon.reset(); + mpRegionBand.reset(); + } + else if(getPolyPolygon()) + { + tools::PolyPolygon aPoly(*getPolyPolygon()); + + aPoly.Scale(fScaleX, fScaleY); + mpB2DPolyPolygon.reset(); + if (aPoly.Count()) + mpPolyPolygon = aPoly; + else + mpPolyPolygon.reset(); + mpRegionBand.reset(); + } + else if(getRegionBand()) + { + std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand()); + + pNew->Scale(fScaleX, fScaleY); + mpB2DPolyPolygon.reset(); + mpPolyPolygon.reset(); + mpRegionBand = std::move(pNew); + } + else + { + OSL_ENSURE(false, "Region::Scale error: impossible combination (!)"); + } +} + +void vcl::Region::Union( const tools::Rectangle& rRect ) +{ + if(rRect.IsEmpty()) + { + // empty rectangle will not expand the existing union, nothing to do + return; + } + + if(IsEmpty()) + { + // no local data, the union will be equal to source. Create using rectangle + *this = rRect; + return; + } + + if(HasPolyPolygonOrB2DPolyPolygon()) + { + // get this B2DPolyPolygon, solve on polygon base + basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon()); + + aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly); + + if(!aThisPolyPoly.count()) + { + // no local polygon, use the rectangle as new region + *this = rRect; + } + else + { + // get the other B2DPolyPolygon and use logical Or-Operation + const basegfx::B2DPolygon aRectPoly( + basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rRect))); + const basegfx::B2DPolyPolygon aClip( + basegfx::utils::solvePolygonOperationOr( + aThisPolyPoly, + basegfx::B2DPolyPolygon(aRectPoly))); + *this = vcl::Region(aClip); + } + + return; + } + + // only region band mode possibility left here or null/empty + const RegionBand* pCurrent = getRegionBand(); + + if(!pCurrent) + { + // no region band, create using the rectangle + *this = rRect; + return; + } + + std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*pCurrent); + + // get justified rectangle + const tools::Long nLeft(std::min(rRect.Left(), rRect.Right())); + const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom())); + const tools::Long nRight(std::max(rRect.Left(), rRect.Right())); + const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom())); + + // insert bands if the boundaries are not already in the list + pNew->InsertBands(nTop, nBottom); + + // process union + pNew->Union(nLeft, nTop, nRight, nBottom); + + // cleanup + if(!pNew->OptimizeBandList()) + { + pNew.reset(); + } + + mpRegionBand = std::move(pNew); +} + +void vcl::Region::Intersect( const tools::Rectangle& rRect ) +{ + if ( rRect.IsEmpty() ) + { + // empty rectangle will create empty region + SetEmpty(); + return; + } + + if(IsNull()) + { + // null region (everything) intersect with rect will give rect + *this = rRect; + return; + } + + if(IsEmpty()) + { + // no content, cannot get more empty + return; + } + + if(HasPolyPolygonOrB2DPolyPolygon()) + { + // if polygon data prefer double precision, the other will be lost (if buffered) + if(getB2DPolyPolygon()) + { + const basegfx::B2DPolyPolygon aPoly( + basegfx::utils::clipPolyPolygonOnRange( + *getB2DPolyPolygon(), + basegfx::B2DRange( + rRect.Left(), + rRect.Top(), + rRect.Right() + 1, + rRect.Bottom() + 1), + true, + false)); + + if (aPoly.count()) + mpB2DPolyPolygon = aPoly; + else + mpB2DPolyPolygon.reset(); + mpPolyPolygon.reset(); + mpRegionBand.reset(); + } + else // if(getPolyPolygon()) + { + tools::PolyPolygon aPoly(*getPolyPolygon()); + + // use the PolyPolygon::Clip method for rectangles, this is + // fairly simple (does not even use GPC) and saves us from + // unnecessary banding + aPoly.Clip(rRect); + + mpB2DPolyPolygon.reset(); + if (aPoly.Count()) + mpPolyPolygon = aPoly; + else + mpPolyPolygon.reset(); + mpRegionBand.reset(); + } + + return; + } + + // only region band mode possibility left here or null/empty + const RegionBand* pCurrent = getRegionBand(); + + if(!pCurrent) + { + // region is empty -> nothing to do! + return; + } + + std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent)); + + // get justified rectangle + const tools::Long nLeft(std::min(rRect.Left(), rRect.Right())); + const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom())); + const tools::Long nRight(std::max(rRect.Left(), rRect.Right())); + const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom())); + + // insert bands if the boundaries are not already in the list + pNew->InsertBands(nTop, nBottom); + + // process intersect + pNew->Intersect(nLeft, nTop, nRight, nBottom); + + // cleanup + if(!pNew->OptimizeBandList()) + { + pNew.reset(); + } + + mpRegionBand = std::move(pNew); +} + +void vcl::Region::Exclude( const tools::Rectangle& rRect ) +{ + if ( rRect.IsEmpty() ) + { + // excluding nothing will do no change + return; + } + + if(IsEmpty()) + { + // cannot exclude from empty, done + return; + } + + if(IsNull()) + { + // error; cannot exclude from null region since this is not representable + // in the data + OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)"); + return; + } + + if( HasPolyPolygonOrB2DPolyPolygon() ) + { + // get this B2DPolyPolygon + basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon()); + + aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly); + + if(!aThisPolyPoly.count()) + { + // when local polygon is empty, nothing can be excluded + return; + } + + // get the other B2DPolyPolygon + const basegfx::B2DPolygon aRectPoly( + basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rRect))); + const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly); + const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff(aThisPolyPoly, aOtherPolyPoly); + + *this = vcl::Region(aClip); + + return; + } + + // only region band mode possibility left here or null/empty + if(!mpRegionBand) + { + // empty? -> done! + return; + } + + std::shared_ptr<RegionBand>& pNew = mpRegionBand; + // only make a copy if someone else is also using it + if (pNew.use_count() > 1) + pNew = std::make_shared<RegionBand>(*pNew); + + // get justified rectangle + const tools::Long nLeft(std::min(rRect.Left(), rRect.Right())); + const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom())); + const tools::Long nRight(std::max(rRect.Left(), rRect.Right())); + const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom())); + + // insert bands if the boundaries are not already in the list + pNew->InsertBands(nTop, nBottom); + + // process exclude + pNew->Exclude(nLeft, nTop, nRight, nBottom); + + // cleanup + if(!pNew->OptimizeBandList()) + pNew.reset(); +} + +void vcl::Region::XOr( const tools::Rectangle& rRect ) +{ + if ( rRect.IsEmpty() ) + { + // empty rectangle will not change local content + return; + } + + if(IsEmpty()) + { + // rRect will be the xored-form (local off, rect on) + *this = rRect; + return; + } + + if(IsNull()) + { + // error; cannot exclude from null region since this is not representable + // in the data + OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)"); + return; + } + + if( HasPolyPolygonOrB2DPolyPolygon() ) + { + // get this B2DPolyPolygon + basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon()); + + aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly ); + + if(!aThisPolyPoly.count()) + { + // no local content, XOr will be equal to rectangle + *this = rRect; + return; + } + + // get the other B2DPolyPolygon + const basegfx::B2DPolygon aRectPoly( + basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rRect))); + const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly); + const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor(aThisPolyPoly, aOtherPolyPoly); + + *this = vcl::Region(aClip); + + return; + } + + // only region band mode possibility left here or null/empty + const RegionBand* pCurrent = getRegionBand(); + + if(!pCurrent) + { + // rRect will be the xored-form (local off, rect on) + *this = rRect; + return; + } + + // only region band mode possibility left here or null/empty + std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*getRegionBand())); + + // get justified rectangle + const tools::Long nLeft(std::min(rRect.Left(), rRect.Right())); + const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom())); + const tools::Long nRight(std::max(rRect.Left(), rRect.Right())); + const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom())); + + // insert bands if the boundaries are not already in the list + pNew->InsertBands(nTop, nBottom); + + // process xor + pNew->XOr(nLeft, nTop, nRight, nBottom); + + // cleanup + if(!pNew->OptimizeBandList()) + { + pNew.reset(); + } + + mpRegionBand = std::move(pNew); +} + +void vcl::Region::Union( const vcl::Region& rRegion ) +{ + if(rRegion.IsEmpty()) + { + // no extension at all + return; + } + + if(rRegion.IsNull()) + { + // extending with null region -> null region + *this = vcl::Region(true); + return; + } + + if(IsEmpty()) + { + // local is empty, union will give source region + *this = rRegion; + return; + } + + if(IsNull()) + { + // already fully expanded (is null region), cannot be extended + return; + } + + if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() ) + { + // get this B2DPolyPolygon + basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon()); + + aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly); + + if(!aThisPolyPoly.count()) + { + // when no local content, union will be equal to rRegion + *this = rRegion; + return; + } + + // get the other B2DPolyPolygon + basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon()); + aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation(aOtherPolyPoly); + + // use logical OR operation + basegfx::B2DPolyPolygon aClip(basegfx::utils::solvePolygonOperationOr(aThisPolyPoly, aOtherPolyPoly)); + + *this = vcl::Region( aClip ); + return; + } + + // only region band mode possibility left here or null/empty + const RegionBand* pCurrent = getRegionBand(); + + if(!pCurrent) + { + // local is empty, union will give source region + *this = rRegion; + return; + } + + const RegionBand* pSource = rRegion.getRegionBand(); + + if(!pSource) + { + // no extension at all + return; + } + + // prepare source and target + std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent)); + + // union with source + pNew->Union(*pSource); + + // cleanup + if(!pNew->OptimizeBandList()) + { + pNew.reset(); + } + + mpRegionBand = std::move(pNew); +} + +void vcl::Region::Intersect( const vcl::Region& rRegion ) +{ + // same instance data? -> nothing to do! + if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon()) + { + return; + } + + if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon()) + { + return; + } + + if(getRegionBand() && getRegionBand() == rRegion.getRegionBand()) + { + return; + } + + if(rRegion.IsNull()) + { + // source region is null-region, intersect will not change local region + return; + } + + if(IsNull()) + { + // when local region is null-region, intersect will be equal to source + *this = rRegion; + return; + } + + if(rRegion.IsEmpty()) + { + // source region is empty, intersection will always be empty + SetEmpty(); + return; + } + + if(IsEmpty()) + { + // local region is empty, cannot get more empty than that. Nothing to do + return; + } + + if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() ) + { + // get this B2DPolyPolygon + basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon()); + + if(!aThisPolyPoly.count()) + { + // local region is empty, cannot get more empty than that. Nothing to do + return; + } + + // get the other B2DPolyPolygon + basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon()); + + if(!aOtherPolyPoly.count()) + { + // source region is empty, intersection will always be empty + SetEmpty(); + return; + } + + static size_t gPointLimit = !utl::ConfigManager::IsFuzzing() ? SAL_MAX_SIZE : 8192; + size_t nPointLimit(gPointLimit); + const basegfx::B2DPolyPolygon aClip( + basegfx::utils::clipPolyPolygonOnPolyPolygon( + aOtherPolyPoly, + aThisPolyPoly, + true, + false, + &nPointLimit)); + *this = vcl::Region( aClip ); + return; + } + + // only region band mode possibility left here or null/empty + const RegionBand* pCurrent = getRegionBand(); + + if(!pCurrent) + { + // local region is empty, cannot get more empty than that. Nothing to do + return; + } + + const RegionBand* pSource = rRegion.getRegionBand(); + + if(!pSource) + { + // source region is empty, intersection will always be empty + SetEmpty(); + return; + } + + // both RegionBands exist and are not empty + if(pCurrent->getRectangleCount() + 2 < pSource->getRectangleCount()) + { + // when we have less rectangles, turn around the call + vcl::Region aTempRegion = rRegion; + aTempRegion.Intersect( *this ); + *this = aTempRegion; + } + else + { + // prepare new regionBand + std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent)); + + // intersect with source + pNew->Intersect(*pSource); + + // cleanup + if(!pNew->OptimizeBandList()) + { + pNew.reset(); + } + + mpRegionBand = std::move(pNew); + } +} + +void vcl::Region::Exclude( const vcl::Region& rRegion ) +{ + if ( rRegion.IsEmpty() ) + { + // excluding nothing will do no change + return; + } + + if ( rRegion.IsNull() ) + { + // excluding everything will create empty region + SetEmpty(); + return; + } + + if(IsEmpty()) + { + // cannot exclude from empty, done + return; + } + + if(IsNull()) + { + // error; cannot exclude from null region since this is not representable + // in the data + OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)"); + return; + } + + if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() ) + { + // get this B2DPolyPolygon + basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon()); + + if(!aThisPolyPoly.count()) + { + // cannot exclude from empty, done + return; + } + + aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly ); + + // get the other B2DPolyPolygon + basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon()); + aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly ); + + basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff( aThisPolyPoly, aOtherPolyPoly ); + *this = vcl::Region( aClip ); + return; + } + + // only region band mode possibility left here or null/empty + const RegionBand* pCurrent = getRegionBand(); + + if(!pCurrent) + { + // cannot exclude from empty, done + return; + } + + const RegionBand* pSource = rRegion.getRegionBand(); + + if(!pSource) + { + // excluding nothing will do no change + return; + } + + // prepare source and target + std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent)); + + // union with source + const bool bSuccess(pNew->Exclude(*pSource)); + + // cleanup + if(!bSuccess) + { + pNew.reset(); + } + + mpRegionBand = std::move(pNew); +} + +bool vcl::Region::XOr( const vcl::Region& rRegion ) +{ + if ( rRegion.IsEmpty() ) + { + // empty region will not change local content + return true; + } + + if ( rRegion.IsNull() ) + { + // error; cannot exclude null region from local since this is not representable + // in the data + OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)"); + return true; + } + + if(IsEmpty()) + { + // rRect will be the xored-form (local off, rect on) + *this = rRegion; + return true; + } + + if(IsNull()) + { + // error: cannot exclude from null region since this is not representable + // in the data + OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)"); + return false; + } + + if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() ) + { + // get this B2DPolyPolygon + basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon()); + + if(!aThisPolyPoly.count()) + { + // rRect will be the xored-form (local off, rect on) + *this = rRegion; + return true; + } + + aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly ); + + // get the other B2DPolyPolygon + basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon()); + aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly ); + + basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor( aThisPolyPoly, aOtherPolyPoly ); + *this = vcl::Region( aClip ); + return true; + } + + // only region band mode possibility left here or null/empty + const RegionBand* pCurrent = getRegionBand(); + + if(!pCurrent) + { + // rRect will be the xored-form (local off, rect on) + *this = rRegion; + return true; + } + + const RegionBand* pSource = rRegion.getRegionBand(); + + if(!pSource) + { + // empty region will not change local content + return true; + } + + // prepare source and target + std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent)); + + // union with source + pNew->XOr(*pSource); + + // cleanup + if(!pNew->OptimizeBandList()) + { + pNew.reset(); + } + + mpRegionBand = std::move(pNew); + + return true; +} + +tools::Rectangle vcl::Region::GetBoundRect() const +{ + if(IsEmpty()) + { + // no internal data? -> region is empty! + return tools::Rectangle(); + } + + if(IsNull()) + { + // error; null region has no BoundRect + // OSL_ENSURE(false, "Region::GetBoundRect error: null region has unlimited bound rect, not representable (!)"); + return tools::Rectangle(); + } + + // prefer double precision source + if(getB2DPolyPolygon()) + { + const basegfx::B2DRange aRange(basegfx::utils::getRange(*getB2DPolyPolygon())); + + if(aRange.isEmpty()) + { + // emulate PolyPolygon::GetBoundRect() when empty polygon + return tools::Rectangle(); + } + else + { + // #i122149# corrected rounding, no need for ceil() and floor() here + return tools::Rectangle( + basegfx::fround(aRange.getMinX()), basegfx::fround(aRange.getMinY()), + basegfx::fround(aRange.getMaxX()), basegfx::fround(aRange.getMaxY())); + } + } + + if(getPolyPolygon()) + { + return getPolyPolygon()->GetBoundRect(); + } + + if(getRegionBand()) + { + return getRegionBand()->GetBoundRect(); + } + + return tools::Rectangle(); +} + +tools::PolyPolygon vcl::Region::GetAsPolyPolygon() const +{ + if(getPolyPolygon()) + { + return *getPolyPolygon(); + } + + if(getB2DPolyPolygon()) + { + // the polygon needs to be converted, buffer the down conversion + const tools::PolyPolygon aPolyPolgon(*getB2DPolyPolygon()); + const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon; + + return *getPolyPolygon(); + } + + if(getRegionBand()) + { + // the BandRegion needs to be converted, buffer the conversion + const tools::PolyPolygon aPolyPolgon(ImplCreatePolyPolygonFromRegionBand()); + const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon; + + return *getPolyPolygon(); + } + + return tools::PolyPolygon(); +} + +basegfx::B2DPolyPolygon vcl::Region::GetAsB2DPolyPolygon() const +{ + if(getB2DPolyPolygon()) + { + return *getB2DPolyPolygon(); + } + + if(getPolyPolygon()) + { + // the polygon needs to be converted, buffer the up conversion. This will be preferred from now. + const basegfx::B2DPolyPolygon aB2DPolyPolygon(getPolyPolygon()->getB2DPolyPolygon()); + const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon; + + return *getB2DPolyPolygon(); + } + + if(getRegionBand()) + { + // the BandRegion needs to be converted, buffer the conversion + const basegfx::B2DPolyPolygon aB2DPolyPolygon(ImplCreateB2DPolyPolygonFromRegionBand()); + const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon; + + return *getB2DPolyPolygon(); + } + + return basegfx::B2DPolyPolygon(); +} + +const RegionBand* vcl::Region::GetAsRegionBand() const +{ + if(!getRegionBand()) + { + if(getB2DPolyPolygon()) + { + // convert B2DPolyPolygon to RegionBand, buffer it and return it + const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(tools::PolyPolygon(*getB2DPolyPolygon())); + } + else if(getPolyPolygon()) + { + // convert B2DPolyPolygon to RegionBand, buffer it and return it + const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(*getPolyPolygon()); + } + } + + return getRegionBand(); +} + +bool vcl::Region::Contains( const Point& rPoint ) const +{ + if(IsEmpty()) + { + // no point can be in empty region + return false; + } + + if(IsNull()) + { + // all points are inside null-region + return true; + } + + // Too expensive (?) + //if(mpImplRegion->getRegionPolyPoly()) + //{ + // return mpImplRegion->getRegionPolyPoly()->Contains( rPoint ); + //} + + // ensure RegionBand existence + const RegionBand* pRegionBand = GetAsRegionBand(); + + if(pRegionBand) + { + return pRegionBand->Contains(rPoint); + } + + return false; +} + +bool vcl::Region::Overlaps( const tools::Rectangle& rRect ) const +{ + if(IsEmpty()) + { + // nothing can be over something empty + return false; + } + + if(IsNull()) + { + // everything is over null region + return true; + } + + // Can we optimize this ??? - is used in StarDraw for brushes pointers + // Why we have no IsOver for Regions ??? + // create region from rectangle and intersect own region + vcl::Region aRegion(rRect); + aRegion.Intersect( *this ); + + // rectangle is over if include is not empty + return !aRegion.IsEmpty(); +} + +bool vcl::Region::IsRectangle() const +{ + if( IsEmpty() || IsNull() ) + return false; + + if( getB2DPolyPolygon() ) + return basegfx::utils::isRectangle( *getB2DPolyPolygon() ); + + if( getPolyPolygon() ) + return getPolyPolygon()->IsRect(); + + if( getRegionBand() ) + return (getRegionBand()->getRectangleCount() == 1); + + return false; +} + +void vcl::Region::SetNull() +{ + // reset all content + mpB2DPolyPolygon.reset(); + mpPolyPolygon.reset(); + mpRegionBand.reset(); + mbIsNull = true; +} + +void vcl::Region::SetEmpty() +{ + // reset all content + mpB2DPolyPolygon.reset(); + mpPolyPolygon.reset(); + mpRegionBand.reset(); + mbIsNull = false; +} + +Region& vcl::Region::operator=( const vcl::Region& ) = default; + +Region& vcl::Region::operator=( vcl::Region&& rRegion ) noexcept +{ + mpB2DPolyPolygon = std::move(rRegion.mpB2DPolyPolygon); + mpPolyPolygon = std::move(rRegion.mpPolyPolygon); + mpRegionBand = std::move(rRegion.mpRegionBand); + mbIsNull = rRegion.mbIsNull; + rRegion.mbIsNull = true; + + return *this; +} + +Region& vcl::Region::operator=( const tools::Rectangle& rRect ) +{ + mpB2DPolyPolygon.reset(); + mpPolyPolygon.reset(); + if (!rRect.IsEmpty()) + mpRegionBand = std::make_shared<RegionBand>(rRect); + else + mpRegionBand.reset(); + mbIsNull = false; + + return *this; +} + +bool vcl::Region::operator==( const vcl::Region& rRegion ) const +{ + if(IsNull() && rRegion.IsNull()) + { + // both are null region + return true; + } + + if(IsEmpty() && rRegion.IsEmpty()) + { + // both are empty + return true; + } + + if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon()) + { + // same instance data? -> equal + return true; + } + + if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon()) + { + // same instance data? -> equal + return true; + } + + if(getRegionBand() && getRegionBand() == rRegion.getRegionBand()) + { + // same instance data? -> equal + return true; + } + + if(IsNull() || IsEmpty()) + { + return false; + } + + if(rRegion.IsNull() || rRegion.IsEmpty()) + { + return false; + } + + if(rRegion.getB2DPolyPolygon() || getB2DPolyPolygon()) + { + // one of both has a B2DPolyPolygon based region, ensure both have it + // by evtl. conversion + GetAsB2DPolyPolygon(); + rRegion.GetAsB2DPolyPolygon(); + + return *rRegion.getB2DPolyPolygon() == *getB2DPolyPolygon(); + } + + if(rRegion.getPolyPolygon() || getPolyPolygon()) + { + // one of both has a B2DPolyPolygon based region, ensure both have it + // by evtl. conversion + GetAsPolyPolygon(); + rRegion.GetAsPolyPolygon(); + + return *rRegion.getPolyPolygon() == *getPolyPolygon(); + } + + // both are not empty or null (see above) and if content supported polygon + // data the comparison is already done. Only both on RegionBand base can be left, + // but better check + if(rRegion.getRegionBand() && getRegionBand()) + { + return *rRegion.getRegionBand() == *getRegionBand(); + } + + // should not happen, but better deny equality + return false; +} + +SvStream& ReadRegion(SvStream& rIStrm, vcl::Region& rRegion) +{ + VersionCompatRead aCompat(rIStrm); + sal_uInt16 nVersion(0); + sal_uInt16 nTmp16(0); + + // clear region to be loaded + rRegion.SetEmpty(); + + // get version of streamed region + rIStrm.ReadUInt16( nVersion ); + + // get type of region + rIStrm.ReadUInt16( nTmp16 ); + + enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX }; + auto eStreamedType = nTmp16; + + switch (eStreamedType) + { + case REGION_NULL: + { + rRegion.SetNull(); + break; + } + + case REGION_EMPTY: + { + rRegion.SetEmpty(); + break; + } + + default: + { + std::shared_ptr<RegionBand> xNewRegionBand(std::make_shared<RegionBand>()); + bool bSuccess = xNewRegionBand->load(rIStrm); + rRegion.mpRegionBand = xNewRegionBand; + + bool bHasPolyPolygon(false); + if (aCompat.GetVersion() >= 2) + { + rIStrm.ReadCharAsBool( bHasPolyPolygon ); + + if (bHasPolyPolygon) + { + tools::PolyPolygon aNewPoly; + ReadPolyPolygon(rIStrm, aNewPoly); + const auto nPolygons = aNewPoly.Count(); + if (nPolygons > 128) + { + SAL_WARN("vcl.gdi", "suspiciously high no of polygons in clip:" << nPolygons); + if (utl::ConfigManager::IsFuzzing()) + aNewPoly.Clear(); + } + rRegion.mpPolyPolygon = aNewPoly; + } + } + + if (!bSuccess && !bHasPolyPolygon) + { + SAL_WARN("vcl.gdi", "bad region band:" << bHasPolyPolygon); + rRegion.SetNull(); + } + + break; + } + } + + return rIStrm; +} + +SvStream& WriteRegion( SvStream& rOStrm, const vcl::Region& rRegion ) +{ + const sal_uInt16 nVersion(2); + VersionCompatWrite aCompat(rOStrm, nVersion); + + // put version + rOStrm.WriteUInt16( nVersion ); + + // put type + enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX }; + RegionType aRegionType(REGION_COMPLEX); + bool bEmpty(rRegion.IsEmpty()); + + if(!bEmpty && rRegion.getB2DPolyPolygon() && 0 == rRegion.getB2DPolyPolygon()->count()) + { + OSL_ENSURE(false, "Region with empty B2DPolyPolygon, should not be created (!)"); + bEmpty = true; + } + + if(!bEmpty && rRegion.getPolyPolygon() && 0 == rRegion.getPolyPolygon()->Count()) + { + OSL_ENSURE(false, "Region with empty PolyPolygon, should not be created (!)"); + bEmpty = true; + } + + if(bEmpty) + { + aRegionType = REGION_EMPTY; + } + else if(rRegion.IsNull()) + { + aRegionType = REGION_NULL; + } + else if(rRegion.getRegionBand() && rRegion.getRegionBand()->isSingleRectangle()) + { + aRegionType = REGION_RECTANGLE; + } + + rOStrm.WriteUInt16( aRegionType ); + + // get RegionBand + const RegionBand* pRegionBand = rRegion.getRegionBand(); + + if(pRegionBand) + { + pRegionBand->save(rOStrm); + } + else + { + // for compatibility, write an empty RegionBand (will only write + // the end marker STREAMENTRY_END, but this *is* needed) + const RegionBand aRegionBand; + + aRegionBand.save(rOStrm); + } + + // write polypolygon if available + const bool bHasPolyPolygon(rRegion.HasPolyPolygonOrB2DPolyPolygon()); + rOStrm.WriteBool( bHasPolyPolygon ); + + if(bHasPolyPolygon) + { + // #i105373# + tools::PolyPolygon aNoCurvePolyPolygon; + rRegion.GetAsPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon); + + WritePolyPolygon( rOStrm, aNoCurvePolyPolygon ); + } + + return rOStrm; +} + +void vcl::Region::GetRegionRectangles(RectangleVector& rTarget) const +{ + // clear returnvalues + rTarget.clear(); + + // ensure RegionBand existence + const RegionBand* pRegionBand = GetAsRegionBand(); + + if(pRegionBand) + { + pRegionBand->GetRegionRectangles(rTarget); + } +} + +static bool ImplPolygonRectTest( const tools::Polygon& rPoly, tools::Rectangle* pRectOut = nullptr ) +{ + bool bIsRect = false; + const Point* pPoints = rPoly.GetConstPointAry(); + sal_uInt16 nPoints = rPoly.GetSize(); + + if( nPoints == 4 || (nPoints == 5 && pPoints[0] == pPoints[4]) ) + { + tools::Long nX1 = pPoints[0].X(), nX2 = pPoints[2].X(), nY1 = pPoints[0].Y(), nY2 = pPoints[2].Y(); + + if( ( (pPoints[1].X() == nX1 && pPoints[3].X() == nX2) && (pPoints[1].Y() == nY2 && pPoints[3].Y() == nY1) ) + || ( (pPoints[1].X() == nX2 && pPoints[3].X() == nX1) && (pPoints[1].Y() == nY1 && pPoints[3].Y() == nY2) ) ) + { + bIsRect = true; + + if( pRectOut ) + { + tools::Long nSwap; + + if( nX2 < nX1 ) + { + nSwap = nX2; + nX2 = nX1; + nX1 = nSwap; + } + + if( nY2 < nY1 ) + { + nSwap = nY2; + nY2 = nY1; + nY1 = nSwap; + } + + if( nX2 != nX1 ) + { + nX2--; + } + + if( nY2 != nY1 ) + { + nY2--; + } + + pRectOut->SetLeft( nX1 ); + pRectOut->SetRight( nX2 ); + pRectOut->SetTop( nY1 ); + pRectOut->SetBottom( nY2 ); + } + } + } + + return bIsRect; +} + +vcl::Region vcl::Region::GetRegionFromPolyPolygon( const tools::PolyPolygon& rPolyPoly ) +{ + //return vcl::Region( rPolyPoly ); + + // check if it's worth extracting the XOr'ing the Rectangles + // empiricism shows that break even between XOr'ing rectangles separately + // and ImplCreateRegionBandFromPolyPolygon is at half rectangles/half polygons + int nPolygonRects = 0, nPolygonPolygons = 0; + int nPolygons = rPolyPoly.Count(); + + for( int i = 0; i < nPolygons; i++ ) + { + const tools::Polygon& rPoly = rPolyPoly[i]; + + if( ImplPolygonRectTest( rPoly ) ) + { + nPolygonRects++; + } + else + { + nPolygonPolygons++; + } + } + + if( nPolygonPolygons > nPolygonRects ) + { + return vcl::Region( rPolyPoly ); + } + + vcl::Region aResult; + tools::Rectangle aRect; + + for( int i = 0; i < nPolygons; i++ ) + { + const tools::Polygon& rPoly = rPolyPoly[i]; + + if( ImplPolygonRectTest( rPoly, &aRect ) ) + { + aResult.XOr( aRect ); + } + else + { + aResult.XOr( vcl::Region(rPoly) ); + } + } + + return aResult; +} + +} /* namespace vcl */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/regionband.cxx b/vcl/source/gdi/regionband.cxx new file mode 100644 index 0000000000..7b48df7a09 --- /dev/null +++ b/vcl/source/gdi/regionband.cxx @@ -0,0 +1,1365 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cstdlib> + +#include <tools/stream.hxx> +#include <regionband.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +RegionBand::RegionBand() +: mpFirstBand(nullptr), + mpLastCheckedBand(nullptr) +{ +} + +RegionBand::RegionBand(const RegionBand& rRef) +: mpFirstBand(nullptr), + mpLastCheckedBand(nullptr) +{ + *this = rRef; +} + +RegionBand& RegionBand::operator=(const RegionBand& rRef) +{ + if (this != &rRef) + { + ImplRegionBand* pPrevBand = nullptr; + ImplRegionBand* pBand = rRef.mpFirstBand; + + while(pBand) + { + ImplRegionBand* pNewBand = new ImplRegionBand(*pBand); + + // first element? -> set as first into the list + if(pBand == rRef.mpFirstBand) + { + mpFirstBand = pNewBand; + } + else + { + pPrevBand->mpNextBand = pNewBand; + } + + pPrevBand = pNewBand; + pBand = pBand->mpNextBand; + } + } + return *this; +} + +RegionBand::RegionBand(const tools::Rectangle& rRect) +: mpFirstBand(nullptr), + mpLastCheckedBand(nullptr) +{ + const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom())); + const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom())); + const tools::Long nLeft(std::min(rRect.Left(), rRect.Right())); + const tools::Long nRight(std::max(rRect.Left(), rRect.Right())); + + // add band with boundaries of the rectangle + mpFirstBand = new ImplRegionBand(nTop, nBottom); + + // Set left and right boundaries of the band + mpFirstBand->Union(nLeft, nRight); + +} + +void RegionBand::implReset() +{ + ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + ImplRegionBand* pTempBand = pBand->mpNextBand; + delete pBand; + pBand = pTempBand; + } + + mpLastCheckedBand = nullptr; + mpFirstBand = nullptr; +} + +RegionBand::~RegionBand() +{ + implReset(); +} + +bool RegionBand::operator==( const RegionBand& rRegionBand ) const +{ + + // initialise pointers + ImplRegionBand* pOwnRectBand = mpFirstBand; + ImplRegionBandSep* pOwnRectBandSep = pOwnRectBand->mpFirstSep; + ImplRegionBand* pSecondRectBand = rRegionBand.mpFirstBand; + ImplRegionBandSep* pSecondRectBandSep = pSecondRectBand->mpFirstSep; + + while ( pOwnRectBandSep && pSecondRectBandSep ) + { + // get boundaries of current rectangle + tools::Long nOwnXLeft = pOwnRectBandSep->mnXLeft; + tools::Long nSecondXLeft = pSecondRectBandSep->mnXLeft; + + if ( nOwnXLeft != nSecondXLeft ) + { + return false; + } + + tools::Long nOwnYTop = pOwnRectBand->mnYTop; + tools::Long nSecondYTop = pSecondRectBand->mnYTop; + + if ( nOwnYTop != nSecondYTop ) + { + return false; + } + + tools::Long nOwnXRight = pOwnRectBandSep->mnXRight; + tools::Long nSecondXRight = pSecondRectBandSep->mnXRight; + + if ( nOwnXRight != nSecondXRight ) + { + return false; + } + + tools::Long nOwnYBottom = pOwnRectBand->mnYBottom; + tools::Long nSecondYBottom = pSecondRectBand->mnYBottom; + + if ( nOwnYBottom != nSecondYBottom ) + { + return false; + } + + // get next separation from current band + pOwnRectBandSep = pOwnRectBandSep->mpNextSep; + + // no separation found? -> go to next band! + if ( !pOwnRectBandSep ) + { + // get next band + pOwnRectBand = pOwnRectBand->mpNextBand; + + // get first separation in current band + if( pOwnRectBand ) + { + pOwnRectBandSep = pOwnRectBand->mpFirstSep; + } + } + + // get next separation from current band + pSecondRectBandSep = pSecondRectBandSep->mpNextSep; + + // no separation found? -> go to next band! + if ( !pSecondRectBandSep ) + { + // get next band + pSecondRectBand = pSecondRectBand->mpNextBand; + + // get first separation in current band + if( pSecondRectBand ) + { + pSecondRectBandSep = pSecondRectBand->mpFirstSep; + } + } + + if ( pOwnRectBandSep && !pSecondRectBandSep ) + { + return false; + } + + if ( !pOwnRectBandSep && pSecondRectBandSep ) + { + return false; + } + } + + return true; +} + +namespace { + +enum StreamEntryType { STREAMENTRY_BANDHEADER, STREAMENTRY_SEPARATION, STREAMENTRY_END }; + +} + +bool RegionBand::load(SvStream& rIStrm) +{ + // clear this instance data + implReset(); + + // get all bands + ImplRegionBand* pCurrBand = nullptr; + + // get header from first element + sal_uInt16 nTmp16(STREAMENTRY_END); + rIStrm.ReadUInt16(nTmp16); + + if (STREAMENTRY_END == static_cast<StreamEntryType>(nTmp16)) + return false; + + size_t nRecordsPossible = rIStrm.remainingSize() / (2*sizeof(sal_Int32)); + if (!nRecordsPossible) + { + OSL_ENSURE(false, "premature end of region stream" ); + implReset(); + return false; + } + + do + { + // insert new band or new separation? + if(STREAMENTRY_BANDHEADER == static_cast<StreamEntryType>(nTmp16)) + { + sal_Int32 nYTop(0); + sal_Int32 nYBottom(0); + + rIStrm.ReadInt32( nYTop ); + rIStrm.ReadInt32( nYBottom ); + + // create band + ImplRegionBand* pNewBand = new ImplRegionBand( nYTop, nYBottom ); + + // first element? -> set as first into the list + if ( !pCurrBand ) + { + mpFirstBand = pNewBand; + } + else + { + pCurrBand->mpNextBand = pNewBand; + } + + // save pointer for next creation + pCurrBand = pNewBand; + } + else + { + sal_Int32 nXLeft(0); + sal_Int32 nXRight(0); + + rIStrm.ReadInt32( nXLeft ); + rIStrm.ReadInt32( nXRight ); + + // add separation + if ( pCurrBand ) + { + pCurrBand->Union( nXLeft, nXRight ); + } + } + + if( rIStrm.eof() ) + { + OSL_ENSURE(false, "premature end of region stream" ); + implReset(); + return false; + } + + // get next header + rIStrm.ReadUInt16( nTmp16 ); + } + while (STREAMENTRY_END != static_cast<StreamEntryType>(nTmp16) && rIStrm.good()); + if (!CheckConsistency()) + { + implReset(); + return false; + } + return true; +} + +void RegionBand::save(SvStream& rOStrm) const +{ + ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + // put boundaries + rOStrm.WriteUInt16( STREAMENTRY_BANDHEADER ); + rOStrm.WriteInt32( pBand->mnYTop ); + rOStrm.WriteInt32( pBand->mnYBottom ); + + // put separations of current band + ImplRegionBandSep* pSep = pBand->mpFirstSep; + + while(pSep) + { + // put separation + rOStrm.WriteUInt16( STREAMENTRY_SEPARATION ); + rOStrm.WriteInt32( pSep->mnXLeft ); + rOStrm.WriteInt32( pSep->mnXRight ); + + // next separation from current band + pSep = pSep->mpNextSep; + } + + pBand = pBand->mpNextBand; + } + + // put endmarker + rOStrm.WriteUInt16( STREAMENTRY_END ); +} + +bool RegionBand::isSingleRectangle() const +{ + // just one band? + if(mpFirstBand && !mpFirstBand->mpNextBand) + { + // just one sep? + if(mpFirstBand->mpFirstSep && !mpFirstBand->mpFirstSep->mpNextSep) + { + return true; + } + } + + return false; +} + +void RegionBand::InsertBand(ImplRegionBand* pPreviousBand, ImplRegionBand* pBandToInsert) +{ + OSL_ASSERT(pBandToInsert!=nullptr); + + if(!pPreviousBand) + { + // Insert band before all others. + if(mpFirstBand) + { + mpFirstBand->mpPrevBand = pBandToInsert; + } + + pBandToInsert->mpNextBand = mpFirstBand; + mpFirstBand = pBandToInsert; + } + else + { + // Insert band directly after pPreviousBand. + pBandToInsert->mpNextBand = pPreviousBand->mpNextBand; + pPreviousBand->mpNextBand = pBandToInsert; + pBandToInsert->mpPrevBand = pPreviousBand; + } + +} + +void RegionBand::processPoints() +{ + ImplRegionBand* pRegionBand = mpFirstBand; + + while(pRegionBand) + { + // generate separations from the lines and process union + pRegionBand->ProcessPoints(); + pRegionBand = pRegionBand->mpNextBand; + } + +} + +/** This function is similar to the RegionBand::InsertBands() method. + It creates a minimal set of missing bands so that the entire vertical + interval from nTop to nBottom is covered by bands. +*/ +void RegionBand::ImplAddMissingBands(const tools::Long nTop, const tools::Long nBottom) +{ + // Iterate over already existing bands and add missing bands atop the + // first and between two bands. + ImplRegionBand* pPreviousBand = nullptr; + ImplRegionBand* pBand = ImplGetFirstRegionBand(); + tools::Long nCurrentTop (nTop); + + while (pBand != nullptr && nCurrentTop<nBottom) + { + if (nCurrentTop < pBand->mnYTop) + { + // Create new band above the current band. + ImplRegionBand* pAboveBand = new ImplRegionBand( + nCurrentTop, + ::std::min(nBottom,pBand->mnYTop-1)); + InsertBand(pPreviousBand, pAboveBand); + } + + // Adapt the top of the interval to prevent overlapping bands. + nCurrentTop = ::std::max(nTop, pBand->mnYBottom+1); + + // Advance to next band. + pPreviousBand = pBand; + pBand = pBand->mpNextBand; + } + + // We still have to cover two cases: + // 1. The region does not yet contain any bands. + // 2. The interval nTop->nBottom extends past the bottom most band. + if (nCurrentTop <= nBottom + && (pBand==nullptr || nBottom>pBand->mnYBottom)) + { + // When there is no previous band then the new one will be the + // first. Otherwise the new band is inserted behind the last band. + InsertBand( + pPreviousBand, + new ImplRegionBand( + nCurrentTop, + nBottom)); + } + +} + +void RegionBand::CreateBandRange(tools::Long nYTop, tools::Long nYBottom) +{ + // add top band + mpFirstBand = new ImplRegionBand( nYTop-1, nYTop-1 ); + + // begin first search from the first element + mpLastCheckedBand = mpFirstBand; + ImplRegionBand* pBand = mpFirstBand; + + for ( tools::Long i = nYTop; i <= nYBottom+1; i++ ) + { + // create new band + ImplRegionBand* pNewBand = new ImplRegionBand( i, i ); + pBand->mpNextBand = pNewBand; + + if ( pBand != mpFirstBand ) + { + pNewBand->mpPrevBand = pBand; + } + + pBand = pBand->mpNextBand; + } + +} + +void RegionBand::InsertLine(const Point& rStartPt, const Point& rEndPt, tools::Long nLineId) +{ + tools::Long nX, nY; + + // lines consisting of a single point do not interest here + if ( rStartPt == rEndPt ) + { + return; + } + + LineType eLineType = (rStartPt.Y() > rEndPt.Y()) ? LineType::Descending : LineType::Ascending; + if ( rStartPt.X() == rEndPt.X() ) + { + // vertical line + const tools::Long nEndY = rEndPt.Y(); + + nX = rStartPt.X(); + nY = rStartPt.Y(); + + if( nEndY > nY ) + { + for ( ; nY <= nEndY; nY++ ) + { + Point aNewPoint( nX, nY ); + InsertPoint( aNewPoint, nLineId, + (aNewPoint == rEndPt) || (aNewPoint == rStartPt), + eLineType ); + } + } + else + { + for ( ; nY >= nEndY; nY-- ) + { + Point aNewPoint( nX, nY ); + InsertPoint( aNewPoint, nLineId, + (aNewPoint == rEndPt) || (aNewPoint == rStartPt), + eLineType ); + } + } + } + else if ( rStartPt.Y() != rEndPt.Y() ) + { + const tools::Long nDX = std::abs( rEndPt.X() - rStartPt.X() ); + const tools::Long nDY = std::abs( rEndPt.Y() - rStartPt.Y() ); + const tools::Long nStartX = rStartPt.X(); + const tools::Long nStartY = rStartPt.Y(); + const tools::Long nEndX = rEndPt.X(); + const tools::Long nEndY = rEndPt.Y(); + const tools::Long nXInc = ( nStartX < nEndX ) ? 1 : -1; + const tools::Long nYInc = ( nStartY < nEndY ) ? 1 : -1; + + if ( nDX >= nDY ) + { + const tools::Long nDYX = ( nDY - nDX ) * 2; + const tools::Long nDY2 = nDY << 1; + tools::Long nD = nDY2 - nDX; + + for ( nX = nStartX, nY = nStartY; nX != nEndX; nX += nXInc ) + { + InsertPoint( Point( nX, nY ), nLineId, nStartX == nX, eLineType ); + + if ( nD < 0 ) + nD += nDY2; + else + { + nD += nDYX; + nY += nYInc; + } + } + } + else + { + const tools::Long nDYX = ( nDX - nDY ) * 2; + const tools::Long nDY2 = nDX << 1; + tools::Long nD = nDY2 - nDY; + + for ( nX = nStartX, nY = nStartY; nY != nEndY; nY += nYInc ) + { + InsertPoint( Point( nX, nY ), nLineId, nStartY == nY, eLineType ); + + if ( nD < 0 ) + nD += nDY2; + else + { + nD += nDYX; + nX += nXInc; + } + } + } + + // last point + InsertPoint( Point( nEndX, nEndY ), nLineId, true, eLineType ); + } +} + +void RegionBand::InsertPoint(const Point &rPoint, tools::Long nLineID, bool bEndPoint, LineType eLineType) +{ + SAL_WARN_IF( mpFirstBand == nullptr, "vcl", "RegionBand::InsertPoint - no bands available!" ); + + if ( rPoint.Y() == mpLastCheckedBand->mnYTop ) + { + mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType ); + return; + } + + if ( rPoint.Y() > mpLastCheckedBand->mnYTop ) + { + // Search ascending + while ( mpLastCheckedBand ) + { + // Insert point if possible + if ( rPoint.Y() == mpLastCheckedBand->mnYTop ) + { + mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType ); + return; + } + + mpLastCheckedBand = mpLastCheckedBand->mpNextBand; + } + + OSL_ENSURE(false, "RegionBand::InsertPoint reached the end of the list!" ); + } + else + { + // Search descending + while ( mpLastCheckedBand ) + { + // Insert point if possible + if ( rPoint.Y() == mpLastCheckedBand->mnYTop ) + { + mpLastCheckedBand->InsertPoint( rPoint.X(), nLineID, bEndPoint, eLineType ); + return; + } + + mpLastCheckedBand = mpLastCheckedBand->mpPrevBand; + } + + OSL_ENSURE(false, "RegionBand::InsertPoint reached the beginning of the list!" ); + } + + OSL_ENSURE(false, "RegionBand::InsertPoint point not inserted!" ); + + // reinitialize pointer (should never be reached!) + mpLastCheckedBand = mpFirstBand; +} + +bool RegionBand::OptimizeBandList() +{ + ImplRegionBand* pPrevBand = nullptr; + ImplRegionBand* pBand = mpFirstBand; + + while ( pBand ) + { + const bool bBTEqual = pBand->mpNextBand && (pBand->mnYBottom == pBand->mpNextBand->mnYTop); + + // no separation? -> remove! + if ( pBand->IsEmpty() || (bBTEqual && (pBand->mnYBottom == pBand->mnYTop)) ) + { + // save pointer + ImplRegionBand* pOldBand = pBand; + + // previous element of the list + if ( pBand == mpFirstBand ) + mpFirstBand = pBand->mpNextBand; + else + pPrevBand->mpNextBand = pBand->mpNextBand; + + pBand = pBand->mpNextBand; + delete pOldBand; + } + else + { + // fixup + if ( bBTEqual ) + pBand->mnYBottom = pBand->mpNextBand->mnYTop-1; + + // this and next band with equal separations? -> combine! + if ( pBand->mpNextBand && + ((pBand->mnYBottom+1) == pBand->mpNextBand->mnYTop) && + (*pBand == *pBand->mpNextBand) ) + { + // expand current height + pBand->mnYBottom = pBand->mpNextBand->mnYBottom; + + // remove next band from list + ImplRegionBand* pDeletedBand = pBand->mpNextBand; + pBand->mpNextBand = pDeletedBand->mpNextBand; + delete pDeletedBand; + + // check band again! + } + else + { + // count rectangles within band + ImplRegionBandSep* pSep = pBand->mpFirstSep; + while ( pSep ) + { + pSep = pSep->mpNextSep; + } + + pPrevBand = pBand; + pBand = pBand->mpNextBand; + } + } + } + +#ifdef DBG_UTIL + pBand = mpFirstBand; + while ( pBand ) + { + SAL_WARN_IF( pBand->mpFirstSep == nullptr, "vcl", "Exiting RegionBand::OptimizeBandList(): empty band in region!" ); + + if ( pBand->mnYBottom < pBand->mnYTop ) + OSL_ENSURE(false, "RegionBand::OptimizeBandList(): YBottomBoundary < YTopBoundary" ); + + if ( pBand->mpNextBand && pBand->mnYBottom >= pBand->mpNextBand->mnYTop ) + OSL_ENSURE(false, "RegionBand::OptimizeBandList(): overlapping bands in region!" ); + + pBand = pBand->mpNextBand; + } +#endif + + return (nullptr != mpFirstBand); +} + +void RegionBand::Move(tools::Long nHorzMove, tools::Long nVertMove) +{ + ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + // process the vertical move + if(nVertMove) + { + pBand->mnYTop = o3tl::saturating_add(pBand->mnYTop, nVertMove); + pBand->mnYBottom = o3tl::saturating_add(pBand->mnYBottom, nVertMove); + } + + // process the horizontal move + if(nHorzMove) + { + pBand->MoveX(nHorzMove); + } + + pBand = pBand->mpNextBand; + } + +} + +void RegionBand::Scale(double fScaleX, double fScaleY) +{ + ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + // process the vertical move + if(0.0 != fScaleY) + { + pBand->mnYTop = basegfx::fround(pBand->mnYTop * fScaleY); + pBand->mnYBottom = basegfx::fround(pBand->mnYBottom * fScaleY); + } + + // process the horizontal move + if(0.0 != fScaleX) + { + pBand->ScaleX(fScaleX); + } + + pBand = pBand->mpNextBand; + } + +} + +void RegionBand::InsertBands(tools::Long nTop, tools::Long nBottom) +{ + // region empty? -> set rectangle as first entry! + if ( !mpFirstBand ) + { + // add band with boundaries of the rectangle + mpFirstBand = new ImplRegionBand( nTop, nBottom ); + return; + } + + // find/insert bands for the boundaries of the rectangle + bool bTopBoundaryInserted = false; + bool bTop2BoundaryInserted = false; + bool bBottomBoundaryInserted = false; + + // special case: top boundary is above the first band + ImplRegionBand* pNewBand; + + if ( nTop < mpFirstBand->mnYTop ) + { + // create new band above the first in the list + pNewBand = new ImplRegionBand( nTop, mpFirstBand->mnYTop ); + + if ( nBottom < mpFirstBand->mnYTop ) + { + pNewBand->mnYBottom = nBottom; + } + + // insert band into the list + pNewBand->mpNextBand = mpFirstBand; + mpFirstBand = pNewBand; + + bTopBoundaryInserted = true; + } + + // insert band(s) into the list + ImplRegionBand* pBand = mpFirstBand; + + while ( pBand ) + { + // Insert Bands if possible + if ( !bTopBoundaryInserted ) + { + bTopBoundaryInserted = InsertSingleBand( pBand, nTop - 1 ); + } + + if ( !bTop2BoundaryInserted ) + { + bTop2BoundaryInserted = InsertSingleBand( pBand, nTop ); + } + + if ( !bBottomBoundaryInserted && (nTop != nBottom) ) + { + bBottomBoundaryInserted = InsertSingleBand( pBand, nBottom ); + } + + // both boundaries inserted? -> nothing more to do + if ( bTopBoundaryInserted && bTop2BoundaryInserted && bBottomBoundaryInserted ) + { + break; + } + + // insert bands between two bands if necessary + if ( pBand->mpNextBand ) + { + if ( (pBand->mnYBottom + 1) < pBand->mpNextBand->mnYTop ) + { + // copy band with list and set new boundary + pNewBand = new ImplRegionBand( pBand->mnYBottom+1, pBand->mpNextBand->mnYTop-1 ); + + // insert band into the list + pNewBand->mpNextBand = pBand->mpNextBand; + pBand->mpNextBand = pNewBand; + } + } + + pBand = pBand->mpNextBand; + } + +} + +bool RegionBand::InsertSingleBand(ImplRegionBand* pBand, tools::Long nYBandPosition) +{ + // boundary already included in band with height 1? -> nothing to do! + if ( (pBand->mnYTop == pBand->mnYBottom) && (nYBandPosition == pBand->mnYTop) ) + { + return true; + } + + // insert single height band on top? + ImplRegionBand* pNewBand; + + if ( nYBandPosition == pBand->mnYTop ) + { + // copy band with list and set new boundary + pNewBand = new ImplRegionBand( *pBand ); + pNewBand->mnYTop = nYBandPosition+1; + + // insert band into the list + pNewBand->mpNextBand = pBand->mpNextBand; + pBand->mnYBottom = nYBandPosition; + pBand->mpNextBand = pNewBand; + + return true; + } + + // top of new rectangle within the current band? -> insert new band and copy data + if ( (nYBandPosition > pBand->mnYTop) && (nYBandPosition < pBand->mnYBottom) ) + { + // copy band with list and set new boundary + pNewBand = new ImplRegionBand( *pBand ); + pNewBand->mnYTop = nYBandPosition; + + // insert band into the list + pNewBand->mpNextBand = pBand->mpNextBand; + pBand->mnYBottom = nYBandPosition; + pBand->mpNextBand = pNewBand; + + // copy band with list and set new boundary + pNewBand = new ImplRegionBand( *pBand ); + pNewBand->mnYTop = nYBandPosition; + + // insert band into the list + pBand->mpNextBand->mnYTop = nYBandPosition+1; + + pNewBand->mpNextBand = pBand->mpNextBand; + pBand->mnYBottom = nYBandPosition - 1; + pBand->mpNextBand = pNewBand; + + return true; + } + + // create new band behind the current in the list + if ( !pBand->mpNextBand ) + { + if ( nYBandPosition == pBand->mnYBottom ) + { + // copy band with list and set new boundary + pNewBand = new ImplRegionBand( *pBand ); + pNewBand->mnYTop = pBand->mnYBottom; + pNewBand->mnYBottom = nYBandPosition; + + pBand->mnYBottom = nYBandPosition-1; + + // append band to the list + pBand->mpNextBand = pNewBand; + return true; + } + + if ( nYBandPosition > pBand->mnYBottom ) + { + // create new band + pNewBand = new ImplRegionBand( pBand->mnYBottom + 1, nYBandPosition ); + + // append band to the list + pBand->mpNextBand = pNewBand; + return true; + } + } + + return false; +} + +void RegionBand::Union(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom) +{ + SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Union() - nLeft > nRight" ); + SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Union() - nTop > nBottom" ); + + // process union + ImplRegionBand* pBand = mpFirstBand; + while ( pBand ) + { + if ( pBand->mnYTop >= nTop ) + { + if ( pBand->mnYBottom <= nBottom ) + pBand->Union( nLeft, nRight ); + else + { +#ifdef DBG_UTIL + tools::Long nCurY = pBand->mnYBottom; + pBand = pBand->mpNextBand; + while ( pBand ) + { + if ( (pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY) ) + { + OSL_ENSURE(false, "RegionBand::Union() - Bands not sorted!" ); + } + pBand = pBand->mpNextBand; + } +#endif + break; + } + } + + pBand = pBand->mpNextBand; + } + +} + +void RegionBand::Intersect(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom) +{ + // process intersections + ImplRegionBand* pPrevBand = nullptr; + ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + // band within intersection boundary? -> process. otherwise remove + if((pBand->mnYTop >= nTop) && (pBand->mnYBottom <= nBottom)) + { + // process intersection + pBand->Intersect(nLeft, nRight); + pPrevBand = pBand; + pBand = pBand->mpNextBand; + } + else + { + ImplRegionBand* pOldBand = pBand; + + if(pBand == mpFirstBand) + { + mpFirstBand = pBand->mpNextBand; + } + else + { + pPrevBand->mpNextBand = pBand->mpNextBand; + } + + pBand = pBand->mpNextBand; + delete pOldBand; + } + } + +} + +void RegionBand::Union(const RegionBand& rSource) +{ + // apply all rectangles from rSource to this + ImplRegionBand* pBand = rSource.mpFirstBand; + + while ( pBand ) + { + // insert bands if the boundaries are not already in the list + InsertBands(pBand->mnYTop, pBand->mnYBottom); + + // process all elements of the list + ImplRegionBandSep* pSep = pBand->mpFirstSep; + + while(pSep) + { + Union(pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom); + pSep = pSep->mpNextSep; + } + + pBand = pBand->mpNextBand; + } + +} + +void RegionBand::Exclude(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom) +{ + SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Exclude() - nLeft > nRight" ); + SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Exclude() - nTop > nBottom" ); + + // process exclude + ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + if(pBand->mnYTop >= nTop) + { + if(pBand->mnYBottom <= nBottom) + { + pBand->Exclude(nLeft, nRight); + } + else + { +#ifdef DBG_UTIL + tools::Long nCurY = pBand->mnYBottom; + pBand = pBand->mpNextBand; + + while(pBand) + { + if((pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY)) + { + OSL_ENSURE(false, "RegionBand::Exclude() - Bands not sorted!" ); + } + + pBand = pBand->mpNextBand; + } +#endif + break; + } + } + + pBand = pBand->mpNextBand; + } + +} + +void RegionBand::XOr(tools::Long nLeft, tools::Long nTop, tools::Long nRight, tools::Long nBottom) +{ + SAL_WARN_IF( nLeft > nRight, "vcl", "RegionBand::Exclude() - nLeft > nRight" ); + SAL_WARN_IF( nTop > nBottom, "vcl", "RegionBand::Exclude() - nTop > nBottom" ); + + // process xor + ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + if(pBand->mnYTop >= nTop) + { + if(pBand->mnYBottom <= nBottom) + { + pBand->XOr(nLeft, nRight); + } + else + { +#ifdef DBG_UTIL + tools::Long nCurY = pBand->mnYBottom; + pBand = pBand->mpNextBand; + + while(pBand) + { + if((pBand->mnYTop < nCurY) || (pBand->mnYBottom < nCurY)) + { + OSL_ENSURE(false, "RegionBand::XOr() - Bands not sorted!" ); + } + + pBand = pBand->mpNextBand; + } +#endif + break; + } + } + + pBand = pBand->mpNextBand; + } + +} + +void RegionBand::Intersect(const RegionBand& rSource) +{ + // mark all bands as untouched + ImplRegionBand* pBand = mpFirstBand; + + while ( pBand ) + { + pBand->mbTouched = false; + pBand = pBand->mpNextBand; + } + + pBand = rSource.mpFirstBand; + + while ( pBand ) + { + // insert bands if the boundaries are not already in the list + InsertBands( pBand->mnYTop, pBand->mnYBottom ); + + // process all elements of the list + ImplRegionBandSep* pSep = pBand->mpFirstSep; + + while ( pSep ) + { + // left boundary? + if ( pSep == pBand->mpFirstSep ) + { + // process intersection and do not remove untouched bands + Exclude( LONG_MIN+1, pBand->mnYTop, pSep->mnXLeft-1, pBand->mnYBottom ); + } + + // right boundary? + if ( pSep->mpNextSep == nullptr ) + { + // process intersection and do not remove untouched bands + Exclude( pSep->mnXRight+1, pBand->mnYTop, LONG_MAX-1, pBand->mnYBottom ); + } + else + { + // process intersection and do not remove untouched bands + Exclude( pSep->mnXRight+1, pBand->mnYTop, pSep->mpNextSep->mnXLeft-1, pBand->mnYBottom ); + } + + pSep = pSep->mpNextSep; + } + + pBand = pBand->mpNextBand; + } + + // remove all untouched bands if bands already left + ImplRegionBand* pPrevBand = nullptr; + pBand = mpFirstBand; + + while ( pBand ) + { + if ( !pBand->mbTouched ) + { + // save pointer + ImplRegionBand* pOldBand = pBand; + + // previous element of the list + if ( pBand == mpFirstBand ) + { + mpFirstBand = pBand->mpNextBand; + } + else + { + pPrevBand->mpNextBand = pBand->mpNextBand; + } + + pBand = pBand->mpNextBand; + delete pOldBand; + } + else + { + pPrevBand = pBand; + pBand = pBand->mpNextBand; + } + } + +} + +bool RegionBand::Exclude(const RegionBand& rSource) +{ + // apply all rectangles to the region passed to this region + ImplRegionBand* pBand = rSource.mpFirstBand; + + while ( pBand ) + { + // insert bands if the boundaries are not already in the list + InsertBands( pBand->mnYTop, pBand->mnYBottom ); + + // process all elements of the list + ImplRegionBandSep* pSep = pBand->mpFirstSep; + + while ( pSep ) + { + Exclude( pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom ); + pSep = pSep->mpNextSep; + } + + // to test less bands, already check in the loop + if ( !OptimizeBandList() ) + { + return false; + } + + pBand = pBand->mpNextBand; + } + + return true; +} + +bool RegionBand::CheckConsistency() const +{ + if (!mpFirstBand) + return true; + // look in the band list (don't test first band again!) + const ImplRegionBand* pBand = mpFirstBand->mpNextBand; + while (pBand) + { + if (!pBand->mpFirstSep) + return false; + pBand = pBand->mpNextBand; + } + return true; +} + +tools::Rectangle RegionBand::GetBoundRect() const +{ + if (!mpFirstBand) + return tools::Rectangle(); + + // get the boundaries of the first band + tools::Long nYTop(mpFirstBand->mnYTop); + tools::Long nYBottom(mpFirstBand->mnYBottom); + tools::Long nXLeft(mpFirstBand->GetXLeftBoundary()); + tools::Long nXRight(mpFirstBand->GetXRightBoundary()); + + // look in the band list (don't test first band again!) + ImplRegionBand* pBand = mpFirstBand->mpNextBand; + + while ( pBand ) + { + nYBottom = pBand->mnYBottom; + nXLeft = std::min( nXLeft, pBand->GetXLeftBoundary() ); + nXRight = std::max( nXRight, pBand->GetXRightBoundary() ); + + pBand = pBand->mpNextBand; + } + + return tools::Rectangle( nXLeft, nYTop, nXRight, nYBottom ); +} + +void RegionBand::XOr(const RegionBand& rSource) +{ + ImplRegionBand* pBand = rSource.mpFirstBand; + + while ( pBand ) + { + // insert bands if the boundaries are not already in the list + InsertBands( pBand->mnYTop, pBand->mnYBottom ); + + // process all elements of the list + ImplRegionBandSep* pSep = pBand->mpFirstSep; + + while ( pSep ) + { + XOr( pSep->mnXLeft, pBand->mnYTop, pSep->mnXRight, pBand->mnYBottom ); + pSep = pSep->mpNextSep; + } + + pBand = pBand->mpNextBand; + } +} + +bool RegionBand::Contains(const Point& rPoint) const +{ + + // search band list + ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + // is point within band? + if((pBand->mnYTop <= rPoint.Y()) && (pBand->mnYBottom >= rPoint.Y())) + { + // is point within separation of the band? + return pBand->Contains(rPoint.X()); + } + + pBand = pBand->mpNextBand; + } + + return false; +} + +void RegionBand::GetRegionRectangles(RectangleVector& rTarget) const +{ + // clear result vector + rTarget.clear(); + ImplRegionBand* pCurrRectBand = mpFirstBand; + tools::Rectangle aRectangle; + + while(pCurrRectBand) + { + ImplRegionBandSep* pCurrRectBandSep = pCurrRectBand->mpFirstSep; + + aRectangle.SetTop( pCurrRectBand->mnYTop ); + aRectangle.SetBottom( pCurrRectBand->mnYBottom ); + + while(pCurrRectBandSep) + { + aRectangle.SetLeft( pCurrRectBandSep->mnXLeft ); + aRectangle.SetRight( pCurrRectBandSep->mnXRight ); + rTarget.push_back(aRectangle); + pCurrRectBandSep = pCurrRectBandSep->mpNextSep; + } + + pCurrRectBand = pCurrRectBand->mpNextBand; + } +} + +sal_uInt32 RegionBand::getRectangleCount() const +{ + sal_uInt32 nCount = 0; + const ImplRegionBand* pBand = mpFirstBand; + + while(pBand) + { + ImplRegionBandSep* pSep = pBand->mpFirstSep; + + while(pSep) + { + nCount++; + pSep = pSep->mpNextSep; + } + + pBand = pBand->mpNextBand; + } + + return nCount; +} + +#ifdef DBG_UTIL +const char* ImplDbgTestRegionBand(const void* pObj) +{ + const RegionBand* pRegionBand = static_cast< const RegionBand* >(pObj); + + if(pRegionBand) + { + const ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand(); + + while(pBand) + { + if(pBand->mnYBottom < pBand->mnYTop) + { + return "YBottom < YTop"; + } + + if(pBand->mpNextBand) + { + if(pBand->mnYBottom >= pBand->mpNextBand->mnYTop) + { + return "overlapping bands in region"; + } + } + + if(pBand->mbTouched) + { + return "Band-mbTouched overwrite"; + } + + ImplRegionBandSep* pSep = pBand->mpFirstSep; + + while(pSep) + { + if(pSep->mnXRight < pSep->mnXLeft) + { + return "XLeft < XRight"; + } + + if(pSep->mpNextSep) + { + if(pSep->mnXRight >= pSep->mpNextSep->mnXLeft) + { + return "overlapping separations in region"; + } + } + + if ( pSep->mbRemoved ) + { + return "Sep-mbRemoved overwrite"; + } + + pSep = pSep->mpNextSep; + } + + pBand = pBand->mpNextBand; + } + } + + return nullptr; +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/salgdiimpl.cxx b/vcl/source/gdi/salgdiimpl.cxx new file mode 100644 index 0000000000..622c86c395 --- /dev/null +++ b/vcl/source/gdi/salgdiimpl.cxx @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <salgdiimpl.hxx> + +SalGraphicsImpl::~SalGraphicsImpl() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/salgdilayout.cxx b/vcl/source/gdi/salgdilayout.cxx new file mode 100644 index 0000000000..0a5eb5140a --- /dev/null +++ b/vcl/source/gdi/salgdilayout.cxx @@ -0,0 +1,940 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> +#include <config_features.h> +#include <sal/log.hxx> +#include <font/PhysicalFontFace.hxx> +#include <salgdi.hxx> +#include <salframe.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <FileDefinitionWidgetDraw.hxx> +#include <rtl/math.hxx> +#include <comphelper/lok.hxx> +#include <toolbarvalue.hxx> + +// The only common SalFrame method + +SalFrameGeometry SalFrame::GetGeometry() const +{ + // mirror frame coordinates at parent + SalFrame *pParent = GetParent(); + if( pParent && AllSettings::GetLayoutRTL() ) + { + SalFrameGeometry aGeom = maGeometry; + const int nParentX = aGeom.x() - pParent->maGeometry.x(); + aGeom.setX(pParent->maGeometry.x() + pParent->maGeometry.width() - maGeometry.width() - nParentX); + return aGeom; + } + else + return maGeometry; +} + +SalGraphics::SalGraphics() +: m_nLayout( SalLayoutFlags::NONE ), + m_eLastMirrorMode(MirrorMode::NONE), + m_nLastMirrorTranslation(0), + m_bAntiAlias(false) +{ + // read global RTL settings + if( AllSettings::GetLayoutRTL() ) + m_nLayout = SalLayoutFlags::BiDiRtl; +} + +bool SalGraphics::initWidgetDrawBackends(bool bForce) +{ + static bool bFileDefinitionsWidgetDraw = !!getenv("VCL_DRAW_WIDGETS_FROM_FILE"); + + if (bFileDefinitionsWidgetDraw || bForce) + { + m_pWidgetDraw.reset(new vcl::FileDefinitionWidgetDraw(*this)); + auto pFileDefinitionWidgetDraw = static_cast<vcl::FileDefinitionWidgetDraw*>(m_pWidgetDraw.get()); + if (!pFileDefinitionWidgetDraw->isActive()) + { + m_pWidgetDraw.reset(); + return false; + } + return true; + } + return false; +} + +SalGraphics::~SalGraphics() COVERITY_NOEXCEPT_FALSE +{ + // can't call ReleaseFonts here, as the destructor just calls this classes SetFont (pure virtual)! +} + +bool SalGraphics::drawTransformedBitmap( + const basegfx::B2DPoint& /* rNull */, + const basegfx::B2DPoint& /* rX */, + const basegfx::B2DPoint& /* rY */, + const SalBitmap& /* rSourceBitmap */, + const SalBitmap* /* pAlphaBitmap */, + double /* fAlpha */) +{ + // here direct support for transformed bitmaps can be implemented + return false; +} + +tools::Long SalGraphics::mirror2( tools::Long x, const OutputDevice& rOutDev ) const +{ + mirror(x, rOutDev); + return x; +} + +inline tools::Long SalGraphics::GetDeviceWidth(const OutputDevice& rOutDev) const +{ + return rOutDev.IsVirtual() ? rOutDev.GetOutputWidthPixel() : GetGraphicsWidth(); +} + +void SalGraphics::mirror( tools::Long& x, const OutputDevice& rOutDev ) const +{ + const tools::Long w = GetDeviceWidth(rOutDev); + if( !w ) + return; + + if (rOutDev.ImplIsAntiparallel() ) + { + // mirror this window back + if( m_nLayout & SalLayoutFlags::BiDiRtl ) + { + tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX + x = devX + (x - rOutDev.GetOutOffXPixel()); + } + else + { + tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX + x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - 1; + } + } + else if( m_nLayout & SalLayoutFlags::BiDiRtl ) + x = w-1-x; +} + +void SalGraphics::mirror( tools::Long& x, tools::Long nWidth, const OutputDevice& rOutDev, bool bBack ) const +{ + const tools::Long w = GetDeviceWidth(rOutDev); + if( !w ) + return; + + if (rOutDev.ImplIsAntiparallel() ) + { + // mirror this window back + if( m_nLayout & SalLayoutFlags::BiDiRtl ) + { + tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX + if( bBack ) + x = x - devX + rOutDev.GetOutOffXPixel(); + else + x = devX + (x - rOutDev.GetOutOffXPixel()); + } + else + { + tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX + if( bBack ) + x = devX + (rOutDev.GetOutputWidthPixel() + devX) - (x + nWidth); + else + x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - nWidth; + } + } + else if( m_nLayout & SalLayoutFlags::BiDiRtl ) + x = w-nWidth-x; +} + +bool SalGraphics::mirror( sal_uInt32 nPoints, const Point *pPtAry, Point *pPtAry2, const OutputDevice& rOutDev ) const +{ + const tools::Long w = GetDeviceWidth(rOutDev); + if( w ) + { + sal_uInt32 i, j; + + if (rOutDev.ImplIsAntiparallel()) + { + // mirror this window back + if( m_nLayout & SalLayoutFlags::BiDiRtl ) + { + tools::Long devX = w - rOutDev.GetOutputWidthPixel() - rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX + for( i=0, j=nPoints-1; i<nPoints; i++,j-- ) + { + pPtAry2[j].setX( devX + (pPtAry[i].getX() - rOutDev.GetOutOffXPixel()) ); + pPtAry2[j].setY( pPtAry[i].getY() ); + } + } + else + { + tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX + for( i=0, j=nPoints-1; i<nPoints; i++,j-- ) + { + pPtAry2[j].setX( rOutDev.GetOutputWidthPixel() - (pPtAry[i].getX() - devX) + rOutDev.GetOutOffXPixel() - 1 ); + pPtAry2[j].setY( pPtAry[i].getY() ); + } + } + } + else if( m_nLayout & SalLayoutFlags::BiDiRtl ) + { + for( i=0, j=nPoints-1; i<nPoints; i++,j-- ) + { + pPtAry2[j].setX( w-1-pPtAry[i].getX() ); + pPtAry2[j].setY( pPtAry[i].getY() ); + } + } + return true; + } + else + return false; +} + +void SalGraphics::mirror( vcl::Region& rRgn, const OutputDevice& rOutDev ) const +{ + if( rRgn.HasPolyPolygonOrB2DPolyPolygon() ) + { + const basegfx::B2DPolyPolygon aPolyPoly(mirror(rRgn.GetAsB2DPolyPolygon(), rOutDev)); + + rRgn = vcl::Region(aPolyPoly); + } + else + { + RectangleVector aRectangles; + rRgn.GetRegionRectangles(aRectangles); + rRgn.SetEmpty(); + + for (auto & rectangle : aRectangles) + { + mirror(rectangle, rOutDev); + rRgn.Union(rectangle); + } + + //ImplRegionInfo aInfo; + //bool bRegionRect; + //Region aMirroredRegion; + //long nX, nY, nWidth, nHeight; + + //bRegionRect = rRgn.ImplGetFirstRect( aInfo, nX, nY, nWidth, nHeight ); + //while ( bRegionRect ) + //{ + // Rectangle aRect( Point(nX, nY), Size(nWidth, nHeight) ); + // mirror( aRect, rOutDev, bBack ); + // aMirroredRegion.Union( aRect ); + // bRegionRect = rRgn.ImplGetNextRect( aInfo, nX, nY, nWidth, nHeight ); + //} + //rRgn = aMirroredRegion; + } +} + +void SalGraphics::mirror( tools::Rectangle& rRect, const OutputDevice& rOutDev, bool bBack ) const +{ + tools::Long nWidth = rRect.GetWidth(); + tools::Long x = rRect.Left(); + tools::Long x_org = x; + + mirror( x, nWidth, rOutDev, bBack ); + rRect.Move( x - x_org, 0 ); +} + +basegfx::B2DPolyPolygon SalGraphics::mirror( const basegfx::B2DPolyPolygon& i_rPoly, const OutputDevice& i_rOutDev ) const +{ + const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev)); + + if(rMirror.isIdentity()) + { + return i_rPoly; + } + else + { + basegfx::B2DPolyPolygon aRet(i_rPoly); + aRet.transform(rMirror); + aRet.flip(); + return aRet; + } +} + +SalGraphics::MirrorMode SalGraphics::GetMirrorMode(const OutputDevice& rOutDev) const +{ + if (rOutDev.ImplIsAntiparallel()) + { + if (m_nLayout & SalLayoutFlags::BiDiRtl) + return MirrorMode::AntiparallelBiDi; + else + return MirrorMode::Antiparallel; + } + else if (m_nLayout & SalLayoutFlags::BiDiRtl) + return MirrorMode::BiDi; + return MirrorMode::NONE; +} + +const basegfx::B2DHomMatrix& SalGraphics::getMirror( const OutputDevice& i_rOutDev ) const +{ + // get mirroring transformation + MirrorMode eNewMirrorMode = GetMirrorMode(i_rOutDev); + tools::Long nTranslate(0); + + switch (eNewMirrorMode) + { + case MirrorMode::AntiparallelBiDi: + { + const tools::Long w = GetDeviceWidth(i_rOutDev); + SAL_WARN_IF(!w, "vcl", "missing graphics width"); + nTranslate = w - i_rOutDev.GetOutputWidthPixel() - (2 * i_rOutDev.GetOutOffXPixel()); + break; + } + case MirrorMode::Antiparallel: + { + nTranslate = i_rOutDev.GetOutputWidthPixel() + (2 * i_rOutDev.GetOutOffXPixel()) - 1; + break; + } + case MirrorMode::BiDi: + { + const tools::Long w = GetDeviceWidth(i_rOutDev); + SAL_WARN_IF(!w, "vcl", "missing graphics width"); + nTranslate = w - 1; + break; + } + case MirrorMode::NONE: + break; + } + + // if the translation (due to device width), or mirror state of the device changed, then m_aLastMirror is invalid + bool bLastMirrorValid = eNewMirrorMode == m_eLastMirrorMode && nTranslate == m_nLastMirrorTranslation; + if (!bLastMirrorValid) + { + const_cast<SalGraphics*>(this)->m_nLastMirrorTranslation = nTranslate; + const_cast<SalGraphics*>(this)->m_eLastMirrorMode = eNewMirrorMode; + + switch (eNewMirrorMode) + { + // mirror this window back + case MirrorMode::AntiparallelBiDi: + { + /* This path gets exercised in calc's RTL UI (e.g. SAL_RTL_ENABLED=1) + with its LTR horizontal scrollbar */ + + // Original code was: + // double devX = w-i_rOutDev.GetOutputWidthPixel()-i_rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX + // aRet.setX( devX + (i_rPoint.getX() - i_rOutDev.GetOutOffXPixel()) ); + const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createTranslateB2DHomMatrix( + nTranslate, 0.0); + break; + } + case MirrorMode::Antiparallel: + { + /* This path gets exercised in writers's LTR UI with a RTL horizontal + scrollbar, cross-reference dialog populated from contents from a + RTL document tdf#131725 */ + + // Original code was; + // tools::Long devX = rOutDev.GetOutOffXPixel(); // re-mirrored mnOutOffX + // x = rOutDev.GetOutputWidthPixel() - (x - devX) + rOutDev.GetOutOffXPixel() - 1; + const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createScaleTranslateB2DHomMatrix( + -1.0, + 1.0, + nTranslate, + 0.0); + break; + } + case MirrorMode::BiDi: + { + // Original code was: + // aRet.setX( w-1-i_rPoint.getX() ); + // -mirror X -> scale(-1.0, 1.0) + // -translate X -> translate(w-1, 0) + // Checked this one, works as expected. + const_cast<SalGraphics*>(this)->m_aLastMirror = basegfx::utils::createScaleTranslateB2DHomMatrix( + -1.0, + 1.0, + nTranslate, + 0.0); + break; + } + case MirrorMode::NONE: + const_cast<SalGraphics*>(this)->m_aLastMirror.identity(); + break; + } + } + + return m_aLastMirror; +} + +void SalGraphics::SetClipRegion( const vcl::Region& i_rClip, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + vcl::Region aMirror( i_rClip ); + mirror( aMirror, rOutDev ); + setClipRegion( aMirror ); + } + else + { + setClipRegion( i_rClip ); + } +} + +void SalGraphics::DrawPixel( tools::Long nX, tools::Long nY, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( nX, rOutDev ); + drawPixel( nX, nY ); +} + +void SalGraphics::DrawPixel( tools::Long nX, tools::Long nY, Color nColor, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( nX, rOutDev ); + drawPixel( nX, nY, nColor ); +} + +void SalGraphics::DrawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + mirror( nX1, rOutDev ); + mirror( nX2, rOutDev ); + } + drawLine( nX1, nY1, nX2, nY2 ); +} + +void SalGraphics::DrawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( nX, nWidth, rOutDev ); + drawRect( nX, nY, nWidth, nHeight ); +} + +void SalGraphics::DrawPolyLine( sal_uInt32 nPoints, Point const * pPtAry, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]); + bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev ); + drawPolyLine( nPoints, bCopied ? pPtAry2.get() : pPtAry ); + } + else + drawPolyLine( nPoints, pPtAry ); +} + +void SalGraphics::DrawPolygon( sal_uInt32 nPoints, const Point* pPtAry, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]); + bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev ); + drawPolygon( nPoints, bCopied ? pPtAry2.get() : pPtAry ); + } + else + drawPolygon( nPoints, pPtAry ); +} + +void SalGraphics::DrawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + // TODO: optimize, reduce new/delete calls + std::unique_ptr<Point*[]> pPtAry2( new Point*[nPoly] ); + sal_uLong i; + for(i=0; i<nPoly; i++) + { + sal_uLong nPoints = pPoints[i]; + pPtAry2[i] = new Point[ nPoints ]; + mirror( nPoints, pPtAry[i], pPtAry2[i], rOutDev ); + } + + drawPolyPolygon( nPoly, pPoints, const_cast<const Point**>(pPtAry2.get()) ); + + for(i=0; i<nPoly; i++) + delete [] pPtAry2[i]; + } + else + drawPolyPolygon( nPoly, pPoints, pPtAry ); +} + +namespace +{ + basegfx::B2DHomMatrix createTranslateToMirroredBounds(const basegfx::B2DRange &rBoundingBox, const basegfx::B2DHomMatrix& rMirror) + { + basegfx::B2DRange aRTLBoundingBox(rBoundingBox); + aRTLBoundingBox *= rMirror; + return basegfx::utils::createTranslateB2DHomMatrix(aRTLBoundingBox.getMinX() - rBoundingBox.getMinX(), 0); + } +} + +void SalGraphics::DrawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& i_rPolyPolygon, + double i_fTransparency, + const OutputDevice& i_rOutDev) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() ) + { + // mirroring set + const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev)); + if(!rMirror.isIdentity()) + { + drawPolyPolygon( + rMirror * rObjectToDevice, + i_rPolyPolygon, + i_fTransparency); + return; + } + } + + drawPolyPolygon( + rObjectToDevice, + i_rPolyPolygon, + i_fTransparency); +} + +bool SalGraphics::DrawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry, const OutputDevice& rOutDev ) +{ + bool bResult = false; + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]); + bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev ); + bResult = drawPolyLineBezier( nPoints, bCopied ? pPtAry2.get() : pPtAry, pFlgAry ); + } + else + bResult = drawPolyLineBezier( nPoints, pPtAry, pFlgAry ); + return bResult; +} + +bool SalGraphics::DrawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry, const OutputDevice& rOutDev ) +{ + bool bResult = false; + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]); + bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev ); + bResult = drawPolygonBezier( nPoints, bCopied ? pPtAry2.get() : pPtAry, pFlgAry ); + } + else + bResult = drawPolygonBezier( nPoints, pPtAry, pFlgAry ); + return bResult; +} + +bool SalGraphics::DrawPolyPolygonBezier( sal_uInt32 i_nPoly, const sal_uInt32* i_pPoints, + const Point* const* i_pPtAry, const PolyFlags* const* i_pFlgAry, const OutputDevice& i_rOutDev ) +{ + bool bRet = false; + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() ) + { + // TODO: optimize, reduce new/delete calls + std::unique_ptr<Point*[]> pPtAry2( new Point*[i_nPoly] ); + sal_uLong i; + for(i=0; i<i_nPoly; i++) + { + sal_uLong nPoints = i_pPoints[i]; + pPtAry2[i] = new Point[ nPoints ]; + mirror( nPoints, i_pPtAry[i], pPtAry2[i], i_rOutDev ); + } + + bRet = drawPolyPolygonBezier( i_nPoly, i_pPoints, const_cast<const Point* const *>(pPtAry2.get()), i_pFlgAry ); + + for(i=0; i<i_nPoly; i++) + delete [] pPtAry2[i]; + } + else + bRet = drawPolyPolygonBezier( i_nPoly, i_pPoints, i_pPtAry, i_pFlgAry ); + return bRet; +} + +bool SalGraphics::DrawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& i_rPolygon, + double i_fTransparency, + double i_rLineWidth, + const std::vector< double >* i_pStroke, // MM01 + basegfx::B2DLineJoin i_eLineJoin, + css::drawing::LineCap i_eLineCap, + double i_fMiterMinimumAngle, + bool bPixelSnapHairline, + const OutputDevice& i_rOutDev) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || i_rOutDev.IsRTLEnabled() ) + { + // mirroring set + const basegfx::B2DHomMatrix& rMirror(getMirror(i_rOutDev)); + if(!rMirror.isIdentity()) + { + return drawPolyLine( + rMirror * rObjectToDevice, + i_rPolygon, + i_fTransparency, + i_rLineWidth, + i_pStroke, // MM01 + i_eLineJoin, + i_eLineCap, + i_fMiterMinimumAngle, + bPixelSnapHairline); + } + } + + // no mirroring set (or identity), use standard call + return drawPolyLine( + rObjectToDevice, + i_rPolygon, + i_fTransparency, + i_rLineWidth, + i_pStroke, // MM01 + i_eLineJoin, + i_eLineCap, + i_fMiterMinimumAngle, + bPixelSnapHairline); +} + +bool SalGraphics::DrawGradient(const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, const OutputDevice& rOutDev) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + tools::PolyPolygon aMirrored(mirror(rPolyPoly.getB2DPolyPolygon(), rOutDev)); + return drawGradient(aMirrored, rGradient); + } + + return drawGradient( rPolyPoly, rGradient ); +} + +void SalGraphics::CopyArea( tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, + const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + mirror( nDestX, nSrcWidth, rOutDev ); + mirror( nSrcX, nSrcWidth, rOutDev ); + } + copyArea( nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, true/*bWindowInvalidate*/ ); +} + +void SalGraphics::CopyBits(const SalTwoRect& rPosAry, const OutputDevice& rOutDev) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + SalTwoRect aPosAry2 = rPosAry; + mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev ); + copyBits( aPosAry2, nullptr ); + } + else + copyBits( rPosAry, nullptr ); +} + +void SalGraphics::CopyBits(const SalTwoRect& rPosAry, SalGraphics& rSrcGraphics, + const OutputDevice& rOutDev, const OutputDevice& rSrcOutDev) +{ + if( ( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) || + ( (rSrcGraphics.GetLayout() & SalLayoutFlags::BiDiRtl) || rSrcOutDev.IsRTLEnabled()) ) + { + SalTwoRect aPosAry2 = rPosAry; + if( (rSrcGraphics.GetLayout() & SalLayoutFlags::BiDiRtl) || rSrcOutDev.IsRTLEnabled() ) + mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, rSrcOutDev ); + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev ); + copyBits( aPosAry2, &rSrcGraphics ); + } + else + copyBits( rPosAry, &rSrcGraphics ); +} + +void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + SalTwoRect aPosAry2 = rPosAry; + mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev ); + drawBitmap( aPosAry2, rSalBitmap ); + } + else + drawBitmap( rPosAry, rSalBitmap ); +} + +void SalGraphics::DrawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rTransparentBitmap, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + SalTwoRect aPosAry2 = rPosAry; + mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev ); + drawBitmap( aPosAry2, rSalBitmap, rTransparentBitmap ); + } + else + drawBitmap( rPosAry, rSalBitmap, rTransparentBitmap ); +} + +void SalGraphics::DrawMask( const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + Color nMaskColor, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + SalTwoRect aPosAry2 = rPosAry; + mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev ); + drawMask( aPosAry2, rSalBitmap, nMaskColor ); + } + else + drawMask( rPosAry, rSalBitmap, nMaskColor ); +} + +std::shared_ptr<SalBitmap> SalGraphics::GetBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( nX, nWidth, rOutDev ); + return getBitmap( nX, nY, nWidth, nHeight ); +} + +Color SalGraphics::GetPixel( tools::Long nX, tools::Long nY, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( nX, rOutDev ); + return getPixel( nX, nY ); +} + +void SalGraphics::Invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( nX, nWidth, rOutDev ); + invert( nX, nY, nWidth, nHeight, nFlags ); +} + +void SalGraphics::Invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + std::unique_ptr<Point[]> pPtAry2(new Point[nPoints]); + bool bCopied = mirror( nPoints, pPtAry, pPtAry2.get(), rOutDev ); + invert( nPoints, bCopied ? pPtAry2.get() : pPtAry, nFlags ); + } + else + invert( nPoints, pPtAry, nFlags ); +} + +bool SalGraphics::DrawEPS( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, void* pPtr, sal_uInt32 nSize, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( nX, nWidth, rOutDev ); + return drawEPS( nX, nY, nWidth, nHeight, pPtr, nSize ); +} + +bool SalGraphics::HitTestNativeScrollbar(ControlPart nPart, const tools::Rectangle& rControlRegion, + const Point& aPos, bool& rIsInside, const OutputDevice& rOutDev) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + Point pt( aPos ); + tools::Rectangle rgn( rControlRegion ); + pt.setX( mirror2( pt.X(), rOutDev ) ); + mirror( rgn, rOutDev ); + return forWidget()->hitTestNativeControl( ControlType::Scrollbar, nPart, rgn, pt, rIsInside ); + } + else + return forWidget()->hitTestNativeControl( ControlType::Scrollbar, nPart, rControlRegion, aPos, rIsInside ); +} + +void SalGraphics::mirror( ImplControlValue& rVal, const OutputDevice& rOutDev ) const +{ + switch( rVal.getType() ) + { + case ControlType::Slider: + { + SliderValue* pSlVal = static_cast<SliderValue*>(&rVal); + mirror(pSlVal->maThumbRect, rOutDev); + } + break; + case ControlType::Scrollbar: + { + ScrollbarValue* pScVal = static_cast<ScrollbarValue*>(&rVal); + mirror(pScVal->maThumbRect, rOutDev); + mirror(pScVal->maButton1Rect, rOutDev); + mirror(pScVal->maButton2Rect, rOutDev); + } + break; + case ControlType::Spinbox: + case ControlType::SpinButtons: + { + SpinbuttonValue* pSpVal = static_cast<SpinbuttonValue*>(&rVal); + mirror(pSpVal->maUpperRect, rOutDev); + mirror(pSpVal->maLowerRect, rOutDev); + } + break; + case ControlType::Toolbar: + { + ToolbarValue* pTVal = static_cast<ToolbarValue*>(&rVal); + mirror(pTVal->maGripRect, rOutDev); + } + break; + default: break; + } +} + +bool SalGraphics::DrawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion, + ControlState nState, const ImplControlValue& aValue, + const OUString& aCaption, const OutputDevice& rOutDev, + const Color& rBackgroundColor) +{ + bool bRet = false; + tools::Rectangle aControlRegion(rControlRegion); + if (aControlRegion.IsEmpty() || aControlRegion.GetWidth() <= 0 || aControlRegion.GetHeight() <= 0) + return bRet; + + bool bLayoutRTL = true && (m_nLayout & SalLayoutFlags::BiDiRtl); + bool bDevRTL = rOutDev.IsRTLEnabled(); + bool bIsLOK = comphelper::LibreOfficeKit::isActive(); + if( (bLayoutRTL || bDevRTL) && !bIsLOK ) + { + mirror(aControlRegion, rOutDev); + std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone()); + mirror( *mirrorValue, rOutDev ); + bRet = forWidget()->drawNativeControl(nType, nPart, aControlRegion, nState, *mirrorValue, aCaption, rBackgroundColor); + } + else + bRet = forWidget()->drawNativeControl(nType, nPart, aControlRegion, nState, aValue, aCaption, rBackgroundColor); + + if (bRet && m_pWidgetDraw) + handleDamage(aControlRegion); + return bRet; +} + +bool SalGraphics::GetNativeControlRegion( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion, ControlState nState, + const ImplControlValue& aValue, + tools::Rectangle &rNativeBoundingRegion, tools::Rectangle &rNativeContentRegion, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + tools::Rectangle rgn( rControlRegion ); + mirror( rgn, rOutDev ); + std::unique_ptr< ImplControlValue > mirrorValue( aValue.clone()); + mirror( *mirrorValue, rOutDev ); + if (forWidget()->getNativeControlRegion(nType, nPart, rgn, nState, *mirrorValue, OUString(), rNativeBoundingRegion, rNativeContentRegion)) + { + mirror( rNativeBoundingRegion, rOutDev, true ); + mirror( rNativeContentRegion, rOutDev, true ); + return true; + } + return false; + } + else + return forWidget()->getNativeControlRegion(nType, nPart, rControlRegion, nState, aValue, OUString(), rNativeBoundingRegion, rNativeContentRegion); +} + +bool SalGraphics::BlendBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rBitmap, + const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + SalTwoRect aPosAry2 = rPosAry; + mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev ); + return blendBitmap( aPosAry2, rBitmap ); + } + else + return blendBitmap( rPosAry, rBitmap ); +} + +bool SalGraphics::BlendAlphaBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSrcBitmap, + const SalBitmap& rMaskBitmap, + const SalBitmap& rAlphaBitmap, + const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + SalTwoRect aPosAry2 = rPosAry; + mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev ); + return blendAlphaBitmap( aPosAry2, rSrcBitmap, rMaskBitmap, rAlphaBitmap ); + } + else + return blendAlphaBitmap( rPosAry, rSrcBitmap, rMaskBitmap, rAlphaBitmap ); +} + +bool SalGraphics::DrawAlphaBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSourceBitmap, + const SalBitmap& rAlphaBitmap, + const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + SalTwoRect aPosAry2 = rPosAry; + mirror( aPosAry2.mnDestX, aPosAry2.mnDestWidth, rOutDev ); + return drawAlphaBitmap( aPosAry2, rSourceBitmap, rAlphaBitmap ); + } + else + return drawAlphaBitmap( rPosAry, rSourceBitmap, rAlphaBitmap ); +} + +bool SalGraphics::DrawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, + double fAlpha, + const OutputDevice& rOutDev) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + { + // mirroring set + const basegfx::B2DHomMatrix& rMirror(getMirror(rOutDev)); + if (!rMirror.isIdentity()) + { + basegfx::B2DPolygon aPoints({rNull, rX, rY}); + basegfx::B2DRange aBoundingBox(aPoints.getB2DRange()); + auto aTranslateToMirroredBounds = createTranslateToMirroredBounds(aBoundingBox, rMirror); + + basegfx::B2DPoint aNull = aTranslateToMirroredBounds * rNull; + basegfx::B2DPoint aX = aTranslateToMirroredBounds * rX; + basegfx::B2DPoint aY = aTranslateToMirroredBounds * rY; + + return drawTransformedBitmap(aNull, aX, aY, rSourceBitmap, pAlphaBitmap, fAlpha); + } + } + + return drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap, fAlpha); +} + +bool SalGraphics::HasFastDrawTransformedBitmap() const +{ + return hasFastDrawTransformedBitmap(); +} + +bool SalGraphics::DrawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, + sal_uInt8 nTransparency, const OutputDevice& rOutDev ) +{ + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || rOutDev.IsRTLEnabled() ) + mirror( nX, nWidth, rOutDev ); + + return drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency ); +} + +OUString SalGraphics::getRenderBackendName() const +{ + if (GetImpl()) + return GetImpl()->getRenderBackendName(); + return OUString(); +} + +bool SalGraphics::ShouldDownscaleIconsAtSurface(double* pScaleOut) const +{ + if (pScaleOut) + *pScaleOut = comphelper::LibreOfficeKit::getDPIScale(); + return comphelper::LibreOfficeKit::isActive(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx new file mode 100644 index 0000000000..af281127ba --- /dev/null +++ b/vcl/source/gdi/sallayout.cxx @@ -0,0 +1,1182 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <iostream> +#include <iomanip> + +#include <sal/log.hxx> + +#include <cstdio> + +#include <math.h> + +#include <ImplLayoutArgs.hxx> +#include <salgdi.hxx> +#include <sallayout.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <i18nlangtag/lang.h> + +#include <vcl/svapp.hxx> + +#include <algorithm> +#include <memory> + +#include <impglyphitem.hxx> + +// Glyph Flags +#define GF_FONTMASK 0xF0000000 +#define GF_FONTSHIFT 28 + + +sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang ) +{ + // currently only conversion from ASCII digits is interesting + if( (nChar < '0') || ('9' < nChar) ) + return nChar; + + int nOffset; + // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region. + // CAVEAT! To some like Mongolian MS assigned the same primary language + // although the script type is different! + LanguageType pri = primary(eLang); + if( pri == primary(LANGUAGE_ARABIC_SAUDI_ARABIA) ) + nOffset = 0x0660 - '0'; // arabic-indic digits + else if ( pri.anyOf( + primary(LANGUAGE_FARSI), + primary(LANGUAGE_URDU_PAKISTAN), + primary(LANGUAGE_PUNJABI), //??? + primary(LANGUAGE_SINDHI))) + nOffset = 0x06F0 - '0'; // eastern arabic-indic digits + else if ( pri == primary(LANGUAGE_BENGALI) ) + nOffset = 0x09E6 - '0'; // bengali + else if ( pri == primary(LANGUAGE_HINDI) ) + nOffset = 0x0966 - '0'; // devanagari + else if ( pri.anyOf( + primary(LANGUAGE_AMHARIC_ETHIOPIA), + primary(LANGUAGE_TIGRIGNA_ETHIOPIA))) + // TODO case: + nOffset = 0x1369 - '0'; // ethiopic + else if ( pri == primary(LANGUAGE_GUJARATI) ) + nOffset = 0x0AE6 - '0'; // gujarati +#ifdef LANGUAGE_GURMUKHI // TODO case: + else if ( pri == primary(LANGUAGE_GURMUKHI) ) + nOffset = 0x0A66 - '0'; // gurmukhi +#endif + else if ( pri == primary(LANGUAGE_KANNADA) ) + nOffset = 0x0CE6 - '0'; // kannada + else if ( pri == primary(LANGUAGE_KHMER)) + nOffset = 0x17E0 - '0'; // khmer + else if ( pri == primary(LANGUAGE_LAO) ) + nOffset = 0x0ED0 - '0'; // lao + else if ( pri == primary(LANGUAGE_MALAYALAM) ) + nOffset = 0x0D66 - '0'; // malayalam + else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO)) + { + if (eLang.anyOf( + LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA, + LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA, + LANGUAGE_MONGOLIAN_MONGOLIAN_LSO)) + nOffset = 0x1810 - '0'; // mongolian + else + nOffset = 0; // mongolian cyrillic + } + else if ( pri == primary(LANGUAGE_BURMESE) ) + nOffset = 0x1040 - '0'; // myanmar + else if ( pri == primary(LANGUAGE_ODIA) ) + nOffset = 0x0B66 - '0'; // odia + else if ( pri == primary(LANGUAGE_TAMIL) ) + nOffset = 0x0BE7 - '0'; // tamil + else if ( pri == primary(LANGUAGE_TELUGU) ) + nOffset = 0x0C66 - '0'; // telugu + else if ( pri == primary(LANGUAGE_THAI) ) + nOffset = 0x0E50 - '0'; // thai + else if ( pri == primary(LANGUAGE_TIBETAN) ) + nOffset = 0x0F20 - '0'; // tibetan + else + { + nOffset = 0; + } + + nChar += nOffset; + return nChar; +} + +SalLayout::SalLayout() +: mnMinCharPos( -1 ), + mnEndCharPos( -1 ), + maLanguageTag( LANGUAGE_DONTKNOW ), + mnOrientation( 0 ), + maDrawOffset( 0, 0 ), + mbSubpixelPositioning(false) +{} + +SalLayout::~SalLayout() +{} + +void SalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs ) +{ + mnMinCharPos = rArgs.mnMinCharPos; + mnEndCharPos = rArgs.mnEndCharPos; + mnOrientation = rArgs.mnOrientation; + maLanguageTag = rArgs.maLanguageTag; +} + +basegfx::B2DPoint SalLayout::GetDrawPosition(const basegfx::B2DPoint& rRelative) const +{ + basegfx::B2DPoint aPos(maDrawBase); + basegfx::B2DPoint aOfs(rRelative.getX() + maDrawOffset.X(), + rRelative.getY() + maDrawOffset.Y()); + + if( mnOrientation == 0_deg10 ) + aPos += aOfs; + else + { + // cache trigonometric results + static Degree10 nOldOrientation(0); + static double fCos = 1.0, fSin = 0.0; + if( nOldOrientation != mnOrientation ) + { + nOldOrientation = mnOrientation; + double fRad = toRadians(mnOrientation); + fCos = cos( fRad ); + fSin = sin( fRad ); + } + + double fX = aOfs.getX(); + double fY = aOfs.getY(); + if (mbSubpixelPositioning) + { + double nX = +fCos * fX + fSin * fY; + double nY = +fCos * fY - fSin * fX; + aPos += basegfx::B2DPoint(nX, nY); + } + else + { + tools::Long nX = static_cast<tools::Long>( +fCos * fX + fSin * fY ); + tools::Long nY = static_cast<tools::Long>( +fCos * fY - fSin * fX ); + aPos += basegfx::B2DPoint(nX, nY); + } + } + + return aPos; +} + +bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const +{ + bool bAllOk = true; + bool bOneOk = false; + + basegfx::B2DPolyPolygon aGlyphOutline; + + basegfx::B2DPoint aPos; + const GlyphItem* pGlyph; + int nStart = 0; + const LogicalFontInstance* pGlyphFont; + while (GetNextGlyph(&pGlyph, aPos, nStart, &pGlyphFont)) + { + // get outline of individual glyph, ignoring "empty" glyphs + bool bSuccess = pGlyph->GetGlyphOutline(pGlyphFont, aGlyphOutline); + bAllOk &= bSuccess; + bOneOk |= bSuccess; + // only add non-empty outlines + if( bSuccess && (aGlyphOutline.count() > 0) ) + { + if( aPos.getX() || aPos.getY() ) + { + aGlyphOutline.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos.getX(), aPos.getY())); + } + + // insert outline at correct position + rVector.push_back( aGlyphOutline ); + } + } + + return (bAllOk && bOneOk); +} + +bool SalLayout::GetBoundRect(tools::Rectangle& rRect) const +{ + bool bRet = false; + rRect.SetEmpty(); + + tools::Rectangle aRectangle; + + basegfx::B2DPoint aPos; + const GlyphItem* pGlyph; + int nStart = 0; + const LogicalFontInstance* pGlyphFont; + while (GetNextGlyph(&pGlyph, aPos, nStart, &pGlyphFont)) + { + // get bounding rectangle of individual glyph + if (pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle)) + { + if (!aRectangle.IsEmpty()) + { + aRectangle.AdjustLeft(std::floor(aPos.getX())); + aRectangle.AdjustRight(std::ceil(aPos.getX())); + aRectangle.AdjustTop(std::floor(aPos.getY())); + aRectangle.AdjustBottom(std::ceil(aPos.getY())); + + // merge rectangle + if (rRect.IsEmpty()) + rRect = aRectangle; + else + rRect.Union(aRectangle); + } + bRet = true; + } + } + + return bRet; +} + +SalLayoutGlyphs SalLayout::GetGlyphs() const +{ + return SalLayoutGlyphs(); // invalid +} + +double GenericSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const +{ + if (pCharWidths) + GetCharWidths(*pCharWidths, rStr); + + return GetTextWidth(); +} + +// the text width is the maximum logical extent of all glyphs +double GenericSalLayout::GetTextWidth() const +{ + if (!m_GlyphItems.IsValid()) + return 0; + + double nWidth = 0; + for (auto const& aGlyphItem : m_GlyphItems) + nWidth += aGlyphItem.newWidth(); + + return nWidth; +} + +void GenericSalLayout::Justify(double nNewWidth) +{ + double nOldWidth = GetTextWidth(); + if( !nOldWidth || nNewWidth==nOldWidth ) + return; + + if (!m_GlyphItems.IsValid()) + { + return; + } + // find rightmost glyph, it won't get stretched + std::vector<GlyphItem>::iterator pGlyphIterRight = m_GlyphItems.begin(); + pGlyphIterRight += m_GlyphItems.size() - 1; + std::vector<GlyphItem>::iterator pGlyphIter; + // count stretchable glyphs + int nStretchable = 0; + double nMaxGlyphWidth = 0; + for(pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter) + { + if( !pGlyphIter->IsInCluster() ) + ++nStretchable; + if (nMaxGlyphWidth < pGlyphIter->origWidth()) + nMaxGlyphWidth = pGlyphIter->origWidth(); + } + + // move rightmost glyph to requested position + nOldWidth -= pGlyphIterRight->origWidth(); + if( nOldWidth <= 0 ) + return; + if( nNewWidth < nMaxGlyphWidth) + nNewWidth = nMaxGlyphWidth; + nNewWidth -= pGlyphIterRight->origWidth(); + pGlyphIterRight->setLinearPosX( nNewWidth ); + + // justify glyph widths and positions + double nDiffWidth = nNewWidth - nOldWidth; + if( nDiffWidth >= 0) // expanded case + { + // expand width by distributing space between glyphs evenly + int nDeltaSum = 0; + for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter ) + { + // move glyph to justified position + pGlyphIter->adjustLinearPosX(nDeltaSum); + + // do not stretch non-stretchable glyphs + if( pGlyphIter->IsInCluster() || (nStretchable <= 0) ) + continue; + + // distribute extra space equally to stretchable glyphs + double nDeltaWidth = nDiffWidth / nStretchable--; + nDiffWidth -= nDeltaWidth; + pGlyphIter->addNewWidth(nDeltaWidth); + nDeltaSum += nDeltaWidth; + } + } + else // condensed case + { + // squeeze width by moving glyphs proportionally + double fSqueeze = nNewWidth / nOldWidth; + if(m_GlyphItems.size() > 1) + { + for( pGlyphIter = m_GlyphItems.begin(); ++pGlyphIter != pGlyphIterRight;) + { + double nX = pGlyphIter->linearPos().getX(); + nX = nX * fSqueeze; + pGlyphIter->setLinearPosX( nX ); + } + } + // adjust glyph widths to new positions + for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter ) + pGlyphIter->setNewWidth( pGlyphIter[1].linearPos().getX() - pGlyphIter[0].linearPos().getX()); + } +} + +// returns asian kerning values in quarter of character width units +// to enable automatic halfwidth substitution for fullwidth punctuation +// return value is negative for l, positive for r, zero for neutral +// TODO: handle vertical layout as proposed in commit 43bf2ad49c2b3989bbbe893e4fee2e032a3920f5? +static int lcl_CalcAsianKerning(sal_UCS4 c, bool bLeft) +{ + // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html + static const signed char nTable[0x30] = + { + 0, -2, -2, 0, 0, 0, 0, 0, +2, -2, +2, -2, +2, -2, +2, -2, + +2, -2, 0, 0, +2, -2, +2, -2, 0, 0, 0, 0, 0, +2, -2, -2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, +2, +2, -2, -2 + }; + + int nResult = 0; + if( (c >= 0x3000) && (c < 0x3030) ) + nResult = nTable[ c - 0x3000 ]; + else switch( c ) + { + case 0x30FB: + nResult = bLeft ? -1 : +1; // 25% left/right/top/bottom + break; + case 0x2019: case 0x201D: + case 0xFF01: case 0xFF09: case 0xFF0C: + case 0xFF1A: case 0xFF1B: + nResult = -2; + break; + case 0x2018: case 0x201C: + case 0xFF08: + nResult = +2; + break; + default: + break; + } + + return nResult; +} + +static bool lcl_CanApplyAsianKerning(sal_Unicode cp) +{ + return (0x3000 == (cp & 0xFF00)) || (0xFF00 == (cp & 0xFF00)) || (0x2010 == (cp & 0xFFF0)); +} + +void GenericSalLayout::ApplyAsianKerning(std::u16string_view rStr) +{ + const int nLength = rStr.size(); + double nOffset = 0; + + for (std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin(), + pGlyphIterEnd = m_GlyphItems.end(); + pGlyphIter != pGlyphIterEnd; ++pGlyphIter) + { + const int n = pGlyphIter->charPos(); + if (n < nLength - 1) + { + // ignore code ranges that are not affected by asian punctuation compression + const sal_Unicode cCurrent = rStr[n]; + if (!lcl_CanApplyAsianKerning(cCurrent)) + continue; + const sal_Unicode cNext = rStr[n + 1]; + if (!lcl_CanApplyAsianKerning(cNext)) + continue; + + // calculate compression values + const int nKernCurrent = +lcl_CalcAsianKerning(cCurrent, true); + if (nKernCurrent == 0) + continue; + const int nKernNext = -lcl_CalcAsianKerning(cNext, false); + if (nKernNext == 0) + continue; + + // apply punctuation compression to logical glyph widths + double nDelta = (nKernCurrent < nKernNext) ? nKernCurrent : nKernNext; + if (nDelta < 0) + { + nDelta = (nDelta * pGlyphIter->origWidth() + 2) / 4; + if( pGlyphIter+1 == pGlyphIterEnd ) + pGlyphIter->addNewWidth( nDelta ); + nOffset += nDelta; + } + } + + // adjust the glyph positions to the new glyph widths + if( pGlyphIter+1 != pGlyphIterEnd ) + pGlyphIter->adjustLinearPosX(nOffset); + } +} + +void GenericSalLayout::GetCaretPositions(std::vector<double>& rCaretPositions, + const OUString& rStr) const +{ + const int nCaretPositions = (mnEndCharPos - mnMinCharPos) * 2; + + rCaretPositions.clear(); + rCaretPositions.resize(nCaretPositions, -1); + + if (m_GlyphItems.empty()) + return; + + std::vector<double> aCharWidths; + GetCharWidths(aCharWidths, rStr); + + // calculate caret positions using glyph array + for (auto const& aGlyphItem : m_GlyphItems) + { + auto nCurrX = aGlyphItem.linearPos().getX() - aGlyphItem.xOffset(); + auto nCharStart = aGlyphItem.charPos(); + auto nCharEnd = nCharStart + aGlyphItem.charCount() - 1; + if (!aGlyphItem.IsRTLGlyph()) + { + // unchanged positions for LTR case + for (int i = nCharStart; i <= nCharEnd; i++) + { + int n = i - mnMinCharPos; + int nCurrIdx = 2 * n; + + auto nLeft = nCurrX; + nCurrX += aCharWidths[n]; + auto nRight = nCurrX; + + rCaretPositions[nCurrIdx] = nLeft; + rCaretPositions[nCurrIdx + 1] = nRight; + } + } + else + { + // reverse positions for RTL case + for (int i = nCharEnd; i >= nCharStart; i--) + { + int n = i - mnMinCharPos; + int nCurrIdx = 2 * n; + + auto nRight = nCurrX; + nCurrX += aCharWidths[n]; + auto nLeft = nCurrX; + + rCaretPositions[nCurrIdx] = nLeft; + rCaretPositions[nCurrIdx + 1] = nRight; + } + } + } +} + +sal_Int32 GenericSalLayout::GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const +{ + std::vector<double> aCharWidths; + GetCharWidths(aCharWidths, {}); + + double nWidth = 0; + for( int i = mnMinCharPos; i < mnEndCharPos; ++i ) + { + double nDelta = aCharWidths[ i - mnMinCharPos ] * nFactor; + + if (nDelta != 0) + { + nWidth += nDelta; + if( nWidth > nMaxWidth ) + return i; + + nWidth += nCharExtra; + } + } + + return -1; +} + +bool GenericSalLayout::GetNextGlyph(const GlyphItem** pGlyph, + basegfx::B2DPoint& rPos, int& nStart, + const LogicalFontInstance** ppGlyphFont) const +{ + std::vector<GlyphItem>::const_iterator pGlyphIter = m_GlyphItems.begin(); + std::vector<GlyphItem>::const_iterator pGlyphIterEnd = m_GlyphItems.end(); + pGlyphIter += nStart; + + // find next glyph in substring + for(; pGlyphIter != pGlyphIterEnd; ++nStart, ++pGlyphIter ) + { + int n = pGlyphIter->charPos(); + if( (mnMinCharPos <= n) && (n < mnEndCharPos) ) + break; + } + + // return zero if no more glyph found + if( nStart >= static_cast<int>(m_GlyphItems.size()) ) + return false; + + if( pGlyphIter == pGlyphIterEnd ) + return false; + + // update return data with glyph info + *pGlyph = &(*pGlyphIter); + ++nStart; + if (ppGlyphFont) + *ppGlyphFont = m_GlyphItems.GetFont().get(); + + // calculate absolute position in pixel units + basegfx::B2DPoint aRelativePos = pGlyphIter->linearPos(); + + rPos = GetDrawPosition( aRelativePos ); + + return true; +} + +void GenericSalLayout::MoveGlyph(int nStart, double nNewXPos) +{ + if( nStart >= static_cast<int>(m_GlyphItems.size()) ) + return; + + std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin(); + pGlyphIter += nStart; + + // the nNewXPos argument determines the new cell position + // as RTL-glyphs are right justified in their cell + // the cell position needs to be adjusted to the glyph position + if( pGlyphIter->IsRTLGlyph() ) + nNewXPos += pGlyphIter->newWidth() - pGlyphIter->origWidth(); + // calculate the x-offset to the old position + double nXDelta = nNewXPos - pGlyphIter->linearPos().getX() + pGlyphIter->xOffset(); + // adjust all following glyph positions if needed + if( nXDelta != 0 ) + { + for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter ) + { + pGlyphIter->adjustLinearPosX(nXDelta); + } + } +} + +void GenericSalLayout::DropGlyph( int nStart ) +{ + if( nStart >= static_cast<int>(m_GlyphItems.size())) + return; + + std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin(); + pGlyphIter += nStart; + pGlyphIter->dropGlyph(); +} + +void GenericSalLayout::Simplify( bool bIsBase ) +{ + // remove dropped glyphs inplace + size_t j = 0; + for(size_t i = 0; i < m_GlyphItems.size(); i++ ) + { + if (bIsBase && m_GlyphItems[i].IsDropped()) + continue; + if (!bIsBase && m_GlyphItems[i].glyphId() == 0) + continue; + + if( i != j ) + { + m_GlyphItems[j] = m_GlyphItems[i]; + } + j += 1; + } + m_GlyphItems.erase(m_GlyphItems.begin() + j, m_GlyphItems.end()); +} + +MultiSalLayout::MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout ) +: mnLevel( 1 ) +, mbIncomplete( false ) +{ + assert(dynamic_cast<GenericSalLayout*>(pBaseLayout.get())); + + mpLayouts[ 0 ].reset(static_cast<GenericSalLayout*>(pBaseLayout.release())); +} + +std::unique_ptr<SalLayout> MultiSalLayout::ReleaseBaseLayout() +{ + return std::move(mpLayouts[0]); +} + +void MultiSalLayout::SetIncomplete(bool bIncomplete) +{ + mbIncomplete = bIncomplete; + maFallbackRuns[mnLevel-1] = ImplLayoutRuns(); +} + +MultiSalLayout::~MultiSalLayout() +{ +} + +void MultiSalLayout::AddFallback( std::unique_ptr<SalLayout> pFallback, + ImplLayoutRuns const & rFallbackRuns) +{ + assert(dynamic_cast<GenericSalLayout*>(pFallback.get())); + if( mnLevel >= MAX_FALLBACK ) + return; + + mpLayouts[ mnLevel ].reset(static_cast<GenericSalLayout*>(pFallback.release())); + maFallbackRuns[ mnLevel-1 ] = rFallbackRuns; + ++mnLevel; +} + +bool MultiSalLayout::LayoutText( vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* ) +{ + if( mnLevel <= 1 ) + return false; + if (!mbIncomplete) + maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns; + return true; +} + +void MultiSalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs ) +{ + SalLayout::AdjustLayout( rArgs ); + vcl::text::ImplLayoutArgs aMultiArgs = rArgs; + std::vector<double> aJustificationArray; + + if( !rArgs.HasDXArray() && rArgs.mnLayoutWidth ) + { + // for stretched text in a MultiSalLayout the target width needs to be + // distributed by individually adjusting its virtual character widths + double nTargetWidth = aMultiArgs.mnLayoutWidth; + aMultiArgs.mnLayoutWidth = 0; + + // we need to get the original unmodified layouts ready + for( int n = 0; n < mnLevel; ++n ) + mpLayouts[n]->SalLayout::AdjustLayout( aMultiArgs ); + // then we can measure the unmodified metrics + int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos; + FillDXArray( &aJustificationArray, {} ); + // #i17359# multilayout is not simplified yet, so calculating the + // unjustified width needs handholding; also count the number of + // stretchable virtual char widths + double nOrigWidth = 0; + int nStretchable = 0; + for( int i = 0; i < nCharCount; ++i ) + { + // convert array from widths to sum of widths + nOrigWidth += aJustificationArray[i]; + if( aJustificationArray[i] > 0 ) + ++nStretchable; + } + + // now we are able to distribute the extra width over the virtual char widths + if( nOrigWidth && (nTargetWidth != nOrigWidth) ) + { + double nDiffWidth = nTargetWidth - nOrigWidth; + double nWidthSum = 0; + for( int i = 0; i < nCharCount; ++i ) + { + double nJustWidth = aJustificationArray[i]; + if( (nJustWidth > 0) && (nStretchable > 0) ) + { + double nDeltaWidth = nDiffWidth / nStretchable; + nJustWidth += nDeltaWidth; + nDiffWidth -= nDeltaWidth; + --nStretchable; + } + nWidthSum += nJustWidth; + aJustificationArray[i] = nWidthSum; + } + if( nWidthSum != nTargetWidth ) + aJustificationArray[ nCharCount-1 ] = nTargetWidth; + + // change the DXArray temporarily (just for the justification) + aMultiArgs.mpDXArray = aJustificationArray.data(); + } + } + + ImplAdjustMultiLayout(rArgs, aMultiArgs, aMultiArgs.mpDXArray); +} + +void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs, + vcl::text::ImplLayoutArgs& rMultiArgs, + const double* pMultiDXArray) +{ + // Compute rtl flags, since in some scripts glyphs/char order can be + // reversed for a few character sequences e.g. Myanmar + std::vector<bool> vRtl(rArgs.mnEndCharPos - rArgs.mnMinCharPos, false); + rArgs.ResetPos(); + bool bRtl; + int nRunStart, nRunEnd; + while (rArgs.GetNextRun(&nRunStart, &nRunEnd, &bRtl)) + { + if (bRtl) std::fill(vRtl.begin() + (nRunStart - rArgs.mnMinCharPos), + vRtl.begin() + (nRunEnd - rArgs.mnMinCharPos), true); + } + rArgs.ResetPos(); + + // prepare "merge sort" + int nStartOld[ MAX_FALLBACK ]; + int nStartNew[ MAX_FALLBACK ]; + const GlyphItem* pGlyphs[MAX_FALLBACK]; + bool bValid[MAX_FALLBACK] = { false }; + + basegfx::B2DPoint aPos; + int nLevel = 0, n; + for( n = 0; n < mnLevel; ++n ) + { + // now adjust the individual components + if( n > 0 ) + { + rMultiArgs.maRuns = maFallbackRuns[ n-1 ]; + rMultiArgs.mnFlags |= SalLayoutFlags::ForFallback; + } + mpLayouts[n]->AdjustLayout( rMultiArgs ); + + // remove unused parts of component + if( n > 0 ) + { + if (mbIncomplete && (n == mnLevel-1)) + mpLayouts[n]->Simplify( true ); + else + mpLayouts[n]->Simplify( false ); + } + + // prepare merging components + nStartNew[ nLevel ] = nStartOld[ nLevel ] = 0; + bValid[nLevel] = mpLayouts[n]->GetNextGlyph(&pGlyphs[nLevel], aPos, nStartNew[nLevel]); + + if( (n > 0) && !bValid[ nLevel ] ) + { + // an empty fallback layout can be released + mpLayouts[n].reset(); + } + else + { + // reshuffle used fallbacks if needed + if( nLevel != n ) + { + mpLayouts[ nLevel ] = std::move(mpLayouts[ n ]); + maFallbackRuns[ nLevel ] = maFallbackRuns[ n ]; + } + ++nLevel; + } + } + mnLevel = nLevel; + + // prepare merge the fallback levels + double nXPos = 0; + for( n = 0; n < nLevel; ++n ) + maFallbackRuns[n].ResetPos(); + + int nFirstValid = -1; + for( n = 0; n < nLevel; ++n ) + { + if(bValid[n]) + { + nFirstValid = n; + break; + } + } + assert(nFirstValid >= 0); + + // get the next codepoint index that needs fallback + int nActiveCharPos = pGlyphs[nFirstValid]->charPos(); + int nActiveCharIndex = nActiveCharPos - mnMinCharPos; + // get the end index of the active run + int nLastRunEndChar = (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex]) ? + rArgs.mnEndCharPos : rArgs.mnMinCharPos - 1; + int nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos(); + // merge the fallback levels + while( bValid[nFirstValid] && (nLevel > 0)) + { + // find best fallback level + for( n = 0; n < nLevel; ++n ) + if( bValid[n] && !maFallbackRuns[n].PosIsInAnyRun( nActiveCharPos ) ) + // fallback level n wins when it requested no further fallback + break; + int nFBLevel = n; + + if( n < nLevel ) + { + // use base(n==0) or fallback(n>=1) level + mpLayouts[n]->MoveGlyph( nStartOld[n], nXPos ); + } + else + { + n = 0; // keep NotDef in base level + } + + if( n > 0 ) + { + // drop the NotDef glyphs in the base layout run if a fallback run exists + while ( + (maFallbackRuns[n-1].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos())) && + (!maFallbackRuns[n].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos())) + ) + { + mpLayouts[0]->DropGlyph( nStartOld[0] ); + nStartOld[0] = nStartNew[0]; + bValid[nFirstValid] = mpLayouts[0]->GetNextGlyph(&pGlyphs[nFirstValid], aPos, nStartNew[0]); + + if( !bValid[nFirstValid] ) + break; + } + } + + // skip to end of layout run and calculate its advance width + double nRunAdvance = 0; + bool bKeepNotDef = (nFBLevel >= nLevel); + for(;;) + { + nRunAdvance += pGlyphs[n]->newWidth(); + + // proceed to next glyph + nStartOld[n] = nStartNew[n]; + int nOrigCharPos = pGlyphs[n]->charPos(); + bValid[n] = mpLayouts[n]->GetNextGlyph(&pGlyphs[n], aPos, nStartNew[n]); + // break after last glyph of active layout + if( !bValid[n] ) + { + // performance optimization (when a fallback layout is no longer needed) + if( n >= nLevel-1 ) + --nLevel; + break; + } + + //If the next character is one which belongs to the next level, then we + //are finished here for now, and we'll pick up after the next level has + //been processed + if ((n+1 < nLevel) && (pGlyphs[n]->charPos() != nOrigCharPos)) + { + if (nOrigCharPos < pGlyphs[n]->charPos()) + { + if (pGlyphs[n+1]->charPos() > nOrigCharPos && (pGlyphs[n+1]->charPos() < pGlyphs[n]->charPos())) + break; + } + else if (nOrigCharPos > pGlyphs[n]->charPos()) + { + if (pGlyphs[n+1]->charPos() > pGlyphs[n]->charPos() && (pGlyphs[n+1]->charPos() < nOrigCharPos)) + break; + } + } + + // break at end of layout run + if( n > 0 ) + { + // skip until end of fallback run + if (!maFallbackRuns[n-1].PosIsInRun(pGlyphs[n]->charPos())) + break; + } + else + { + // break when a fallback is needed and available + bool bNeedFallback = maFallbackRuns[0].PosIsInRun(pGlyphs[nFirstValid]->charPos()); + if( bNeedFallback ) + if (!maFallbackRuns[nLevel-1].PosIsInRun(pGlyphs[nFirstValid]->charPos())) + break; + // break when change from resolved to unresolved base layout run + if( bKeepNotDef && !bNeedFallback ) + { maFallbackRuns[0].NextRun(); break; } + bKeepNotDef = bNeedFallback; + } + // check for reordered glyphs + if (pMultiDXArray && + nRunVisibleEndChar < mnEndCharPos && + nRunVisibleEndChar >= mnMinCharPos && + pGlyphs[n]->charPos() < mnEndCharPos && + pGlyphs[n]->charPos() >= mnMinCharPos) + { + if (vRtl[nActiveCharPos - mnMinCharPos]) + { + if (pMultiDXArray[nRunVisibleEndChar-mnMinCharPos] + >= pMultiDXArray[pGlyphs[n]->charPos() - mnMinCharPos]) + { + nRunVisibleEndChar = pGlyphs[n]->charPos(); + } + } + else if (pMultiDXArray[nRunVisibleEndChar-mnMinCharPos] + <= pMultiDXArray[pGlyphs[n]->charPos() - mnMinCharPos]) + { + nRunVisibleEndChar = pGlyphs[n]->charPos(); + } + } + } + + // if a justification array is available + // => use it directly to calculate the corresponding run width + if (pMultiDXArray) + { + // the run advance is the width from the first char + // in the run to the first char in the next run + nRunAdvance = 0; + nActiveCharIndex = nActiveCharPos - mnMinCharPos; + if (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex]) + { + if (nRunVisibleEndChar > mnMinCharPos && nRunVisibleEndChar <= mnEndCharPos) + nRunAdvance -= pMultiDXArray[nRunVisibleEndChar - 1 - mnMinCharPos]; + if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos) + nRunAdvance += pMultiDXArray[nLastRunEndChar - 1 - mnMinCharPos]; + } + else + { + if (nRunVisibleEndChar >= mnMinCharPos) + nRunAdvance += pMultiDXArray[nRunVisibleEndChar - mnMinCharPos]; + if (nLastRunEndChar >= mnMinCharPos) + nRunAdvance -= pMultiDXArray[nLastRunEndChar - mnMinCharPos]; + } + nLastRunEndChar = nRunVisibleEndChar; + nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos(); + } + + // calculate new x position + nXPos += nRunAdvance; + + // prepare for next fallback run + nActiveCharPos = pGlyphs[nFirstValid]->charPos(); + // it essential that the runs don't get ahead of themselves and in the + // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may + // have already been reached on the base level + for( int i = nFBLevel; --i >= 0;) + { + if (maFallbackRuns[i].GetRun(&nRunStart, &nRunEnd, &bRtl)) + { + if (bRtl) + { + if (nRunStart > nActiveCharPos) + maFallbackRuns[i].NextRun(); + } + else + { + if (nRunEnd <= nActiveCharPos) + maFallbackRuns[i].NextRun(); + } + } + } + } + + mpLayouts[0]->Simplify( true ); +} + +void MultiSalLayout::InitFont() const +{ + if( mnLevel > 0 ) + mpLayouts[0]->InitFont(); +} + +void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const +{ + for( int i = mnLevel; --i >= 0; ) + { + SalLayout& rLayout = *mpLayouts[ i ]; + rLayout.DrawBase() += maDrawBase; + rLayout.DrawOffset() += maDrawOffset; + rLayout.InitFont(); + rLayout.DrawText( rGraphics ); + rLayout.DrawOffset() -= maDrawOffset; + rLayout.DrawBase() -= maDrawBase; + } + // NOTE: now the baselevel font is active again +} + +sal_Int32 MultiSalLayout::GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const +{ + if( mnLevel <= 0 ) + return -1; + if( mnLevel == 1 ) + return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor ); + + int nCharCount = mnEndCharPos - mnMinCharPos; + std::vector<double> aCharWidths; + std::vector<double> aFallbackCharWidths; + mpLayouts[0]->FillDXArray( &aCharWidths, {} ); + + for( int n = 1; n < mnLevel; ++n ) + { + SalLayout& rLayout = *mpLayouts[ n ]; + rLayout.FillDXArray( &aFallbackCharWidths, {} ); + for( int i = 0; i < nCharCount; ++i ) + if( aCharWidths[ i ] == 0 ) + aCharWidths[i] = aFallbackCharWidths[i]; + } + + double nWidth = 0; + for( int i = 0; i < nCharCount; ++i ) + { + nWidth += aCharWidths[ i ] * nFactor; + if( nWidth > nMaxWidth ) + return (i + mnMinCharPos); + nWidth += nCharExtra; + } + + return -1; +} + +double MultiSalLayout::GetTextWidth() const +{ + // Measure text width. There might be holes in each SalLayout due to + // missing chars, so we use GetNextGlyph() to get the glyphs across all + // layouts. + int nStart = 0; + basegfx::B2DPoint aPos; + const GlyphItem* pGlyphItem; + + double nWidth = 0; + while (GetNextGlyph(&pGlyphItem, aPos, nStart)) + nWidth += pGlyphItem->newWidth(); + + return nWidth; +} + +double MultiSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const +{ + if (pCharWidths) + { + // prepare merging of fallback levels + std::vector<double> aTempWidths; + const int nCharCount = mnEndCharPos - mnMinCharPos; + pCharWidths->clear(); + pCharWidths->resize(nCharCount, 0); + + for (int n = mnLevel; --n >= 0;) + { + // query every fallback level + mpLayouts[n]->FillDXArray(&aTempWidths, rStr); + + // calculate virtual char widths using most probable fallback layout + for (int i = 0; i < nCharCount; ++i) + { + // #i17359# restriction: + // one char cannot be resolved from different fallbacks + if ((*pCharWidths)[i] != 0) + continue; + double nCharWidth = aTempWidths[i]; + if (!nCharWidth) + continue; + (*pCharWidths)[i] = nCharWidth; + } + } + } + + return GetTextWidth(); +} + +void MultiSalLayout::GetCaretPositions(std::vector<double>& rCaretPositions, + const OUString& rStr) const +{ + // prepare merging of fallback levels + std::vector<double> aTempPos; + const int nCaretPositions = (mnEndCharPos - mnMinCharPos) * 2; + rCaretPositions.clear(); + rCaretPositions.resize(nCaretPositions, -1); + + for (int n = mnLevel; --n >= 0;) + { + // query every fallback level + mpLayouts[n]->GetCaretPositions(aTempPos, rStr); + + // calculate virtual char widths using most probable fallback layout + for (int i = 0; i < nCaretPositions; ++i) + { + // one char cannot be resolved from different fallbacks + if (rCaretPositions[i] != -1) + continue; + if (aTempPos[i] >= 0) + rCaretPositions[i] = aTempPos[i]; + } + } +} + +bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph, + basegfx::B2DPoint& rPos, int& nStart, + const LogicalFontInstance** ppGlyphFont) const +{ + // NOTE: nStart is tagged with current font index + int nLevel = static_cast<unsigned>(nStart) >> GF_FONTSHIFT; + nStart &= ~GF_FONTMASK; + for(; nLevel < mnLevel; ++nLevel, nStart=0 ) + { + GenericSalLayout& rLayout = *mpLayouts[ nLevel ]; + rLayout.InitFont(); + if (rLayout.GetNextGlyph(pGlyph, rPos, nStart, ppGlyphFont)) + { + int nFontTag = nLevel << GF_FONTSHIFT; + nStart |= nFontTag; + rPos.adjustX(maDrawBase.getX() + maDrawOffset.X()); + rPos.adjustY(maDrawBase.getY() + maDrawOffset.Y()); + return true; + } + } + + // #111016# reset to base level font when done + mpLayouts[0]->InitFont(); + return false; +} + +bool MultiSalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rPPV) const +{ + bool bRet = false; + + for( int i = mnLevel; --i >= 0; ) + { + SalLayout& rLayout = *mpLayouts[ i ]; + rLayout.DrawBase() = maDrawBase; + rLayout.DrawOffset() += maDrawOffset; + rLayout.InitFont(); + bRet |= rLayout.GetOutline(rPPV); + rLayout.DrawOffset() -= maDrawOffset; + } + + return bRet; +} + +bool MultiSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const +{ + // Check the base layout + bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos, nNextCharPos); + + // If base layout returned false, it might be because the character was not + // supported there, so we check fallback layouts. + if (!bValid) + { + for (int i = 1; i < mnLevel; ++i) + { + // - 1 because there is no fallback run for the base layout, IIUC. + if (maFallbackRuns[i - 1].PosIsInAnyRun(nCharPos) && + maFallbackRuns[i - 1].PosIsInAnyRun(nNextCharPos)) + { + bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos, nNextCharPos); + break; + } + } + } + + return bValid; +} + +SalLayoutGlyphs MultiSalLayout::GetGlyphs() const +{ + SalLayoutGlyphs glyphs; + for( int n = 0; n < mnLevel; ++n ) + glyphs.AppendImpl(mpLayouts[n]->GlyphsImpl().clone()); + return glyphs; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/salmisc.cxx b/vcl/source/gdi/salmisc.cxx new file mode 100644 index 0000000000..e8c09ab1d8 --- /dev/null +++ b/vcl/source/gdi/salmisc.cxx @@ -0,0 +1,433 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/salgtype.hxx> +#include <bitmap/bmpfast.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <tools/helpers.hxx> +#include <memory> + +#define IMPL_CASE_SET_FORMAT( Format, BitCount ) \ +case( ScanlineFormat::Format ): \ +{ \ + pFncSetPixel = BitmapReadAccess::SetPixelFor##Format; \ + pDstBuffer->mnBitCount = BitCount; \ +} \ +break + +#define DOUBLE_SCANLINES() \ +while( ( nActY < nHeight1 ) && ( pMapY[ nActY + 1 ] == nMapY ) ) \ +{ \ + memcpy( pDstScanMap[ nActY + 1 ], pDstScan, rDstBuffer.mnScanlineSize ); \ + nActY++; \ +} + +constexpr int TC_TO_PAL_COLORS = 4096; + +static tools::Long ImplIndexFromColor( const BitmapColor& rCol ) +{ + return ( ( static_cast<tools::Long>(rCol.GetBlue()) >> 4) << 8 ) | + ( ( static_cast<tools::Long>(rCol.GetGreen()) >> 4 ) << 4 ) | + ( static_cast<tools::Long>(rCol.GetRed()) >> 4 ); +} + +static void ImplPALToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer& rDstBuffer, + FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel, + Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY ) +{ + const tools::Long nHeight1 = rDstBuffer.mnHeight - 1; + const ColorMask& rSrcMask = rSrcBuffer.maColorMask; + const ColorMask& rDstMask = rDstBuffer.maColorMask; + BitmapPalette aColMap( rSrcBuffer.maPalette.GetEntryCount() ); + BitmapColor* pColMapBuf = aColMap.ImplGetColorBuffer(); + BitmapColor aIndex( 0 ); + + for( sal_uInt16 i = 0, nSrcCount = aColMap.GetEntryCount(), nDstCount = rDstBuffer.maPalette.GetEntryCount(); i < nSrcCount; i++ ) + { + if( ( i < nDstCount ) && ( rSrcBuffer.maPalette[ i ] == rDstBuffer.maPalette[ i ] ) ) + aIndex.SetIndex( sal::static_int_cast<sal_uInt8>(i) ); + else + aIndex.SetIndex( sal::static_int_cast<sal_uInt8>(rDstBuffer.maPalette.GetBestIndex( rSrcBuffer.maPalette[ i ] )) ); + + pColMapBuf[ i ] = aIndex; + } + + for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY) + { + tools::Long nMapY = pMapY[nActY]; + Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]); + + for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX) + pFncSetPixel( pDstScan, nX, pColMapBuf[ pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ).GetIndex() ], rDstMask ); + + DOUBLE_SCANLINES(); + } +} + +static void ImplPALToTC( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer, + FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel, + Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY ) +{ + const tools::Long nHeight1 = rDstBuffer.mnHeight - 1; + const ColorMask& rSrcMask = rSrcBuffer.maColorMask; + const ColorMask& rDstMask = rDstBuffer.maColorMask; + const BitmapColor* pColBuf = rSrcBuffer.maPalette.ImplGetColorBuffer(); + + if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N1BitMsbPal ) + { + const BitmapColor aCol0( pColBuf[ 0 ] ); + const BitmapColor aCol1( pColBuf[ 1 ] ); + tools::Long nMapX; + + for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY) + { + tools::Long nMapY = pMapY[nActY]; + Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]); + + for (tools::Long nX = 0; nX < rDstBuffer.mnWidth;) + { + nMapX = pMapX[ nX ]; + pFncSetPixel( pDstScan, nX++, + pSrcScan[ nMapX >> 3 ] & ( 1 << ( 7 - ( nMapX & 7 ) ) ) ? aCol1 : aCol0, + rDstMask ); + } + + DOUBLE_SCANLINES(); + } + } + else if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N8BitPal ) + { + for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY) + { + tools::Long nMapY = pMapY[nActY]; + Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]); + + for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX) + pFncSetPixel( pDstScan, nX, pColBuf[ pSrcScan[ pMapX[ nX ] ] ], rDstMask ); + + DOUBLE_SCANLINES(); + } + } + else + { + for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY) + { + tools::Long nMapY = pMapY[nActY]; + Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]); + + for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX) + pFncSetPixel( pDstScan, nX, pColBuf[ pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ).GetIndex() ], rDstMask ); + + DOUBLE_SCANLINES(); + } + } +} + +static void ImplTCToTC( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer, + FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel, + Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY ) +{ + const tools::Long nHeight1 = rDstBuffer.mnHeight - 1; + const ColorMask& rSrcMask = rSrcBuffer.maColorMask; + const ColorMask& rDstMask = rDstBuffer.maColorMask; + + if( RemoveScanline( rSrcBuffer.mnFormat ) == ScanlineFormat::N24BitTcBgr ) + { + BitmapColor aCol; + sal_uInt8* pPixel = nullptr; + + for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY) + { + tools::Long nMapY = pMapY[nActY]; + Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]); + + for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX) + { + pPixel = pSrcScan + pMapX[ nX ] * 3; + aCol.SetBlue( *pPixel++ ); + aCol.SetGreen( *pPixel++ ); + aCol.SetRed( *pPixel ); + pFncSetPixel( pDstScan, nX, aCol, rDstMask ); + } + + DOUBLE_SCANLINES() + } + } + else + { + for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY) + { + tools::Long nMapY = pMapY[nActY]; + Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]); + + for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX) + pFncSetPixel( pDstScan, nX, pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ), rDstMask ); + + DOUBLE_SCANLINES(); + } + } +} + +static void ImplTCToPAL( const BitmapBuffer& rSrcBuffer, BitmapBuffer const & rDstBuffer, + FncGetPixel pFncGetPixel, FncSetPixel pFncSetPixel, + Scanline* pSrcScanMap, Scanline* pDstScanMap, sal_Int32 const * pMapX, const sal_Int32* pMapY ) +{ + const tools::Long nHeight1 = rDstBuffer.mnHeight- 1; + const ColorMask& rSrcMask = rSrcBuffer.maColorMask; + const ColorMask& rDstMask = rDstBuffer.maColorMask; + std::unique_ptr<sal_uInt8[]> pColToPalMap(new sal_uInt8[ TC_TO_PAL_COLORS ]); + BitmapColor aIndex( 0 ); + + for( tools::Long nR = 0; nR < 16; nR++ ) + { + for( tools::Long nG = 0; nG < 16; nG++ ) + { + for( tools::Long nB = 0; nB < 16; nB++ ) + { + BitmapColor aCol( sal::static_int_cast<sal_uInt8>(nR << 4), + sal::static_int_cast<sal_uInt8>(nG << 4), + sal::static_int_cast<sal_uInt8>(nB << 4) ); + pColToPalMap[ ImplIndexFromColor( aCol ) ] = static_cast<sal_uInt8>(rDstBuffer.maPalette.GetBestIndex( aCol )); + } + } + } + + for (tools::Long nActY = 0; nActY < rDstBuffer.mnHeight; ++nActY) + { + tools::Long nMapY = pMapY[nActY]; + Scanline pSrcScan(pSrcScanMap[nMapY]), pDstScan(pDstScanMap[nActY]); + + for (tools::Long nX = 0; nX < rDstBuffer.mnWidth; ++nX) + { + aIndex.SetIndex( pColToPalMap[ ImplIndexFromColor( pFncGetPixel( pSrcScan, pMapX[ nX ], rSrcMask ) ) ] ); + pFncSetPixel( pDstScan, nX, aIndex, rDstMask ); + } + + DOUBLE_SCANLINES(); + } +} + +std::optional<BitmapBuffer> StretchAndConvert( + const BitmapBuffer& rSrcBuffer, const SalTwoRect& rTwoRect, + ScanlineFormat nDstBitmapFormat, std::optional<BitmapPalette> pDstPal, const ColorMask* pDstMask ) +{ + FncGetPixel pFncGetPixel; + FncSetPixel pFncSetPixel; + std::optional<BitmapBuffer> pDstBuffer(std::in_place); + + // set function for getting pixels + pFncGetPixel = BitmapReadAccess::GetPixelFunction( rSrcBuffer.mnFormat ); + if( !pFncGetPixel ) + { + // should never come here + // initialize pFncGetPixel to something valid that is + // least likely to crash + pFncGetPixel = BitmapReadAccess::GetPixelForN1BitMsbPal; + OSL_FAIL( "unknown read format" ); + } + + // set function for setting pixels + const ScanlineFormat nDstScanlineFormat = RemoveScanline( nDstBitmapFormat ); + switch( nDstScanlineFormat ) + { + IMPL_CASE_SET_FORMAT( N1BitMsbPal, 1 ); + IMPL_CASE_SET_FORMAT( N8BitPal, 8 ); + IMPL_CASE_SET_FORMAT( N24BitTcBgr, 24 ); + IMPL_CASE_SET_FORMAT( N24BitTcRgb, 24 ); + IMPL_CASE_SET_FORMAT( N32BitTcAbgr, 32 ); + IMPL_CASE_SET_FORMAT( N32BitTcArgb, 32 ); + IMPL_CASE_SET_FORMAT( N32BitTcBgra, 32 ); + IMPL_CASE_SET_FORMAT( N32BitTcRgba, 32 ); + IMPL_CASE_SET_FORMAT( N32BitTcMask, 32 ); + + default: + // should never come here + // initialize pFncSetPixel to something valid that is + // least likely to crash + pFncSetPixel = BitmapReadAccess::SetPixelForN1BitMsbPal; + pDstBuffer->mnBitCount = 1; + OSL_FAIL( "unknown write format" ); + break; + } + + // fill destination buffer + pDstBuffer->mnFormat = nDstBitmapFormat; + pDstBuffer->mnWidth = rTwoRect.mnDestWidth; + pDstBuffer->mnHeight = rTwoRect.mnDestHeight; + tools::Long nScanlineBase; + bool bFail = o3tl::checked_multiply<tools::Long>(pDstBuffer->mnBitCount, pDstBuffer->mnWidth, nScanlineBase); + if (bFail) + { + SAL_WARN("vcl.gdi", "checked multiply failed"); + pDstBuffer->mpBits = nullptr; + return std::nullopt; + } + pDstBuffer->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase); + if (pDstBuffer->mnScanlineSize < nScanlineBase/8) + { + SAL_WARN("vcl.gdi", "scanline calculation wraparound"); + pDstBuffer->mpBits = nullptr; + return std::nullopt; + } + try + { + pDstBuffer->mpBits = new sal_uInt8[ pDstBuffer->mnScanlineSize * pDstBuffer->mnHeight ]; + } + catch( const std::bad_alloc& ) + { + // memory exception, clean up + pDstBuffer->mpBits = nullptr; + return std::nullopt; + } + + // do we need a destination palette or color mask? + if( ( nDstScanlineFormat == ScanlineFormat::N1BitMsbPal ) || + ( nDstScanlineFormat == ScanlineFormat::N8BitPal ) ) + { + assert(pDstPal && "destination buffer requires palette"); + if (!pDstPal) + { + return std::nullopt; + } + pDstBuffer->maPalette = *pDstPal; + } + else if(nDstScanlineFormat == ScanlineFormat::N32BitTcMask ) + { + assert(pDstMask && "destination buffer requires color mask"); + if (!pDstMask) + { + return std::nullopt; + } + pDstBuffer->maColorMask = *pDstMask; + } + + // short circuit the most important conversions + bool bFastConvert = ImplFastBitmapConversion( *pDstBuffer, rSrcBuffer, rTwoRect ); + if( bFastConvert ) + return pDstBuffer; + + std::unique_ptr<Scanline[]> pSrcScan; + std::unique_ptr<Scanline[]> pDstScan; + std::unique_ptr<sal_Int32[]> pMapX; + std::unique_ptr<sal_Int32[]> pMapY; + + try + { + pSrcScan.reset(new Scanline[rSrcBuffer.mnHeight]); + pDstScan.reset(new Scanline[pDstBuffer->mnHeight]); + pMapX.reset(new sal_Int32[pDstBuffer->mnWidth]); + pMapY.reset(new sal_Int32[pDstBuffer->mnHeight]); + } + catch( const std::bad_alloc& ) + { + // memory exception, clean up + // remark: the buffer ptr causing the exception + // is still NULL here + return std::nullopt; + } + + // horizontal mapping table + if( (pDstBuffer->mnWidth != rTwoRect.mnSrcWidth) && (pDstBuffer->mnWidth != 0) ) + { + const double fFactorX = static_cast<double>(rTwoRect.mnSrcWidth) / pDstBuffer->mnWidth; + + for (tools::Long i = 0; i < pDstBuffer->mnWidth; ++i) + pMapX[ i ] = rTwoRect.mnSrcX + static_cast<int>( i * fFactorX ); + } + else + { + for (tools::Long i = 0, nTmp = rTwoRect.mnSrcX ; i < pDstBuffer->mnWidth; ++i) + pMapX[ i ] = nTmp++; + } + + // vertical mapping table + if( (pDstBuffer->mnHeight != rTwoRect.mnSrcHeight) && (pDstBuffer->mnHeight != 0) ) + { + const double fFactorY = static_cast<double>(rTwoRect.mnSrcHeight) / pDstBuffer->mnHeight; + + for (tools::Long i = 0; i < pDstBuffer->mnHeight; ++i) + pMapY[ i ] = rTwoRect.mnSrcY + static_cast<int>( i * fFactorY ); + } + else + { + for (tools::Long i = 0, nTmp = rTwoRect.mnSrcY; i < pDstBuffer->mnHeight; ++i) + pMapY[ i ] = nTmp++; + } + + // source scanline buffer + Scanline pTmpScan; + tools::Long nOffset; + if( rSrcBuffer.mnFormat & ScanlineFormat::TopDown ) + { + pTmpScan = rSrcBuffer.mpBits; + nOffset = rSrcBuffer.mnScanlineSize; + } + else + { + pTmpScan = rSrcBuffer.mpBits + ( rSrcBuffer.mnHeight - 1 ) * rSrcBuffer.mnScanlineSize; + nOffset = -rSrcBuffer.mnScanlineSize; + } + + for (tools::Long i = 0; i < rSrcBuffer.mnHeight; i++, pTmpScan += nOffset) + pSrcScan[ i ] = pTmpScan; + + // destination scanline buffer + if( pDstBuffer->mnFormat & ScanlineFormat::TopDown ) + { + pTmpScan = pDstBuffer->mpBits; + nOffset = pDstBuffer->mnScanlineSize; + } + else + { + pTmpScan = pDstBuffer->mpBits + ( pDstBuffer->mnHeight - 1 ) * pDstBuffer->mnScanlineSize; + nOffset = -pDstBuffer->mnScanlineSize; + } + + for (tools::Long i = 0; i < pDstBuffer->mnHeight; i++, pTmpScan += nOffset) + pDstScan[ i ] = pTmpScan; + + // do buffer scaling and conversion + if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount <= 8 ) + { + ImplPALToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel, + pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() ); + } + else if( rSrcBuffer.mnBitCount <= 8 && pDstBuffer->mnBitCount > 8 ) + { + ImplPALToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel, + pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() ); + } + else if( rSrcBuffer.mnBitCount > 8 && pDstBuffer->mnBitCount > 8 ) + { + ImplTCToTC( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel, + pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() ); + } + else + { + ImplTCToPAL( rSrcBuffer, *pDstBuffer, pFncGetPixel, pFncSetPixel, + pSrcScan.get(), pDstScan.get(), pMapX.get(), pMapY.get() ); + } + + return pDstBuffer; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/scrptrun.cxx b/vcl/source/gdi/scrptrun.cxx new file mode 100644 index 0000000000..19cb54772b --- /dev/null +++ b/vcl/source/gdi/scrptrun.cxx @@ -0,0 +1,259 @@ +/* + ******************************************************************************* + * + * Copyright (c) 1995-2013 International Business Machines Corporation and others + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN + * NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE + * LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY + * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall not be + * used in advertising or otherwise to promote the sale, use or other dealings in + * this Software without prior written authorization of the copyright holder. + * + ******************************************************************************* + * file name: scrptrun.cpp + * + * created on: 10/17/2001 + * created by: Eric R. Mader + */ +/** + * This file is largely copied from the ICU project, + * under folder source/extra/scrptrun/scrptrun.cpp + */ + +#include <sal/config.h> + +#include <rtl/character.hxx> +#include <unicode/uchar.h> +#include <unicode/utypes.h> +#include <unicode/uscript.h> + +#include <scrptrun.h> +#include <algorithm> + +namespace { + +struct PairIndices +{ + int8_t ma00[0xff]; + int8_t ma20[0x7f]; + int8_t ma30[0x7f]; + + PairIndices() + { + std::fill_n(ma00, 0xff, -1); + std::fill_n(ma20, 0x7f, -1); + std::fill_n(ma30, 0x7f, -1); + + // characters in the range 0x0000 - 0x007e (inclusive) + // ascii paired punctuation + ma00[0x28] = 0; + ma00[0x29] = 1; + ma00[0x3c] = 2; + ma00[0x3e] = 3; + ma00[0x5b] = 4; + ma00[0x5d] = 5; + ma00[0x7b] = 6; + ma00[0x7d] = 7; + // guillemets + ma00[0xab] = 8; + ma00[0xbb] = 9; + + // characters in the range 0x2000 - 0x207e (inclusive) + // general punctuation + ma20[0x18] = 10; + ma20[0x19] = 11; + ma20[0x1c] = 12; + ma20[0x1d] = 13; + ma20[0x39] = 14; + ma20[0x3a] = 15; + + // characters in the range 0x3000 - 0x307e (inclusive) + // chinese paired punctuation + ma30[0x08] = 16; + ma30[0x09] = 17; + ma30[0x0a] = 18; + ma30[0x0b] = 19; + ma30[0x0c] = 20; + ma30[0x0d] = 21; + ma30[0x0e] = 22; + ma30[0x0f] = 23; + ma30[0x10] = 24; + ma30[0x11] = 25; + ma30[0x14] = 26; + ma30[0x15] = 27; + ma30[0x16] = 28; + ma30[0x17] = 29; + ma30[0x18] = 30; + ma30[0x19] = 31; + ma30[0x1a] = 32; + ma30[0x1b] = 33; + } + + int32_t getPairIndex(UChar32 ch) const + { + if (ch < 0xff) + return ma00[ch]; + if (ch >= 0x2000 && ch < 0x207f) + return ma20[ch - 0x2000]; + if (ch >= 0x3000 && ch < 0x307f) + return ma30[ch - 0x3000]; + return -1; + } + +}; + +UScriptCode getScript(UChar32 ch, UErrorCode* status) +{ + // tdf#154549 + // Make combining marks inherit the script of their bases, regardless of + // their own script. + if (u_getIntPropertyValue(ch, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK) + return USCRIPT_INHERITED; + + UScriptCode script = uscript_getScript(ch, status); + if (U_FAILURE(*status)) + return script; + + // There are three Unicode script codes for Japanese text, but only one + // OpenType script tag, so we want to keep them in one run as splitting is + // pointless for the purpose of OpenType shaping. + if (script == USCRIPT_KATAKANA || script == USCRIPT_KATAKANA_OR_HIRAGANA) + return USCRIPT_HIRAGANA; + return script; +} + +} + +const PairIndices gPairIndices; + + +namespace vcl { + +const char ScriptRun::fgClassID=0; + +static bool sameScript(int32_t scriptOne, int32_t scriptTwo) +{ + return scriptOne <= USCRIPT_INHERITED || scriptTwo <= USCRIPT_INHERITED || scriptOne == scriptTwo; +} + +UBool ScriptRun::next() +{ + int32_t startSP = parenSP; // used to find the first new open character + UErrorCode error = U_ZERO_ERROR; + + // if we've fallen off the end of the text, we're done + if (scriptEnd >= charLimit) { + return false; + } + + scriptCode = USCRIPT_COMMON; + + for (scriptStart = scriptEnd; scriptEnd < charLimit; scriptEnd += 1) { + UChar high = charArray[scriptEnd]; + UChar32 ch = high; + + // if the character is a high surrogate and it's not the last one + // in the text, see if it's followed by a low surrogate + if (rtl::isHighSurrogate(high) && scriptEnd < charLimit - 1) + { + UChar low = charArray[scriptEnd + 1]; + + // if it is followed by a low surrogate, + // consume it and form the full character + if (rtl::isLowSurrogate(low)) { + ch = rtl::combineSurrogates(high, low); + scriptEnd += 1; + } + } + + UScriptCode sc = getScript(ch, &error); + int32_t pairIndex = gPairIndices.getPairIndex(ch); + + // Paired character handling: + + // if it's an open character, push it onto the stack. + // if it's a close character, find the matching open on the + // stack, and use that script code. Any non-matching open + // characters above it on the stack will be popped. + if (pairIndex >= 0) { + if ((pairIndex & 1) == 0) { + ++parenSP; + int32_t nVecSize = parenStack.size(); + if (parenSP == nVecSize) + parenStack.resize(nVecSize + 128); + parenStack[parenSP].pairIndex = pairIndex; + parenStack[parenSP].scriptCode = scriptCode; + } else if (parenSP >= 0) { + int32_t pi = pairIndex & ~1; + + while (parenSP >= 0 && parenStack[parenSP].pairIndex != pi) { + parenSP -= 1; + } + + if (parenSP < startSP) { + startSP = parenSP; + } + + if (parenSP >= 0) { + sc = parenStack[parenSP].scriptCode; + } + } + } + + if (sameScript(scriptCode, sc)) { + if (scriptCode <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) { + scriptCode = sc; + + // now that we have a final script code, fix any open + // characters we pushed before we knew the script code. + while (startSP < parenSP) { + parenStack[++startSP].scriptCode = scriptCode; + } + } + + // if this character is a close paired character, + // pop it from the stack + if (pairIndex >= 0 && (pairIndex & 1) != 0 && parenSP >= 0) { + parenSP -= 1; + /* decrement startSP only if it is >= 0, + decrementing it unnecessarily will lead to memory corruption + while processing the above while block. + e.g. startSP = -4 , parenSP = -1 + */ + if (startSP >= 0) { + startSP -= 1; + } + } + } else { + // if the run broke on a surrogate pair, + // end it before the high surrogate + if (ch >= 0x10000) { + scriptEnd -= 1; + } + + break; + } + } + + return true; +} + +} diff --git a/vcl/source/gdi/vectorgraphicdata.cxx b/vcl/source/gdi/vectorgraphicdata.cxx new file mode 100644 index 0000000000..9d94b171a4 --- /dev/null +++ b/vcl/source/gdi/vectorgraphicdata.cxx @@ -0,0 +1,357 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <comphelper/diagnose_ex.hxx> +#include <tools/stream.hxx> +#include <sal/log.hxx> +#include <utility> +#include <vcl/vectorgraphicdata.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/graphic/PdfTools.hpp> +#include <com/sun/star/graphic/SvgTools.hpp> +#include <com/sun/star/graphic/EmfTools.hpp> +#include <com/sun/star/graphic/Primitive2DTools.hpp> +#include <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp> +#include <com/sun/star/util/XAccounting.hpp> +#include <com/sun/star/util/XBinaryDataContainer.hpp> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <vcl/canvastools.hxx> +#include <comphelper/seqstream.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <rtl/crc.h> +#include <vcl/svapp.hxx> +#include <vcl/outdev.hxx> +#include <vcl/wmfexternal.hxx> +#include <vcl/pdfread.hxx> +#include <unotools/streamwrap.hxx> +#include <graphic/UnoBinaryDataContainer.hxx> + +using namespace ::com::sun::star; + +BitmapEx convertPrimitive2DSequenceToBitmapEx( + const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > >& rSequence, + const basegfx::B2DRange& rTargetRange, + const sal_uInt32 nMaximumQuadraticPixels, + const o3tl::Length eTargetUnit, + const std::optional<Size>& rTargetDPI) +{ + BitmapEx aRetval; + + if(!rSequence.empty()) + { + // create replacement graphic from maSequence + // create XPrimitive2DRenderer + try + { + uno::Reference< uno::XComponentContext > xContext(::comphelper::getProcessComponentContext()); + const uno::Reference< graphic::XPrimitive2DRenderer > xPrimitive2DRenderer = graphic::Primitive2DTools::create(xContext); + + uno::Sequence< beans::PropertyValue > aViewParameters = { + comphelper::makePropertyValue("RangeUnit", static_cast<sal_Int32>(eTargetUnit)), + }; + geometry::RealRectangle2D aRealRect; + + aRealRect.X1 = rTargetRange.getMinX(); + aRealRect.Y1 = rTargetRange.getMinY(); + aRealRect.X2 = rTargetRange.getMaxX(); + aRealRect.Y2 = rTargetRange.getMaxY(); + + // get system DPI + Size aDPI(Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); + if (rTargetDPI.has_value()) + { + aDPI = *rTargetDPI; + } + + const uno::Reference< rendering::XBitmap > xBitmap( + xPrimitive2DRenderer->rasterize( + comphelper::containerToSequence(rSequence), + aViewParameters, + aDPI.getWidth(), + aDPI.getHeight(), + aRealRect, + nMaximumQuadraticPixels)); + + if(xBitmap.is()) + { + const uno::Reference< rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap, uno::UNO_QUERY_THROW); + aRetval = vcl::unotools::bitmapExFromXBitmap(xIntBmp); + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("vcl", "Got no graphic::XPrimitive2DRenderer!"); + } + catch (const std::exception& e) + { + SAL_WARN("vcl", "Got no graphic::XPrimitive2DRenderer! : " << e.what()); + } + } + + return aRetval; +} + +static size_t estimateSize( + std::deque<uno::Reference<graphic::XPrimitive2D>> const& rSequence) +{ + size_t nRet(0); + for (auto& it : rSequence) + { + uno::Reference<util::XAccounting> const xAcc(it, uno::UNO_QUERY); + assert(xAcc.is()); // we expect only BasePrimitive2D from SVG parser + nRet += xAcc->estimateUsage(); + } + return nRet; +} + +bool VectorGraphicData::operator==(const VectorGraphicData& rCandidate) const +{ + if (getType() == rCandidate.getType()) + { + if (maDataContainer.getSize() == rCandidate.maDataContainer.getSize()) + { + if (0 == memcmp( + maDataContainer.getData(), + rCandidate.maDataContainer.getData(), + maDataContainer.getSize())) + { + return true; + } + } + } + + return false; +} + +void VectorGraphicData::ensurePdfReplacement() +{ + assert(getType() == VectorGraphicDataType::Pdf); + + if (!maReplacement.IsEmpty()) + return; // nothing to do + + // use PDFium directly + std::vector<BitmapEx> aBitmaps; + sal_Int32 nUsePageIndex = 0; + if (mnPageIndex >= 0) + nUsePageIndex = mnPageIndex; + vcl::RenderPDFBitmaps(maDataContainer.getData(), + maDataContainer.getSize(), aBitmaps, nUsePageIndex, 1, + &maSizeHint); + if (!aBitmaps.empty()) + maReplacement = aBitmaps[0]; +} + +void VectorGraphicData::ensureReplacement() +{ + if (!maReplacement.IsEmpty()) + return; // nothing to do + + // shortcut for PDF - PDFium can generate the replacement bitmap for us + // directly + if (getType() == VectorGraphicDataType::Pdf) + { + ensurePdfReplacement(); + return; + } + + ensureSequenceAndRange(); + + if (!maSequence.empty()) + { + maReplacement = convertPrimitive2DSequenceToBitmapEx(maSequence, getRange()); + } +} + +void VectorGraphicData::ensureSequenceAndRange() +{ + if (mbSequenceCreated || maDataContainer.isEmpty()) + return; + + // import SVG to maSequence, also set maRange + maRange.reset(); + + // create Vector Graphic Data interpreter + uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext()); + + switch (getType()) + { + case VectorGraphicDataType::Svg: + { + const uno::Reference<io::XInputStream> xInputStream = maDataContainer.getAsXInputStream(); + + const uno::Reference< graphic::XSvgParser > xSvgParser = graphic::SvgTools::create(xContext); + + if (xInputStream.is()) + maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xSvgParser->getDecomposition(xInputStream, OUString())); + + break; + } + case VectorGraphicDataType::Emf: + case VectorGraphicDataType::Wmf: + { + const uno::Reference< graphic::XEmfParser > xEmfParser = graphic::EmfTools::create(xContext); + + const uno::Reference<io::XInputStream> xInputStream = maDataContainer.getAsXInputStream(); + + if (xInputStream.is()) + { + uno::Sequence< ::beans::PropertyValue > aPropertySequence; + + // Pass the size hint of the graphic to the EMF parser. + geometry::RealPoint2D aSizeHint; + aSizeHint.X = maSizeHint.getX(); + aSizeHint.Y = maSizeHint.getY(); + xEmfParser->setSizeHint(aSizeHint); + + if (!mbEnableEMFPlus) + { + aPropertySequence = { comphelper::makePropertyValue("EMFPlusEnable", uno::Any(false)) }; + } + + maSequence = comphelper::sequenceToContainer<std::deque<css::uno::Reference< css::graphic::XPrimitive2D >>>(xEmfParser->getDecomposition(xInputStream, OUString(), aPropertySequence)); + } + + break; + } + case VectorGraphicDataType::Pdf: + { + const uno::Reference<graphic::XPdfDecomposer> xPdfDecomposer = graphic::PdfTools::create(xContext); + uno::Sequence<beans::PropertyValue> aDecompositionParameters = comphelper::InitPropertySequence({ + {"PageIndex", uno::Any(sal_Int32(mnPageIndex))}, + }); + + rtl::Reference<UnoBinaryDataContainer> xDataContainer = new UnoBinaryDataContainer(getBinaryDataContainer()); + + auto xPrimitive2D = xPdfDecomposer->getDecomposition(xDataContainer, aDecompositionParameters); + maSequence = comphelper::sequenceToContainer<std::deque<uno::Reference<graphic::XPrimitive2D>>>(xPrimitive2D); + + break; + } + } + + if(!maSequence.empty()) + { + const sal_Int32 nCount(maSequence.size()); + geometry::RealRectangle2D aRealRect; + uno::Sequence< beans::PropertyValue > aViewParameters; + + for(sal_Int32 a(0); a < nCount; a++) + { + // get reference + const css::uno::Reference< css::graphic::XPrimitive2D > xReference(maSequence[a]); + + if(xReference.is()) + { + aRealRect = xReference->getRange(aViewParameters); + + maRange.expand( + basegfx::B2DRange( + aRealRect.X1, + aRealRect.Y1, + aRealRect.X2, + aRealRect.Y2)); + } + } + } + mNestedBitmapSize = estimateSize(maSequence); + mbSequenceCreated = true; +} + +std::pair<VectorGraphicData::State, size_t> VectorGraphicData::getSizeBytes() const +{ + if (!maSequence.empty() && !maDataContainer.isEmpty()) + { + return std::make_pair(State::PARSED, maDataContainer.getSize() + mNestedBitmapSize); + } + else + { + return std::make_pair(State::UNPARSED, maDataContainer.getSize()); + } +} + +VectorGraphicData::VectorGraphicData( + BinaryDataContainer aDataContainer, + VectorGraphicDataType eVectorDataType, + sal_Int32 nPageIndex) +: maDataContainer(std::move(aDataContainer)), + mbSequenceCreated(false), + mNestedBitmapSize(0), + meType(eVectorDataType), + mnPageIndex(nPageIndex) +{ +} + +VectorGraphicData::VectorGraphicData( + const OUString& rPath, + VectorGraphicDataType eVectorDataType) +: mbSequenceCreated(false), + mNestedBitmapSize(0), + meType(eVectorDataType), + mnPageIndex(-1) +{ + SvFileStream rIStm(rPath, StreamMode::STD_READ); + if(rIStm.GetError()) + return; + const sal_uInt32 nStmLen(rIStm.remainingSize()); + if (nStmLen) + { + BinaryDataContainer aData(rIStm, nStmLen); + + if (!rIStm.GetError()) + { + maDataContainer = aData; + } + } +} + +VectorGraphicData::~VectorGraphicData() +{ +} + +const basegfx::B2DRange& VectorGraphicData::getRange() const +{ + const_cast< VectorGraphicData* >(this)->ensureSequenceAndRange(); + + return maRange; +} + +const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > >& VectorGraphicData::getPrimitive2DSequence() const +{ + const_cast< VectorGraphicData* >(this)->ensureSequenceAndRange(); + + return maSequence; +} + +const BitmapEx& VectorGraphicData::getReplacement() const +{ + const_cast< VectorGraphicData* >(this)->ensureReplacement(); + + return maReplacement; +} + +BitmapChecksum VectorGraphicData::GetChecksum() const +{ + return rtl_crc32(0, maDataContainer.getData(), maDataContainer.getSize()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/virdev.cxx b/vcl/source/gdi/virdev.cxx new file mode 100644 index 0000000000..b86c4ae496 --- /dev/null +++ b/vcl/source/gdi/virdev.cxx @@ -0,0 +1,521 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <comphelper/lok.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> + +#include <vcl/pdfextoutdevdata.hxx> +#include <vcl/virdev.hxx> + +#include <ImplOutDevData.hxx> +#include <font/PhysicalFontCollection.hxx> +#include <font/PhysicalFontFaceCollection.hxx> +#include <impfontcache.hxx> +#include <salinst.hxx> +#include <salgdi.hxx> +#include <salvd.hxx> +#include <svdata.hxx> + +using namespace ::com::sun::star::uno; + +bool VirtualDevice::CanEnableNativeWidget() const +{ + const vcl::ExtOutDevData* pOutDevData(GetExtOutDevData()); + const vcl::PDFExtOutDevData* pPDFData(dynamic_cast<const vcl::PDFExtOutDevData*>(pOutDevData)); + return pPDFData == nullptr; +} + +bool VirtualDevice::AcquireGraphics() const +{ + DBG_TESTSOLARMUTEX(); + + if ( mpGraphics ) + return true; + + mbInitLineColor = true; + mbInitFillColor = true; + mbInitFont = true; + mbInitTextColor = true; + mbInitClipRegion = true; + + ImplSVData* pSVData = ImplGetSVData(); + + if ( mpVirDev ) + { + mpGraphics = mpVirDev->AcquireGraphics(); + // if needed retry after releasing least recently used virtual device graphics + while ( !mpGraphics ) + { + if ( !pSVData->maGDIData.mpLastVirGraphics ) + break; + pSVData->maGDIData.mpLastVirGraphics->ReleaseGraphics(); + mpGraphics = mpVirDev->AcquireGraphics(); + } + // update global LRU list of virtual device graphics + if ( mpGraphics ) + { + mpNextGraphics = pSVData->maGDIData.mpFirstVirGraphics; + pSVData->maGDIData.mpFirstVirGraphics = const_cast<VirtualDevice*>(this); + if ( mpNextGraphics ) + mpNextGraphics->mpPrevGraphics = const_cast<VirtualDevice*>(this); + if ( !pSVData->maGDIData.mpLastVirGraphics ) + pSVData->maGDIData.mpLastVirGraphics = const_cast<VirtualDevice*>(this); + } + } + + if ( mpGraphics ) + { + mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp ); + mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable)); + } + + return mpGraphics != nullptr; +} + +void VirtualDevice::ReleaseGraphics( bool bRelease ) +{ + DBG_TESTSOLARMUTEX(); + + if ( !mpGraphics ) + return; + + // release the fonts of the physically released graphics device + if ( bRelease ) + ImplReleaseFonts(); + + ImplSVData* pSVData = ImplGetSVData(); + + VirtualDevice* pVirDev = this; + + if ( bRelease ) + pVirDev->mpVirDev->ReleaseGraphics( mpGraphics ); + // remove from global LRU list of virtual device graphics + if ( mpPrevGraphics ) + mpPrevGraphics->mpNextGraphics = mpNextGraphics; + else + pSVData->maGDIData.mpFirstVirGraphics = mpNextGraphics; + if ( mpNextGraphics ) + mpNextGraphics->mpPrevGraphics = mpPrevGraphics; + else + pSVData->maGDIData.mpLastVirGraphics = mpPrevGraphics; + + mpGraphics = nullptr; + mpPrevGraphics = nullptr; + mpNextGraphics = nullptr; +} + +void VirtualDevice::ImplInitVirDev( const OutputDevice* pOutDev, + tools::Long nDX, tools::Long nDY, const SystemGraphicsData *pData ) +{ + SAL_INFO( "vcl.virdev", "ImplInitVirDev(" << nDX << "," << nDY << ")" ); + + meRefDevMode = RefDevMode::NONE; + mbForceZeroExtleadBug = false; + mnBitCount = 0; + mbScreenComp = false; + + + bool bErase = nDX > 0 && nDY > 0; + + if ( nDX < 1 ) + nDX = 1; + + if ( nDY < 1 ) + nDY = 1; + + ImplSVData* pSVData = ImplGetSVData(); + + if ( !pOutDev ) + pOutDev = ImplGetDefaultWindow()->GetOutDev(); + if( !pOutDev ) + return; + + SalGraphics* pGraphics; + if ( !pOutDev->mpGraphics ) + (void)pOutDev->AcquireGraphics(); + pGraphics = pOutDev->mpGraphics; + if ( pGraphics ) + mpVirDev = pSVData->mpDefInst->CreateVirtualDevice(*pGraphics, nDX, nDY, meFormatAndAlpha, pData); + else + mpVirDev = nullptr; + if ( !mpVirDev ) + { + // do not abort but throw an exception, may be the current thread terminates anyway (plugin-scenario) + throw css::uno::RuntimeException( + "Could not create system bitmap!", + css::uno::Reference< css::uno::XInterface >() ); + } + + mnBitCount = pOutDev->GetBitCount(); + mnOutWidth = nDX; + mnOutHeight = nDY; + + mbScreenComp = pOutDev->IsScreenComp(); + + mbDevOutput = true; + mxFontCollection = pSVData->maGDIData.mxScreenFontList; + mxFontCache = pSVData->maGDIData.mxScreenFontCache; + mnDPIX = pOutDev->mnDPIX; + mnDPIY = pOutDev->mnDPIY; + mnDPIScalePercentage = pOutDev->mnDPIScalePercentage; + maFont = pOutDev->maFont; + + if( maTextColor != pOutDev->maTextColor ) + { + maTextColor = pOutDev->maTextColor; + mbInitTextColor = true; + } + + // virtual devices have white background by default + SetBackground( Wallpaper( COL_WHITE ) ); + + // #i59283# don't erase user-provided surface + if( !pData && bErase) + Erase(); + + // register VirDev in the list + mpNext = pSVData->maGDIData.mpFirstVirDev; + mpPrev = nullptr; + if ( mpNext ) + mpNext->mpPrev = this; + pSVData->maGDIData.mpFirstVirDev = this; +} + +VirtualDevice::VirtualDevice(const OutputDevice* pCompDev, DeviceFormat eFormatAndAlpha, + OutDevType eOutDevType) + : OutputDevice(eOutDevType) + , meFormatAndAlpha(eFormatAndAlpha) +{ + SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormatAndAlpha) + << ", " << static_cast<int>(eOutDevType) << " )" ); + + ImplInitVirDev(pCompDev ? pCompDev : Application::GetDefaultDevice(), 0, 0); +} + +VirtualDevice::VirtualDevice(const SystemGraphicsData& rData, const Size &rSize, + DeviceFormat eFormat) + : OutputDevice(OUTDEV_VIRDEV) + , meFormatAndAlpha(eFormat) +{ + SAL_INFO( "vcl.virdev", "VirtualDevice::VirtualDevice( " << static_cast<int>(eFormat) << " )" ); + + ImplInitVirDev(Application::GetDefaultDevice(), rSize.Width(), rSize.Height(), &rData); +} + +VirtualDevice::~VirtualDevice() +{ + SAL_INFO( "vcl.virdev", "VirtualDevice::~VirtualDevice()" ); + disposeOnce(); +} + +void VirtualDevice::dispose() +{ + SAL_INFO( "vcl.virdev", "VirtualDevice::dispose()" ); + + ImplSVData* pSVData = ImplGetSVData(); + + ReleaseGraphics(); + + mpVirDev.reset(); + + // remove this VirtualDevice from the double-linked global list + if( mpPrev ) + mpPrev->mpNext = mpNext; + else + pSVData->maGDIData.mpFirstVirDev = mpNext; + + if( mpNext ) + mpNext->mpPrev = mpPrev; + + OutputDevice::dispose(); +} + +bool VirtualDevice::InnerImplSetOutputSizePixel( const Size& rNewSize, bool bErase, + sal_uInt8 *const pBuffer) +{ + SAL_INFO( "vcl.virdev", + "VirtualDevice::InnerImplSetOutputSizePixel( " << rNewSize.Width() << ", " + << rNewSize.Height() << ", " << int(bErase) << " )" ); + + if ( !mpVirDev ) + return false; + else if ( rNewSize == GetOutputSizePixel() ) + { + if ( bErase ) + Erase(); + SAL_INFO( "vcl.virdev", "Trying to re-use a VirtualDevice but this time using a pre-allocated buffer"); + return true; + } + + bool bRet; + tools::Long nNewWidth = rNewSize.Width(), nNewHeight = rNewSize.Height(); + + if ( nNewWidth < 1 ) + nNewWidth = 1; + + if ( nNewHeight < 1 ) + nNewHeight = 1; + + if ( bErase ) + { + if ( pBuffer ) + bRet = mpVirDev->SetSizeUsingBuffer( nNewWidth, nNewHeight, pBuffer ); + else + bRet = mpVirDev->SetSize( nNewWidth, nNewHeight ); + + if ( bRet ) + { + mnOutWidth = rNewSize.Width(); + mnOutHeight = rNewSize.Height(); + Erase(); + } + } + else + { + std::unique_ptr<SalVirtualDevice> pNewVirDev; + ImplSVData* pSVData = ImplGetSVData(); + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return false; + + assert(mpGraphics); + + pNewVirDev = pSVData->mpDefInst->CreateVirtualDevice(*mpGraphics, nNewWidth, nNewHeight, meFormatAndAlpha); + if ( pNewVirDev ) + { + SalGraphics* pGraphics = pNewVirDev->AcquireGraphics(); + if ( pGraphics ) + { + tools::Long nWidth; + tools::Long nHeight; + if ( mnOutWidth < nNewWidth ) + nWidth = mnOutWidth; + else + nWidth = nNewWidth; + if ( mnOutHeight < nNewHeight ) + nHeight = mnOutHeight; + else + nHeight = nNewHeight; + SalTwoRect aPosAry(0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight); + pGraphics->CopyBits( aPosAry, *mpGraphics, *this, *this ); + pNewVirDev->ReleaseGraphics( pGraphics ); + ReleaseGraphics(); + mpVirDev = std::move(pNewVirDev); + mnOutWidth = rNewSize.Width(); + mnOutHeight = rNewSize.Height(); + bRet = true; + } + else + { + bRet = false; + } + } + else + bRet = false; + } + + return bRet; +} + +// #i32109#: Fill opaque areas correctly (without relying on +// fill/linecolor state) +void VirtualDevice::ImplFillOpaqueRectangle( const tools::Rectangle& rRect ) +{ + // Set line and fill color to opaque, + // fill rect with that (linecolor, too, because of + // those pesky missing pixel problems) + Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); + SetLineColor( COL_ALPHA_OPAQUE ); + SetFillColor( COL_ALPHA_OPAQUE ); + DrawRect( rRect ); + Pop(); +} + +bool VirtualDevice::ImplSetOutputSizePixel( const Size& rNewSize, bool bErase, + sal_uInt8 *const pBuffer, bool bAlphaMaskTransparent ) +{ + if( InnerImplSetOutputSizePixel(rNewSize, bErase, pBuffer) ) + { + if (meFormatAndAlpha != DeviceFormat::WITHOUT_ALPHA) + { + // #110958# Setup alpha bitmap + if(mpAlphaVDev && mpAlphaVDev->GetOutputSizePixel() != rNewSize) + { + mpAlphaVDev.disposeAndClear(); + } + + if( !mpAlphaVDev ) + { + mpAlphaVDev = VclPtr<VirtualDevice>::Create(*this, meFormatAndAlpha); + mpAlphaVDev->InnerImplSetOutputSizePixel(rNewSize, bErase, nullptr); + mpAlphaVDev->SetBackground( Wallpaper(bAlphaMaskTransparent ? COL_ALPHA_TRANSPARENT : COL_ALPHA_OPAQUE) ); + mpAlphaVDev->Erase(); + } + + // TODO: copy full outdev state to new one, here. Also needed in outdev2.cxx:DrawOutDev + if( GetLineColor() != COL_TRANSPARENT ) + mpAlphaVDev->SetLineColor( COL_ALPHA_OPAQUE ); + + if( GetFillColor() != COL_TRANSPARENT ) + mpAlphaVDev->SetFillColor( COL_ALPHA_OPAQUE ); + + mpAlphaVDev->SetMapMode( GetMapMode() ); + + mpAlphaVDev->SetAntialiasing( GetAntialiasing() ); + } + + return true; + } + + return false; +} + +void VirtualDevice::EnableRTL( bool bEnable ) +{ + // virdevs default to not mirroring, they will only be set to mirroring + // under rare circumstances in the UI, eg the valueset control + // because each virdev has its own SalGraphics we can safely switch the SalGraphics here + // ...hopefully + if( AcquireGraphics() ) + mpGraphics->SetLayout( bEnable ? SalLayoutFlags::BiDiRtl : SalLayoutFlags::NONE ); + + OutputDevice::EnableRTL(bEnable); +} + +bool VirtualDevice::SetOutputSizePixel( const Size& rNewSize, bool bErase, bool bAlphaMaskTransparent ) +{ + return ImplSetOutputSizePixel(rNewSize, bErase, nullptr, bAlphaMaskTransparent); +} + +bool VirtualDevice::SetOutputSizePixelScaleOffsetAndLOKBuffer( + const Size& rNewSize, const Fraction& rScale, const Point& rNewOffset, + sal_uInt8 *const pBuffer) +{ + // If this is ever needed for something else than LOK, changes will + // be needed in SvpSalVirtualDevice::CreateSurface() . + assert(comphelper::LibreOfficeKit::isActive()); + assert(pBuffer); + MapMode mm = GetMapMode(); + mm.SetOrigin( rNewOffset ); + mm.SetScaleX( rScale ); + mm.SetScaleY( rScale ); + SetMapMode( mm ); + return ImplSetOutputSizePixel(rNewSize, true, pBuffer); +} + +void VirtualDevice::SetReferenceDevice( RefDevMode i_eRefDevMode ) +{ + sal_Int32 nDPIX = 600, nDPIY = 600; + switch( i_eRefDevMode ) + { + case RefDevMode::NONE: + default: + SAL_WARN( "vcl.virdev", "VDev::SetRefDev illegal argument!" ); + break; + case RefDevMode::Dpi600: + nDPIX = nDPIY = 600; + break; + case RefDevMode::MSO1: + nDPIX = nDPIY = 6*1440; + break; + case RefDevMode::PDF1: + nDPIX = nDPIY = 720; + break; + } + ImplSetReferenceDevice( i_eRefDevMode, nDPIX, nDPIY ); +} + +void VirtualDevice::SetReferenceDevice( sal_Int32 i_nDPIX, sal_Int32 i_nDPIY ) +{ + ImplSetReferenceDevice( RefDevMode::Custom, i_nDPIX, i_nDPIY ); +} + +bool VirtualDevice::IsVirtual() const +{ + return true; +} + +void VirtualDevice::ImplSetReferenceDevice( RefDevMode i_eRefDevMode, sal_Int32 i_nDPIX, sal_Int32 i_nDPIY ) +{ + mnDPIX = i_nDPIX; + mnDPIY = i_nDPIY; + mnDPIScalePercentage = 100; + + EnableOutput( false ); // prevent output on reference device + mbScreenComp = false; + + // invalidate currently selected fonts + mbInitFont = true; + mbNewFont = true; + + // avoid adjusting font lists when already in refdev mode + RefDevMode nOldRefDevMode = meRefDevMode; + meRefDevMode = i_eRefDevMode; + if( nOldRefDevMode != RefDevMode::NONE ) + return; + + // the reference device should have only scalable fonts + // => clean up the original font lists before getting new ones + mpFontInstance.clear(); + mpFontFaceCollection.reset(); + + // preserve global font lists + ImplSVData* pSVData = ImplGetSVData(); + mxFontCollection.reset(); + mxFontCache.reset(); + + // get font list with scalable fonts only + (void)AcquireGraphics(); + mxFontCollection = pSVData->maGDIData.mxScreenFontList->Clone(); + + // prepare to use new font lists + mxFontCache = std::make_shared<ImplFontCache>(); +} + +sal_uInt16 VirtualDevice::GetBitCount() const +{ + return mnBitCount; +} + +bool VirtualDevice::UsePolyPolygonForComplexGradient() +{ + return true; +} + +void VirtualDevice::Compat_ZeroExtleadBug() +{ + mbForceZeroExtleadBug = true; +} + +tools::Long VirtualDevice::GetFontExtLeading() const +{ +#ifdef UNX + // backwards compatible line metrics after fixing #i60945# + if ( mbForceZeroExtleadBug ) + return 0; +#endif + + return mpFontInstance->mxFontMetric->GetExternalLeading(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/gdi/wall.cxx b/vcl/source/gdi/wall.cxx new file mode 100644 index 0000000000..2c7d571e90 --- /dev/null +++ b/vcl/source/gdi/wall.cxx @@ -0,0 +1,271 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/gradient.hxx> +#include <vcl/wall.hxx> +#include <vcl/svapp.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/settings.hxx> +#include <vcl/TypeSerializer.hxx> + +SvStream& ReadWallpaper( SvStream& rIStm, Wallpaper& rImplWallpaper ) +{ + VersionCompatRead aCompat(rIStm); + + rImplWallpaper.maRect.SetEmpty(); + rImplWallpaper.mpGradient.reset(); + rImplWallpaper.maBitmap.SetEmpty(); + + // version 1 + TypeSerializer aSerializer(rIStm); + aSerializer.readColor(rImplWallpaper.maColor); + sal_uInt16 nTmp16(0); + rIStm.ReadUInt16(nTmp16); + rImplWallpaper.meStyle = static_cast<WallpaperStyle>(nTmp16); + + // version 2 + if( aCompat.GetVersion() >= 2 ) + { + bool bRect(false), bGrad(false), bBmp(false), bDummy; + + rIStm.ReadCharAsBool( bRect ).ReadCharAsBool( bGrad ).ReadCharAsBool( bBmp ).ReadCharAsBool( bDummy ).ReadCharAsBool( bDummy ).ReadCharAsBool( bDummy ); + + if( bRect ) + { + rImplWallpaper.maRect = tools::Rectangle(); + aSerializer.readRectangle(rImplWallpaper.maRect); + } + + if( bGrad ) + { + rImplWallpaper.mpGradient.emplace(); + aSerializer.readGradient(*rImplWallpaper.mpGradient); + } + + if( bBmp ) + { + rImplWallpaper.maBitmap.SetEmpty(); + ReadDIBBitmapEx(rImplWallpaper.maBitmap, rIStm); + } + + // version 3 (new color format) + if( aCompat.GetVersion() >= 3 ) + { + sal_uInt32 nTmp; + rIStm.ReadUInt32(nTmp); + rImplWallpaper.maColor = ::Color(ColorTransparency, nTmp); + } + } + + return rIStm; +} + +SvStream& WriteWallpaper( SvStream& rOStm, const Wallpaper& rImplWallpaper ) +{ + VersionCompatWrite aCompat(rOStm, 3); + bool bRect = !rImplWallpaper.maRect.IsEmpty(); + bool bGrad = bool(rImplWallpaper.mpGradient); + bool bBmp = !rImplWallpaper.maBitmap.IsEmpty(); + bool bDummy = false; + + // version 1 + TypeSerializer aSerializer(rOStm); + aSerializer.writeColor(rImplWallpaper.maColor); + + rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplWallpaper.meStyle) ); + + // version 2 + rOStm.WriteBool( bRect ).WriteBool( bGrad ).WriteBool( bBmp ).WriteBool( bDummy ).WriteBool( bDummy ).WriteBool( bDummy ); + + if( bRect ) + { + aSerializer.writeRectangle(rImplWallpaper.maRect); + } + + if (bGrad) + { + aSerializer.writeGradient(*rImplWallpaper.mpGradient); + } + + if( bBmp ) + WriteDIBBitmapEx(rImplWallpaper.maBitmap, rOStm); + + // version 3 (new color format) + rOStm.WriteUInt32(static_cast<sal_uInt32>(rImplWallpaper.maColor)); + + return rOStm; +} + +Wallpaper::Wallpaper() : + maColor( COL_TRANSPARENT ), meStyle( WallpaperStyle::NONE ) +{ +} + +Wallpaper::Wallpaper( const Wallpaper& ) = default; + +Wallpaper::Wallpaper( Wallpaper&& ) = default; + +Wallpaper::Wallpaper( const Color& rColor ) +{ + maColor = rColor; + meStyle = WallpaperStyle::Tile; +} + +Wallpaper::Wallpaper( const BitmapEx& rBmpEx ) +{ + maBitmap = rBmpEx; + meStyle = WallpaperStyle::Tile; +} + +Wallpaper::~Wallpaper() = default; + +void Wallpaper::ImplSetCachedBitmap( const BitmapEx& rBmp ) const +{ + maCache = rBmp; +} + +const BitmapEx* Wallpaper::ImplGetCachedBitmap() const +{ + return maCache.IsEmpty() ? nullptr : &maCache; +} + +void Wallpaper::ImplReleaseCachedBitmap() const +{ + maCache.SetEmpty(); +} + +void Wallpaper::SetColor( const Color& rColor ) +{ + maCache.SetEmpty(); + maColor = rColor; + + if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle ) + meStyle = WallpaperStyle::Tile; +} + +void Wallpaper::SetStyle( WallpaperStyle eStyle ) +{ + if( eStyle == WallpaperStyle::ApplicationGradient ) + // set a dummy gradient, the correct gradient + // will be created dynamically in GetGradient() + SetGradient( ImplGetApplicationGradient() ); + + meStyle = eStyle; +} + +void Wallpaper::SetBitmap( const BitmapEx& rBitmap ) +{ + maCache.SetEmpty(); + maBitmap = rBitmap; + + if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle) + meStyle = WallpaperStyle::Tile; +} + +const BitmapEx & Wallpaper::GetBitmap() const +{ + return maBitmap; +} + +bool Wallpaper::IsBitmap() const +{ + return !maBitmap.IsEmpty(); +} + +void Wallpaper::SetGradient( const Gradient& rGradient ) +{ + maCache.SetEmpty(); + mpGradient = rGradient; + + if( WallpaperStyle::NONE == meStyle || WallpaperStyle::ApplicationGradient == meStyle ) + meStyle = WallpaperStyle::Tile; +} + +Gradient Wallpaper::GetGradient() const +{ + if( WallpaperStyle::ApplicationGradient == meStyle ) + return ImplGetApplicationGradient(); + else if ( mpGradient ) + return *mpGradient; + else + return Gradient(); +} + +bool Wallpaper::IsGradient() const +{ + return bool(mpGradient); +} + +Gradient Wallpaper::ImplGetApplicationGradient() +{ + Gradient g; + g.SetAngle( 900_deg10 ); + g.SetStyle( css::awt::GradientStyle_LINEAR ); + g.SetStartColor( Application::GetSettings().GetStyleSettings().GetFaceColor() ); + // no 'extreme' gradient when high contrast + if( Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + g.SetEndColor( Application::GetSettings().GetStyleSettings().GetFaceColor() ); + else + g.SetEndColor( Application::GetSettings().GetStyleSettings().GetFaceGradientColor() ); + return g; +} + +bool Wallpaper::IsRect() const +{ + return !maRect.IsEmpty(); +} + +bool Wallpaper::IsFixed() const +{ + if ( meStyle == WallpaperStyle::NONE ) + return false; + else + return (maBitmap.IsEmpty() && !mpGradient); +} + +bool Wallpaper::IsScrollable() const +{ + if ( meStyle == WallpaperStyle::NONE ) + return false; + else if ( maBitmap.IsEmpty() && !mpGradient ) + return true; + else if ( !maBitmap.IsEmpty() ) + return (meStyle == WallpaperStyle::Tile); + else + return false; +} + +Wallpaper& Wallpaper::operator=( const Wallpaper& ) = default; + +Wallpaper& Wallpaper::operator=( Wallpaper&& ) = default; + +bool Wallpaper::operator==( const Wallpaper& rOther ) const +{ + return meStyle == rOther.meStyle && + maColor == rOther.maColor && + maRect == rOther.maRect && + maBitmap == rOther.maBitmap && + mpGradient == rOther.mpGradient; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |