summaryrefslogtreecommitdiffstats
path: root/vcl/ios
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/ios')
-rw-r--r--vcl/ios/DataFlavorMapping.cxx576
-rw-r--r--vcl/ios/DataFlavorMapping.hxx124
-rw-r--r--vcl/ios/HtmlFmtFlt.cxx172
-rw-r--r--vcl/ios/HtmlFmtFlt.hxx38
-rw-r--r--vcl/ios/clipboard.cxx182
-rw-r--r--vcl/ios/clipboard.hxx107
-rw-r--r--vcl/ios/dummies.cxx117
-rw-r--r--vcl/ios/iOSTransferable.cxx184
-rw-r--r--vcl/ios/iOSTransferable.hxx69
-rw-r--r--vcl/ios/iosinst.cxx165
-rw-r--r--vcl/ios/salios.cxx585
11 files changed, 2319 insertions, 0 deletions
diff --git a/vcl/ios/DataFlavorMapping.cxx b/vcl/ios/DataFlavorMapping.cxx
new file mode 100644
index 0000000000..14bf0f6f43
--- /dev/null
+++ b/vcl/ios/DataFlavorMapping.cxx
@@ -0,0 +1,576 @@
+/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 "DataFlavorMapping.hxx"
+#include "HtmlFmtFlt.hxx"
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <osl/endian.h>
+
+#include <cassert>
+#include <cstring>
+
+#include <premac.h>
+#include <UIKit/UIKit.h>
+#include <MobileCoreServices/MobileCoreServices.h>
+#include <postmac.h>
+
+using namespace css::datatransfer;
+using namespace css::uno;
+using namespace css::lang;
+using namespace cppu;
+
+namespace
+{
+/* Determine whether or not a DataFlavor is valid.
+ */
+bool isValidFlavor(const DataFlavor& aFlavor)
+{
+ size_t len = aFlavor.MimeType.getLength();
+ Type dtype = aFlavor.DataType;
+ return ((len > 0)
+ && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get())
+ || (dtype == cppu::UnoType<OUString>::get())));
+}
+
+OUString NSStringToOUString(const NSString* cfString)
+{
+ assert(cfString && "Invalid parameter");
+
+ const char* utf8Str = [cfString UTF8String];
+ unsigned int len = rtl_str_getLength(utf8Str);
+
+ return OUString(utf8Str, len, RTL_TEXTENCODING_UTF8);
+}
+
+NSString* OUStringToNSString(const OUString& ustring)
+{
+ OString utf8Str = OUStringToOString(ustring, RTL_TEXTENCODING_UTF8);
+ return [NSString stringWithCString:utf8Str.getStr() encoding:NSUTF8StringEncoding];
+}
+
+NSString* PBTYPE_UTF8PLAINTEXT = (__bridge NSString*)kUTTypeUTF8PlainText;
+NSString* PBTYPE_RTF = (__bridge NSString*)kUTTypeRTF;
+NSString* PBTYPE_PNG = (__bridge NSString*)kUTTypePNG;
+NSString* PBTYPE_JPEG = (__bridge NSString*)kUTTypeJPEG;
+NSString* PBTYPE_HTML = (__bridge NSString*)kUTTypeHTML;
+NSString* PBTYPE_PDF = (__bridge NSString*)kUTTypePDF;
+
+const char* FLAVOR_SESX
+ = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+const char* FLAVOR_SLSDX = "application/"
+ "x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link "
+ "Source Descriptor (XML)\"";
+const char* FLAVOR_LSX
+ = "application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\"";
+const char* FLAVOR_EOX
+ = "application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\"";
+const char* FLAVOR_SVXB
+ = "application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\"";
+const char* FLAVOR_GDIMF
+ = "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"";
+const char* FLAVOR_SODX = "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star "
+ "Object Descriptor (XML)\"";
+struct FlavorMap
+{
+ NSString* SystemFlavor;
+ const char* OOoFlavor;
+ const char* HumanPresentableName;
+ bool DataTypeOUString; // sequence<byte> otherwise
+};
+
+// The SystemFlavor member is nil for the cases where there is no predefined pasteboard type UTI and
+// we use the internal MIME type (media type) also on the pasteboard. That is OK, there dos not seem
+// to be a requirement that the types are well-formed UTIs even on iOS. For an introduction to UTIs,
+// see for instance
+// https://alastairs-place.net/blog/2012/06/06/utis-are-better-than-you-think-and-heres-why/
+//
+// In those cases the MIME type might actually have parameters appended, separated by semicolons.
+// At least the FLAVOR_SODX one must have at least a typename="%PRODUCTNAME %PRODUCTVERSION
+// Spreadsheet" parameter (with macros expanded and translated) for LO to recognise it. See
+// lcl_TestFormat() in sc/source/ui/view/cellsh.cxx.
+
+static const FlavorMap flavorMap[]
+ = { { PBTYPE_UTF8PLAINTEXT, "text/plain;charset=utf-16", "Unicode Text (UTF-16)", true },
+ { PBTYPE_RTF, "text/rtf", "Rich Text Format", false },
+ { PBTYPE_PNG, "image/png", "Portable Network Graphics", false },
+ { PBTYPE_JPEG, "image/jpeg", "JPEG", false },
+ { PBTYPE_HTML, "text/html", "Plain HTML", false },
+ { PBTYPE_PDF, "application/pdf", "PDF File", false },
+ { nil, FLAVOR_SESX, "Star Embed Source (XML)", false },
+ { nil, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false },
+ { nil, FLAVOR_LSX, "Star Link Source (XML)", false },
+ { nil, FLAVOR_EOX, "Star Embedded Object (XML)", false },
+ { nil, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false },
+ { nil, FLAVOR_GDIMF, "GDIMetaFile", false },
+ { nil, FLAVOR_SODX, "Star Object Descriptor (XML)", false } };
+
+#define SIZE_FLAVOR_MAP (sizeof(flavorMap) / sizeof(FlavorMap))
+
+inline bool isByteSequenceType(const Type& theType)
+{
+ return (theType == cppu::UnoType<Sequence<sal_Int8>>::get());
+}
+
+inline bool isOUStringType(const Type& theType)
+{
+ return (theType == cppu::UnoType<OUString>::get());
+}
+
+} // unnamed namespace
+
+/* A base class for other data provider.
+ */
+class DataProviderBaseImpl : public DataProvider
+{
+public:
+ DataProviderBaseImpl(const Any& data);
+ DataProviderBaseImpl(id data);
+ virtual ~DataProviderBaseImpl() override;
+
+protected:
+ Any mData;
+ //NSData* mSystemData;
+ id mSystemData;
+};
+
+DataProviderBaseImpl::DataProviderBaseImpl(const Any& data)
+ : mData(data)
+ , mSystemData(nil)
+{
+}
+
+DataProviderBaseImpl::DataProviderBaseImpl(id data)
+ : mSystemData(data)
+{
+ [mSystemData retain];
+}
+
+DataProviderBaseImpl::~DataProviderBaseImpl()
+{
+ if (mSystemData)
+ {
+ [mSystemData release];
+ }
+}
+
+class Utf8DataProvider : public DataProviderBaseImpl
+{
+public:
+ Utf8DataProvider(const Any& data);
+ Utf8DataProvider(NSData* data);
+
+ NSData* getSystemData() override;
+ Any getOOoData() override;
+};
+
+Utf8DataProvider::Utf8DataProvider(const Any& data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+Utf8DataProvider::Utf8DataProvider(NSData* data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+NSData* Utf8DataProvider::getSystemData()
+{
+ OUString ustr;
+ mData >>= ustr;
+
+ OString strUtf8;
+ ustr.convertToString(&strUtf8, RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS);
+
+ return [NSData dataWithBytes:strUtf8.getStr() length:strUtf8.getLength()];
+}
+
+Any Utf8DataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ oOOData <<= OUString(static_cast<const char*>([mSystemData bytes]), [mSystemData length],
+ RTL_TEXTENCODING_UTF8);
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+class ByteSequenceDataProvider : public DataProviderBaseImpl
+{
+public:
+ ByteSequenceDataProvider(const Any& data);
+ ByteSequenceDataProvider(NSData* data);
+
+ NSData* getSystemData() override;
+ Any getOOoData() override;
+};
+
+ByteSequenceDataProvider::ByteSequenceDataProvider(const Any& data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+ByteSequenceDataProvider::ByteSequenceDataProvider(NSData* data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+NSData* ByteSequenceDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> rawData;
+ mData >>= rawData;
+
+ return [NSData dataWithBytes:rawData.getArray() length:rawData.getLength()];
+}
+
+Any ByteSequenceDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> byteSequence;
+ byteSequence.realloc(flavorDataLength);
+ memcpy(byteSequence.getArray(), [mSystemData bytes], flavorDataLength);
+ oOOData <<= byteSequence;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+class HTMLFormatDataProvider : public DataProviderBaseImpl
+{
+public:
+ HTMLFormatDataProvider(NSData* data);
+
+ NSData* getSystemData() override;
+ Any getOOoData() override;
+};
+
+HTMLFormatDataProvider::HTMLFormatDataProvider(NSData* data)
+ : DataProviderBaseImpl(data)
+{
+}
+
+NSData* HTMLFormatDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> textHtmlData;
+ mData >>= textHtmlData;
+
+ Sequence<sal_Int8> htmlFormatData = TextHtmlToHTMLFormat(textHtmlData);
+
+ return [NSData dataWithBytes:htmlFormatData.getArray() length:htmlFormatData.getLength()];
+}
+
+Any HTMLFormatDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> unkHtmlData;
+
+ unkHtmlData.realloc(flavorDataLength);
+ memcpy(unkHtmlData.getArray(), [mSystemData bytes], flavorDataLength);
+
+ Sequence<sal_Int8>* pPlainHtml = &unkHtmlData;
+ Sequence<sal_Int8> plainHtml;
+
+ if (isHTMLFormat(unkHtmlData))
+ {
+ plainHtml = HTMLFormatToTextHtml(unkHtmlData);
+ pPlainHtml = &plainHtml;
+ }
+
+ oOOData <<= *pPlainHtml;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+DataFlavorMapper::DataFlavorMapper()
+{
+ Reference<XComponentContext> xContext = comphelper::getProcessComponentContext();
+ mrXMimeCntFactory = MimeContentTypeFactory::create(xContext);
+}
+
+DataFlavorMapper::~DataFlavorMapper()
+{
+ // release potential NSStrings
+ for (OfficeOnlyTypes::iterator it = maOfficeOnlyTypes.begin(); it != maOfficeOnlyTypes.end();
+ ++it)
+ {
+ [it->second release];
+ it->second = nil;
+ }
+}
+
+DataFlavor DataFlavorMapper::systemToOpenOfficeFlavor(const NSString* systemDataFlavor) const
+{
+ DataFlavor oOOFlavor;
+
+ for (size_t i = 0; i < SIZE_FLAVOR_MAP; i++)
+ {
+ if ((flavorMap[i].SystemFlavor == nil
+ && ([systemDataFlavor
+ isEqualToString:[NSString stringWithUTF8String:flavorMap[i].OOoFlavor]]
+ ||
+ [systemDataFlavor hasPrefix:[[NSString stringWithUTF8String:flavorMap[i].OOoFlavor]
+ stringByAppendingString:@";"]]))
+ || (flavorMap[i].SystemFlavor != nil &&
+ [systemDataFlavor
+ isEqualToString:const_cast<NSString*>(flavorMap[i].SystemFlavor)]))
+ {
+ if (flavorMap[i].SystemFlavor == nil)
+ oOOFlavor.MimeType = NSStringToOUString(systemDataFlavor);
+ else
+ oOOFlavor.MimeType = OUString::createFromAscii(flavorMap[i].OOoFlavor);
+ oOOFlavor.HumanPresentableName
+ = OUString::createFromAscii(flavorMap[i].HumanPresentableName);
+ oOOFlavor.DataType = flavorMap[i].DataTypeOUString
+ ? cppu::UnoType<OUString>::get()
+ : cppu::UnoType<Sequence<sal_Int8>>::get();
+ return oOOFlavor;
+ }
+ } // for
+
+ // look if this might be an internal type; if it comes in here it must have
+ // been through openOfficeToSystemFlavor before, so it should then be in the map
+ OUString aTryFlavor(NSStringToOUString(systemDataFlavor));
+ if (maOfficeOnlyTypes.find(aTryFlavor) != maOfficeOnlyTypes.end())
+ {
+ oOOFlavor.MimeType = aTryFlavor;
+ oOOFlavor.HumanPresentableName.clear();
+ oOOFlavor.DataType = cppu::UnoType<Sequence<sal_Int8>>::get();
+ }
+
+ return oOOFlavor;
+}
+
+NSString* DataFlavorMapper::openOfficeToSystemFlavor(const DataFlavor& oOOFlavor,
+ bool& rbInternal) const
+{
+ NSString* sysFlavor = nullptr;
+ rbInternal = false;
+
+ for (size_t i = 0; i < SIZE_FLAVOR_MAP; ++i)
+ {
+ if (oOOFlavor.MimeType.startsWith(OUString::createFromAscii(flavorMap[i].OOoFlavor)))
+ {
+ if (flavorMap[i].SystemFlavor != nil)
+ sysFlavor = flavorMap[i].SystemFlavor;
+ else
+ sysFlavor = OUStringToNSString(oOOFlavor.MimeType);
+ }
+ }
+
+ if (!sysFlavor)
+ {
+ // For some reason, if we allow text/html, we get an OSL_ENSURE failure in xmloff that
+ // apparently is a symptom of something being seriously wrong:
+ // xmloff/source/transform/OOo2Oasis.cxx:1925: duplicate doc handler
+ // Because is then followed a bit later by an assertion failure:
+ // Assertion failed: (!m_pFirst && !m_pLast && "There are still indices registered"), function ~SwContentIndexReg, file [...]/sw/source/core/bastyp/index.cxx, line 226
+
+ if (oOOFlavor.MimeType == "text/html")
+ return nil;
+
+ rbInternal = true;
+ OfficeOnlyTypes::const_iterator it = maOfficeOnlyTypes.find(oOOFlavor.MimeType);
+
+ if (it == maOfficeOnlyTypes.end())
+ sysFlavor = maOfficeOnlyTypes[oOOFlavor.MimeType]
+ = OUStringToNSString(oOOFlavor.MimeType);
+ else
+ sysFlavor = it->second;
+ }
+
+ return sysFlavor;
+}
+
+NSString* DataFlavorMapper::openOfficeImageToSystemFlavor()
+{
+ if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_PNG ]])
+ return PBTYPE_PNG;
+ else if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_JPEG ]])
+ return PBTYPE_JPEG;
+ else if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_PDF ]])
+ return PBTYPE_PDF;
+ return @"";
+}
+
+DataProviderPtr_t
+DataFlavorMapper::getDataProvider(const NSString* systemFlavor,
+ Reference<XTransferable> const& rTransferable) const
+{
+ DataProviderPtr_t dp;
+
+ try
+ {
+ DataFlavor oOOFlavor = systemToOpenOfficeFlavor(systemFlavor);
+
+ Any data = rTransferable->getTransferData(oOOFlavor);
+
+ if (isByteSequenceType(data.getValueType()))
+ {
+ dp = DataProviderPtr_t(new ByteSequenceDataProvider(data));
+ }
+ else // Must be OUString type
+ {
+ SAL_WARN_IF(!isOUStringType(data.getValueType()), "vcl", "must be OUString type");
+ dp = DataProviderPtr_t(new Utf8DataProvider(data));
+ }
+ }
+ catch (const UnsupportedFlavorException& e)
+ {
+ SAL_WARN("vcl.ios.clipboard",
+ "DataFlavorMapper::getDataProvider(): Exception: " << e.Message);
+ // Somebody violates the contract of the clipboard
+ // interface @see XTransferable
+ }
+
+ return dp;
+}
+
+DataProviderPtr_t DataFlavorMapper::getDataProvider(const NSString* systemFlavor,
+ NSData* systemData)
+{
+ DataProviderPtr_t dp;
+
+ if (systemData == nil)
+ return dp;
+
+ if ([systemFlavor caseInsensitiveCompare:PBTYPE_UTF8PLAINTEXT] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new Utf8DataProvider(systemData));
+ }
+ else if ([systemFlavor caseInsensitiveCompare:PBTYPE_HTML] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new HTMLFormatDataProvider(systemData));
+ }
+ else
+ {
+ dp = DataProviderPtr_t(new ByteSequenceDataProvider(systemData));
+ }
+
+ return dp;
+}
+
+bool DataFlavorMapper::isValidMimeContentType(const OUString& contentType) const
+{
+ bool result = true;
+
+ try
+ {
+ Reference<XMimeContentType> xCntType(mrXMimeCntFactory->createMimeContentType(contentType));
+ }
+ catch (const IllegalArgumentException& e)
+ {
+ SAL_WARN("vcl.ios.clipboard",
+ "DataFlavorMapper::isValidMimeContentType(): Exception: " << e.Message);
+ result = false;
+ }
+
+ return result;
+}
+
+NSArray* DataFlavorMapper::flavorSequenceToTypesArray(
+ const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors) const
+{
+ const sal_uInt32 nFlavors = flavors.getLength();
+ NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:1];
+
+ bool bNeedDummyInternalFlavor(false);
+
+ for (sal_uInt32 i = 0; i < nFlavors; i++)
+ {
+ if (flavors[i].MimeType.startsWith("image/bmp"))
+ {
+ [array addObject:PBTYPE_PNG];
+ }
+ else
+ {
+ const NSString* str = openOfficeToSystemFlavor(flavors[i], bNeedDummyInternalFlavor);
+
+ if (str != nil)
+ {
+ [str retain];
+ [array addObject:str];
+ }
+ }
+ }
+
+ return [array autorelease];
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor>
+DataFlavorMapper::typesArrayToFlavorSequence(NSArray* types) const
+{
+ int nFormats = [types count];
+ Sequence<DataFlavor> flavors;
+
+ for (int i = 0; i < nFormats; i++)
+ {
+ NSString* sysFormat = [types objectAtIndex:i];
+ DataFlavor oOOFlavor = systemToOpenOfficeFlavor(sysFormat);
+
+ if (isValidFlavor(oOOFlavor))
+ {
+ flavors.realloc(flavors.getLength() + 1);
+ flavors.getArray()[flavors.getLength() - 1] = oOOFlavor;
+ SAL_INFO("vcl.ios.clipboard",
+ "Mapped " << [sysFormat UTF8String] << " to " << oOOFlavor.MimeType);
+ }
+ else
+ {
+ SAL_INFO("vcl.ios.clipboard",
+ "Was not able to map " << [sysFormat UTF8String] << " to an internal flavour");
+ }
+ }
+
+ return flavors;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/DataFlavorMapping.hxx b/vcl/ios/DataFlavorMapping.hxx
new file mode 100644
index 0000000000..dd115575f2
--- /dev/null
+++ b/vcl/ios/DataFlavorMapping.hxx
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <premac.h>
+#import <UIKit/UIKit.h>
+#include <postmac.h>
+
+#include <memory>
+#include <unordered_map>
+
+/* An interface to get the clipboard data in either
+ system or OOo format.
+ */
+class DataProvider
+{
+public:
+ virtual ~DataProvider(){};
+
+ /* Get the clipboard data in the system format.
+ The caller has to retain/release the returned
+ CFDataRef on demand.
+ */
+ virtual NSData* getSystemData() = 0;
+
+ /* Get the clipboard data in OOo format.
+ */
+ virtual css::uno::Any getOOoData() = 0;
+};
+
+typedef std::unique_ptr<DataProvider> DataProviderPtr_t;
+
+class DataFlavorMapper
+{
+public:
+ /* Initialize a DataFavorMapper instance. Throws a RuntimeException in case the XMimeContentTypeFactory service
+ cannot be created.
+ */
+ DataFlavorMapper();
+ ~DataFlavorMapper();
+
+ /* Map a system data flavor to an OpenOffice data flavor.
+ Return an empty string if there is not suitable
+ mapping from a system data flavor to an OpenOffice data
+ flavor.
+ */
+ css::datatransfer::DataFlavor systemToOpenOfficeFlavor(const NSString* systemDataFlavor) const;
+
+ /* Map an OpenOffice data flavor to a system data flavor.
+ If there is no suitable mapping available NULL will
+ be returned.
+ */
+ NSString* openOfficeToSystemFlavor(const css::datatransfer::DataFlavor& oooDataFlavor,
+ bool& rbInternal) const;
+
+ /* Select the best available image data type
+ If there is no suitable mapping available NULL will
+ be returned.
+ */
+ static NSString* openOfficeImageToSystemFlavor();
+
+ /* Get a data provider which is able to provide the data 'rTransferable' offers in a format that can
+ be put on to the system clipboard.
+ */
+ DataProviderPtr_t getDataProvider(
+ const NSString* systemFlavor,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTransferable) const;
+
+ /* Get a data provider which is able to provide 'systemData' in the OOo expected format.
+ */
+ static DataProviderPtr_t getDataProvider(const NSString* systemFlavor, NSArray* systemData);
+
+ /* Get a data provider which is able to provide 'systemData' in the OOo expected format.
+ */
+ static DataProviderPtr_t getDataProvider(const NSString* systemFlavor, NSData* systemData);
+
+ /* Translate a sequence of DataFlavors into a NSArray of system types.
+ Only those DataFlavors for which a suitable mapping to a system
+ type exist will be contained in the returned types array.
+ */
+ NSArray* flavorSequenceToTypesArray(
+ const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors) const;
+
+ /* Translate a NSArray of system types into a sequence of DataFlavors.
+ Only those types for which a suitable mapping to a DataFlavor
+ exist will be contained in the new DataFlavor Sequence.
+ */
+ css::uno::Sequence<css::datatransfer::DataFlavor>
+ typesArrayToFlavorSequence(NSArray* types) const;
+
+private:
+ /* Determines if the provided Mime content type is valid.
+ */
+ bool isValidMimeContentType(const OUString& contentType) const;
+
+private:
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ typedef std::unordered_map<OUString, NSString*> OfficeOnlyTypes;
+ mutable OfficeOnlyTypes maOfficeOnlyTypes;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/HtmlFmtFlt.cxx b/vcl/ios/HtmlFmtFlt.cxx
new file mode 100644
index 0000000000..4f90ced3bc
--- /dev/null
+++ b/vcl/ios/HtmlFmtFlt.cxx
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 "HtmlFmtFlt.hxx"
+
+#include <rtl/string.h>
+#include <osl/diagnose.h>
+
+#include <string>
+#include <sstream>
+#include <vector>
+#include <iomanip>
+#include <cassert>
+
+using namespace com::sun::star::uno;
+
+// converts the openoffice text/html clipboard format to the HTML Format
+// well known under MS Windows
+// the MS HTML Format has a header before the real html data
+
+// Version:1.0 Version number of the clipboard. Starting is 0.9
+// StartHTML: Byte count from the beginning of the clipboard to the start
+// of the context, or -1 if no context
+// EndHTML: Byte count from the beginning of the clipboard to the end
+// of the context, or -1 if no context
+// StartFragment: Byte count from the beginning of the clipboard to the
+// start of the fragment
+// EndFragment: Byte count from the beginning of the clipboard to the
+// end of the fragment
+// StartSelection: Byte count from the beginning of the clipboard to the
+// start of the selection
+// EndSelection: Byte count from the beginning of the clipboard to the
+// end of the selection
+
+// StartSelection and EndSelection are optional
+// The fragment should be preceded and followed by the HTML comments
+// <!--StartFragment--> and <!--EndFragment--> (no space between !-- and the
+// text
+
+namespace
+{
+std::string GetHtmlFormatHeader(size_t startHtml, size_t endHtml, size_t startFragment,
+ size_t endFragment)
+{
+ std::ostringstream htmlHeader;
+ htmlHeader << "Version:1.0" << '\r' << '\n';
+ htmlHeader << "StartHTML:" << std::setw(10) << std::setfill('0') << std::dec << startHtml
+ << '\r' << '\n';
+ htmlHeader << "EndHTML:" << std::setw(10) << std::setfill('0') << std::dec << endHtml << '\r'
+ << '\n';
+ htmlHeader << "StartFragment:" << std::setw(10) << std::setfill('0') << std::dec
+ << startFragment << '\r' << '\n';
+ htmlHeader << "EndFragment:" << std::setw(10) << std::setfill('0') << std::dec << endFragment
+ << '\r' << '\n';
+ return htmlHeader.str();
+}
+}
+
+// the office always writes the start and end html tag in upper cases and
+// without spaces both tags don't allow parameters
+const std::string TAG_HTML = std::string("<html>");
+const std::string TAG_END_HTML = std::string("</html>");
+
+// The body tag may have parameters so we need to search for the
+// closing '>' manually e.g. <BODY param> #92840#
+const std::string TAG_BODY = std::string("<body");
+const std::string TAG_END_BODY = std::string("</body");
+
+Sequence<sal_Int8> SAL_CALL TextHtmlToHTMLFormat(Sequence<sal_Int8> const& aTextHtml)
+{
+ OSL_ASSERT(aTextHtml.getLength() > 0);
+
+ if (aTextHtml.getLength() <= 0)
+ return Sequence<sal_Int8>();
+
+ // fill the buffer with dummy values to calc the exact length
+ std::string dummyHtmlHeader = GetHtmlFormatHeader(0, 0, 0, 0);
+ size_t lHtmlFormatHeader = dummyHtmlHeader.length();
+
+ std::string textHtml(reinterpret_cast<const char*>(aTextHtml.getConstArray()),
+ reinterpret_cast<const char*>(aTextHtml.getConstArray())
+ + aTextHtml.getLength());
+
+ std::string::size_type nStartHtml = textHtml.find(TAG_HTML) + lHtmlFormatHeader
+ - 1; // we start one before '<HTML>' Word 2000 does also so
+ std::string::size_type nEndHtml = textHtml.find(TAG_END_HTML) + lHtmlFormatHeader
+ + TAG_END_HTML.length()
+ + 1; // our SOffice 5.2 wants 2 behind </HTML>?
+
+ // The body tag may have parameters so we need to search for the
+ // closing '>' manually e.g. <BODY param> #92840#
+ std::string::size_type nStartFragment
+ = textHtml.find(">", textHtml.find(TAG_BODY)) + lHtmlFormatHeader + 1;
+ std::string::size_type nEndFragment = textHtml.find(TAG_END_BODY) + lHtmlFormatHeader;
+
+ std::string htmlFormat
+ = GetHtmlFormatHeader(nStartHtml, nEndHtml, nStartFragment, nEndFragment);
+ htmlFormat += textHtml;
+
+ Sequence<sal_Int8> byteSequence(htmlFormat.length() + 1); // space the trailing '\0'
+ memset(byteSequence.getArray(), 0, byteSequence.getLength());
+
+ memcpy(static_cast<void*>(byteSequence.getArray()),
+ static_cast<const void*>(htmlFormat.c_str()), htmlFormat.length());
+
+ return byteSequence;
+}
+
+const char* const HtmlStartTag = "<html";
+
+Sequence<sal_Int8> HTMLFormatToTextHtml(const Sequence<sal_Int8>& aHTMLFormat)
+{
+ assert(isHTMLFormat(aHTMLFormat) && "No HTML Format provided");
+
+ Sequence<sal_Int8>& nonconstHTMLFormatRef = const_cast<Sequence<sal_Int8>&>(aHTMLFormat);
+ char* dataStart = reinterpret_cast<char*>(nonconstHTMLFormatRef.getArray());
+ char* dataEnd = dataStart + nonconstHTMLFormatRef.getLength() - 1;
+ const char* htmlStartTag = strcasestr(dataStart, HtmlStartTag);
+
+ assert(htmlStartTag && "Seems to be no HTML at all");
+
+ // It doesn't seem to be HTML? Well then simply return what has been
+ // provided in non-debug builds
+ if (htmlStartTag == nullptr)
+ {
+ return aHTMLFormat;
+ }
+
+ sal_Int32 len = dataEnd - htmlStartTag;
+ Sequence<sal_Int8> plainHtmlData(len);
+
+ memcpy(static_cast<void*>(plainHtmlData.getArray()), htmlStartTag, len);
+
+ return plainHtmlData;
+}
+
+/* A simple format detection. We are just comparing the first few bytes
+ of the provided byte sequence to see whether or not it is the MS
+ Office Html format. If it shows that this is not reliable enough we
+ can improve this
+*/
+const char HtmlFormatStart[] = "Version:";
+int const HtmlFormatStartLen = (sizeof(HtmlFormatStart) - 1);
+
+bool isHTMLFormat(const Sequence<sal_Int8>& aHtmlSequence)
+{
+ if (aHtmlSequence.getLength() < HtmlFormatStartLen)
+ return false;
+
+ return rtl_str_compareIgnoreAsciiCase_WithLength(
+ HtmlFormatStart, HtmlFormatStartLen,
+ reinterpret_cast<const char*>(aHtmlSequence.getConstArray()), HtmlFormatStartLen)
+ == 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/HtmlFmtFlt.hxx b/vcl/ios/HtmlFmtFlt.hxx
new file mode 100644
index 0000000000..6a2cd6f646
--- /dev/null
+++ b/vcl/ios/HtmlFmtFlt.hxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Sequence.hxx>
+
+/* Transform plain HTML into the format expected by MS Office.
+ */
+css::uno::Sequence<sal_Int8> TextHtmlToHTMLFormat(css::uno::Sequence<sal_Int8> const& aTextHtml);
+
+/* Transform the MS Office HTML format into plain HTML.
+ */
+css::uno::Sequence<sal_Int8> HTMLFormatToTextHtml(const css::uno::Sequence<sal_Int8>& aHTMLFormat);
+
+/* Detects whether the given byte sequence contains the MS Office Html format.
+
+ @returns True if the MS Office Html format will be detected False otherwise.
+ */
+bool isHTMLFormat(const css::uno::Sequence<sal_Int8>& aHtmlSequence);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/clipboard.cxx b/vcl/ios/clipboard.cxx
new file mode 100644
index 0000000000..59209504da
--- /dev/null
+++ b/vcl/ios/clipboard.cxx
@@ -0,0 +1,182 @@
+/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 "ios/iosinst.hxx"
+#include "quartz/utils.h"
+
+#include "clipboard.hxx"
+
+#include "DataFlavorMapping.hxx"
+#include "iOSTransferable.hxx"
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+iOSClipboard::iOSClipboard()
+ : WeakComponentImplHelper<XSystemClipboard, XServiceInfo>(m_aMutex)
+{
+ auto xContext = comphelper::getProcessComponentContext();
+
+ mrXMimeCntFactory = css::datatransfer::MimeContentTypeFactory::create(xContext);
+
+ mpDataFlavorMapper.reset(new DataFlavorMapper());
+}
+
+iOSClipboard::~iOSClipboard() {}
+
+css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL iOSClipboard::getContents()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ return css::uno::Reference<css::datatransfer::XTransferable>(
+ new iOSTransferable(mrXMimeCntFactory, mpDataFlavorMapper));
+}
+
+void SAL_CALL iOSClipboard::setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& /*xClipboardOwner*/)
+{
+ NSArray* types = xTransferable.is() ? mpDataFlavorMapper->flavorSequenceToTypesArray(
+ xTransferable->getTransferDataFlavors())
+ : [NSArray array];
+
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:1];
+ NSArray* array = @[ dict ];
+
+ for (sal_uInt32 i = 0; i < [types count]; ++i)
+ {
+ DataProviderPtr_t dp = mpDataFlavorMapper->getDataProvider(types[i], xTransferable);
+
+ if (dp.get() != nullptr)
+ {
+ NSData* pBoardData = (NSData*)dp->getSystemData();
+ dict[types[i]] = pBoardData;
+ }
+ }
+ SAL_INFO("vcl.ios.clipboard", "Setting pasteboard items: " << NSDictionaryKeysToOUString(dict));
+ [[UIPasteboard generalPasteboard] setItems:array options:@{}];
+
+ // We don't keep a copy of the clipboard contents around in-process, so fire the lost clipboard
+ // ownership event right away.
+ // fireLostClipboardOwnershipEvent(xClipboardOwner, xTransferable);
+
+ // fireClipboardChangedEvent(xTransferable);
+}
+
+OUString SAL_CALL iOSClipboard::getName() { return OUString(); }
+
+sal_Int8 SAL_CALL iOSClipboard::getRenderingCapabilities() { return 0; }
+
+void SAL_CALL iOSClipboard::addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!listener.is())
+ throw css::lang::IllegalArgumentException(
+ "empty reference", static_cast<css::datatransfer::clipboard::XClipboardEx*>(this), 1);
+
+ mClipboardListeners.push_back(listener);
+}
+
+void SAL_CALL iOSClipboard::removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!listener.is())
+ throw css::lang::IllegalArgumentException(
+ "empty reference", static_cast<css::datatransfer::clipboard::XClipboardEx*>(this), 1);
+
+ mClipboardListeners.remove(listener);
+}
+
+void iOSClipboard::fireClipboardChangedEvent(
+ css::uno::Reference<css::datatransfer::XTransferable> xNewContents)
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ std::list<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> listeners(
+ mClipboardListeners);
+ css::datatransfer::clipboard::ClipboardEvent aEvent;
+
+ if (!listeners.empty())
+ {
+ aEvent = css::datatransfer::clipboard::ClipboardEvent(getXWeak(), xNewContents);
+ }
+
+ aGuard.clear();
+
+ while (!listeners.empty())
+ {
+ if (listeners.front().is())
+ {
+ try
+ {
+ listeners.front()->changedContents(aEvent);
+ }
+ catch (const css::uno::RuntimeException&)
+ {
+ }
+ }
+ listeners.pop_front();
+ }
+}
+
+void iOSClipboard::fireLostClipboardOwnershipEvent(
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const& oldOwner,
+ css::uno::Reference<css::datatransfer::XTransferable> const& oldContent)
+{
+ assert(oldOwner.is());
+
+ try
+ {
+ oldOwner->lostOwnership(static_cast<css::datatransfer::clipboard::XClipboardEx*>(this),
+ oldContent);
+ }
+ catch (const css::uno::RuntimeException&)
+ {
+ }
+}
+
+OUString SAL_CALL iOSClipboard::getImplementationName()
+{
+ return OUString("com.sun.star.datatransfer.clipboard.iOSClipboard");
+}
+
+sal_Bool SAL_CALL iOSClipboard::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL iOSClipboard::getSupportedServiceNames()
+{
+ return { OUString("com.sun.star.datatransfer.clipboard.SystemClipboard") };
+}
+
+css::uno::Reference<css::uno::XInterface>
+IosSalInstance::CreateClipboard(const css::uno::Sequence<css::uno::Any>&)
+{
+ return getXWeak(new iOSClipboard());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/clipboard.hxx b/vcl/ios/clipboard.hxx
new file mode 100644
index 0000000000..0868409126
--- /dev/null
+++ b/vcl/ios/clipboard.hxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include "DataFlavorMapping.hxx"
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <list>
+
+#include <premac.h>
+#import <UIKit/UIKit.h>
+#include <postmac.h>
+
+class iOSClipboard
+ : public ::cppu::BaseMutex,
+ public ::cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::lang::XServiceInfo>
+{
+public:
+ iOSClipboard();
+
+ virtual ~iOSClipboard() override;
+ iOSClipboard(const iOSClipboard&) = delete;
+ iOSClipboard& operator=(const iOSClipboard&) = delete;
+
+ // XClipboard
+
+ css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override;
+
+ void SAL_CALL setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+ override;
+
+ OUString SAL_CALL getName() override;
+
+ // XClipboardEx
+
+ sal_Int8 SAL_CALL getRenderingCapabilities() override;
+
+ // XClipboardNotifier
+
+ void SAL_CALL addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+
+ void SAL_CALL removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+ override;
+
+ // XServiceInfo
+
+ OUString SAL_CALL getImplementationName() override;
+
+ sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+private:
+ /* Notify the current clipboard owner that he is no longer the clipboard owner. */
+ void fireLostClipboardOwnershipEvent(
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const& oldOwner,
+ css::uno::Reference<css::datatransfer::XTransferable> const& oldContent);
+
+ /* Notify all registered XClipboardListener that the clipboard content has changed. */
+ void
+ fireClipboardChangedEvent(css::uno::Reference<css::datatransfer::XTransferable> xNewContents);
+
+private:
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ std::list<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>>
+ mClipboardListeners;
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> mXClipboardOwner;
+ std::shared_ptr<DataFlavorMapper> mpDataFlavorMapper;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/dummies.cxx b/vcl/ios/dummies.cxx
new file mode 100644
index 0000000000..a1d3cbcb67
--- /dev/null
+++ b/vcl/ios/dummies.cxx
@@ -0,0 +1,117 @@
+/* -*- 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/svapp.hxx>
+#include "ios/iosinst.hxx"
+#include "salprn.hxx"
+#include "quartz/salgdi.h"
+#include "headless/svpdata.hxx"
+#include "headless/svpinst.hxx"
+#include "unx/fontmanager.hxx"
+
+std::unique_ptr<SalPrinter> SvpSalInstance::CreatePrinter( SalInfoPrinter* /* pInfoPrinter */ )
+{
+ return nullptr;
+}
+
+OUString SvpSalInstance::GetDefaultPrinter()
+{
+ return OUString();
+}
+
+std::unique_ptr<GenPspGraphics> SvpSalInstance::CreatePrintGraphics()
+{
+ return nullptr;
+}
+
+void SvpSalInstance::PostPrintersChanged()
+{
+}
+
+SalInfoPrinter* SvpSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* /* pQueueInfo */,
+ ImplJobSetup* /* pJobSetup */ )
+{
+ return NULL;
+}
+
+void SvpSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+void SvpSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* /* pList */ )
+{
+}
+
+void SvpSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* /* pInfo */ )
+{
+}
+
+std::unique_ptr<SalPrinter> SalGenericInstance::CreatePrinter( SalInfoPrinter* /* pInfoPrinter */ )
+{
+ return nullptr;
+}
+
+OUString SalGenericInstance::GetDefaultPrinter()
+{
+ return OUString();
+}
+
+void SalGenericInstance::PostPrintersChanged()
+{
+}
+
+SalInfoPrinter* SalGenericInstance::CreateInfoPrinter( SalPrinterQueueInfo* /* pQueueInfo */,
+ ImplJobSetup* /* pJobSetup */ )
+{
+ return NULL;
+}
+
+void SalGenericInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+void SalGenericInstance::GetPrinterQueueInfo( ImplPrnQueueList* /* pList */ )
+{
+}
+
+void SalGenericInstance::GetPrinterQueueState( SalPrinterQueueInfo* /* pInfo */ )
+{
+}
+
+void SalGenericInstance::updatePrinterUpdate()
+{
+}
+
+void SalGenericInstance::jobEndedPrinterUpdate()
+{
+}
+
+using namespace psp;
+
+bool AquaGraphicsBackend::drawNativeControl(ControlType /* nType */,
+ ControlPart /* nPart */,
+ const tools::Rectangle & /* rControlRegion */,
+ ControlState /* nState */,
+ const ImplControlValue & /* aValue */)
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/iOSTransferable.cxx b/vcl/ios/iOSTransferable.cxx
new file mode 100644
index 0000000000..bfbc8a9afa
--- /dev/null
+++ b/vcl/ios/iOSTransferable.cxx
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <osl/diagnose.h>
+
+#include <quartz/utils.h>
+
+#include "iOSTransferable.hxx"
+
+#include "DataFlavorMapping.hxx"
+
+using namespace osl;
+using namespace cppu;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+
+namespace
+{
+bool isValidFlavor(const DataFlavor& aFlavor)
+{
+ size_t len = aFlavor.MimeType.getLength();
+ Type dtype = aFlavor.DataType;
+ return ((len > 0)
+ && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get())
+ || (dtype == cppu::UnoType<OUString>::get())));
+}
+
+bool cmpAllContentTypeParameter(const Reference<XMimeContentType>& xLhs,
+ const Reference<XMimeContentType>& xRhs)
+{
+ Sequence<OUString> xLhsFlavors = xLhs->getParameters();
+ Sequence<OUString> xRhsFlavors = xRhs->getParameters();
+
+ // Stop here if the number of parameters is different already
+ if (xLhsFlavors.getLength() != xRhsFlavors.getLength())
+ return false;
+
+ try
+ {
+ OUString pLhs;
+ OUString pRhs;
+
+ for (sal_Int32 i = 0; i < xLhsFlavors.getLength(); i++)
+ {
+ pLhs = xLhs->getParameterValue(xLhsFlavors[i]);
+ pRhs = xRhs->getParameterValue(xLhsFlavors[i]);
+
+ if (!pLhs.equalsIgnoreAsciiCase(pRhs))
+ {
+ return false;
+ }
+ }
+ }
+ catch (IllegalArgumentException&)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+} // unnamed namespace
+
+iOSTransferable::iOSTransferable(const Reference<XMimeContentTypeFactory>& rXMimeCntFactory,
+ std::shared_ptr<DataFlavorMapper> pDataFlavorMapper)
+ : mrXMimeCntFactory(rXMimeCntFactory)
+ , mDataFlavorMapper(pDataFlavorMapper)
+{
+ initClipboardItemList();
+}
+
+iOSTransferable::~iOSTransferable() {}
+
+Any SAL_CALL iOSTransferable::getTransferData(const DataFlavor& aFlavor)
+{
+ if (!isValidFlavor(aFlavor) || !isDataFlavorSupported(aFlavor))
+ {
+ throw UnsupportedFlavorException("Unsupported data flavor",
+ static_cast<XTransferable*>(this));
+ }
+
+ bool bInternal(false);
+ NSString* sysFormat = (aFlavor.MimeType.startsWith("image/png"))
+ ? DataFlavorMapper::openOfficeImageToSystemFlavor()
+ : mDataFlavorMapper->openOfficeToSystemFlavor(aFlavor, bInternal);
+ DataProviderPtr_t dp;
+
+ NSData* sysData = [[UIPasteboard generalPasteboard] dataForPasteboardType:sysFormat];
+ if (!sysData)
+ {
+ // Related: gh#5908 throw an exception if the data flavor is nil
+ // If nil is returned, it can mean that the user has selected the
+ // "disallow" option and so we can't access the current clipboard
+ // contents. Also, by throwing an exception, the "allow or disallow"
+ // dialog will display again the next time the user tries to paste.
+ throw UnsupportedFlavorException("Data flavor is nil", static_cast<XTransferable*>(this));
+ }
+
+ dp = DataFlavorMapper::getDataProvider(sysFormat, sysData);
+
+ if (dp.get() == nullptr)
+ {
+ throw UnsupportedFlavorException("Unsupported data flavor",
+ static_cast<XTransferable*>(this));
+ }
+
+ return dp->getOOoData();
+}
+
+Sequence<DataFlavor> SAL_CALL iOSTransferable::getTransferDataFlavors() { return mFlavorList; }
+
+sal_Bool SAL_CALL iOSTransferable::isDataFlavorSupported(const DataFlavor& aFlavor)
+{
+ for (sal_Int32 i = 0; i < mFlavorList.getLength(); i++)
+ if (compareDataFlavors(aFlavor, mFlavorList[i]))
+ return true;
+
+ return false;
+}
+
+void iOSTransferable::initClipboardItemList()
+{
+ NSArray* pboardFormats = [[UIPasteboard generalPasteboard] pasteboardTypes];
+
+ if (pboardFormats == nullptr)
+ {
+ throw RuntimeException("Cannot get clipboard data", static_cast<XTransferable*>(this));
+ }
+
+ SAL_INFO("vcl.ios.clipboard", "Types on clipboard: " << NSStringArrayToOUString(pboardFormats));
+
+ mFlavorList = mDataFlavorMapper->typesArrayToFlavorSequence(pboardFormats);
+}
+
+/* Compares two DataFlavors. Returns true if both DataFlavor have the same media type
+ and the number of parameter and all parameter values do match otherwise false
+ is returned.
+ */
+bool iOSTransferable::compareDataFlavors(const DataFlavor& lhs, const DataFlavor& rhs)
+{
+ try
+ {
+ Reference<XMimeContentType> xLhs(mrXMimeCntFactory->createMimeContentType(lhs.MimeType));
+ Reference<XMimeContentType> xRhs(mrXMimeCntFactory->createMimeContentType(rhs.MimeType));
+
+ if (!xLhs->getFullMediaType().equalsIgnoreAsciiCase(xRhs->getFullMediaType())
+ || !cmpAllContentTypeParameter(xLhs, xRhs))
+ {
+ return false;
+ }
+ }
+ catch (IllegalArgumentException&)
+ {
+ OSL_FAIL("Invalid content type detected");
+ return false;
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/iOSTransferable.hxx b/vcl/ios/iOSTransferable.hxx
new file mode 100644
index 0000000000..91f4e440ee
--- /dev/null
+++ b/vcl/ios/iOSTransferable.hxx
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+
+#include "DataFlavorMapping.hxx"
+
+#include <premac.h>
+#import <UIKit/UIKit.h>
+#include <postmac.h>
+
+#include <memory>
+#include <vector>
+
+class iOSTransferable : public ::cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+public:
+ explicit iOSTransferable(
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> const& rXMimeCntFactory,
+ std::shared_ptr<DataFlavorMapper> pDataFlavorMapper);
+
+ virtual ~iOSTransferable() override;
+ iOSTransferable(const iOSTransferable&) = delete;
+ iOSTransferable& operator=(const iOSTransferable&) = delete;
+
+ // XTransferable
+
+ virtual css::uno::Any SAL_CALL
+ getTransferData(const css::datatransfer::DataFlavor& aFlavor) override;
+
+ css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
+
+ sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& aFlavor) override;
+
+ // Helper functions not part of the XTransferable interface
+
+ void initClipboardItemList();
+
+ bool compareDataFlavors(const css::datatransfer::DataFlavor& lhs,
+ const css::datatransfer::DataFlavor& rhs);
+
+private:
+ css::uno::Sequence<css::datatransfer::DataFlavor> mFlavorList;
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ std::shared_ptr<DataFlavorMapper> mDataFlavorMapper;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/iosinst.cxx b/vcl/ios/iosinst.cxx
new file mode 100644
index 0000000000..371d5c246c
--- /dev/null
+++ b/vcl/ios/iosinst.cxx
@@ -0,0 +1,165 @@
+/* -*- 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 <premac.h>
+#include <UIKit/UIKit.h>
+#include <postmac.h>
+
+#include "ios/iosinst.hxx"
+#include "quartz/salgdi.h"
+#include "headless/svpdata.hxx"
+#include "headless/svpdummies.hxx"
+#include "quartz/utils.h"
+#include "quartz/SystemFontList.hxx"
+#include <vcl/layout.hxx>
+#include <vcl/settings.hxx>
+
+// Totally wrong of course but doesn't seem to harm much in the iOS app.
+static int viewWidth = 1, viewHeight = 1;
+
+void IosSalInstance::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
+{
+ rRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( 0, 0 ),
+ AbsoluteScreenPixelSize( viewWidth, viewHeight ) );
+}
+
+IosSalInstance *IosSalInstance::getInstance()
+{
+ if (!ImplGetSVData())
+ return NULL;
+ return static_cast<IosSalInstance *>(GetSalInstance());
+}
+
+IosSalInstance::IosSalInstance( std::unique_ptr<SalYieldMutex> pMutex )
+ : SvpSalInstance( std::move(pMutex) )
+{
+}
+
+IosSalInstance::~IosSalInstance()
+{
+}
+
+class IosSalSystem : public SvpSalSystem {
+public:
+ IosSalSystem() : SvpSalSystem() {}
+ virtual ~IosSalSystem() {}
+ virtual int ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons );
+};
+
+SalSystem *IosSalInstance::CreateSalSystem()
+{
+ return new IosSalSystem();
+}
+
+class IosSalFrame : public SvpSalFrame
+{
+public:
+ IosSalFrame( IosSalInstance *pInstance,
+ SalFrame *pParent,
+ SalFrameStyleFlags nSalFrameStyle)
+ : SvpSalFrame( pInstance, pParent, nSalFrameStyle )
+ {
+ if (pParent == NULL && viewWidth > 1 && viewHeight > 1)
+ SetPosSize(0, 0, viewWidth, viewHeight, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT);
+ }
+
+ virtual void GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) override
+ {
+ IosSalInstance::getInstance()->GetWorkArea( rRect );
+ }
+
+ virtual void ShowFullScreen( bool, sal_Int32 ) override
+ {
+ SetPosSize( 0, 0, viewWidth, viewHeight,
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+ }
+
+ virtual void UpdateSettings( AllSettings &rSettings ) override
+ {
+ // Clobber the UI fonts
+ vcl::Font aFont( "Helvetica", Size( 0, 10 ) );
+
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+ aStyleSet.SetAppFont( aFont );
+ aStyleSet.SetHelpFont( aFont );
+ aStyleSet.SetMenuFont( aFont );
+ aStyleSet.SetToolFont( aFont );
+ aStyleSet.SetLabelFont( aFont );
+ aStyleSet.SetRadioCheckFont( aFont );
+ aStyleSet.SetPushButtonFont( aFont );
+ aStyleSet.SetFieldFont( aFont );
+ aStyleSet.SetIconFont( aFont );
+ aStyleSet.SetTabFont( aFont );
+ aStyleSet.SetGroupFont( aFont );
+
+ Color aBackgroundColor( 0xff, 0xff, 0xff );
+ aStyleSet.BatchSetBackgrounds( aBackgroundColor, false );
+ aStyleSet.SetMenuColor( aBackgroundColor );
+ aStyleSet.SetMenuBarColor( aBackgroundColor );
+ aStyleSet.SetDialogColor( aBackgroundColor );
+
+ rSettings.SetStyleSettings( aStyleSet );
+ }
+};
+
+SalFrame *IosSalInstance::CreateChildFrame( SystemParentData* pParent, SalFrameStyleFlags nStyle )
+{
+ (void)pParent;
+ return new IosSalFrame( this, NULL, nStyle );
+}
+
+SalFrame *IosSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ return new IosSalFrame( this, pParent, nStyle );
+}
+
+SalData::SalData() :
+ mxRGBSpace( CGColorSpaceCreateDeviceRGB() ),
+ mxGraySpace( CGColorSpaceCreateDeviceGray() )
+{
+ SetSalData(this);
+}
+
+SalData::~SalData()
+{
+ CGColorSpaceRelease(mxRGBSpace);
+ CGColorSpaceRelease(mxGraySpace);
+}
+
+extern "C" SalInstance *create_SalInstance()
+{
+ IosSalInstance* pInstance = new IosSalInstance( std::make_unique<SvpSalYieldMutex>() );
+ new SvpSalData();
+ return pInstance;
+}
+
+int IosSalSystem::ShowNativeDialog( const OUString& rTitle,
+ const OUString& rMessage,
+ const std::vector< OUString >& rButtons )
+{
+ (void)rButtons;
+
+ NSLog(@"%@: %@", CreateNSString(rTitle), CreateNSString(rMessage));
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/ios/salios.cxx b/vcl/ios/salios.cxx
new file mode 100644
index 0000000000..362fa258ea
--- /dev/null
+++ b/vcl/ios/salios.cxx
@@ -0,0 +1,585 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+// This file contains the iOS-specific versions of the functions which were touched in the commit to
+// fix tdf#138122. The functions are here (for now) as they were before that commit. The
+// macOS-specific versions of these functions are in vcl/osx/salmacos.cxx.
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/bitmap.hxx>
+
+#include <ios/iosinst.hxx>
+#include <quartz/salbmp.h>
+#include <quartz/salgdi.h>
+#include <quartz/salvd.h>
+#include <quartz/utils.h>
+
+#include <svdata.hxx>
+
+// From salbmp.cxx
+
+bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits, int nX, int nY, int nWidth, int nHeight, bool bFlipped)
+{
+ SAL_WARN_IF(!rLayerHolder.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
+
+ // sanitize input parameters
+ if( nX < 0 ) {
+ nWidth += nX;
+ nX = 0;
+ }
+
+ if( nY < 0 ) {
+ nHeight += nY;
+ nY = 0;
+ }
+
+ const CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
+
+ if( nWidth >= static_cast<int>(aLayerSize.width) - nX )
+ nWidth = static_cast<int>(aLayerSize.width) - nX;
+
+ if( nHeight >= static_cast<int>(aLayerSize.height) - nY )
+ nHeight = static_cast<int>(aLayerSize.height) - nY;
+
+ if( (nWidth < 0) || (nHeight < 0) )
+ nWidth = nHeight = 0;
+
+ // initialize properties
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ mnBits = nBitmapBits ? nBitmapBits : 32;
+
+ // initialize drawing context
+ CreateContext();
+
+ // copy layer content into the bitmap buffer
+ const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX), static_cast<CGFloat>(-nY) };
+ if (maGraphicContext.isSet()) // remove warning
+ {
+ if( bFlipped )
+ {
+ CGContextTranslateCTM( maGraphicContext.get(), 0, +mnHeight );
+
+ CGContextScaleCTM( maGraphicContext.get(), +1, -1 );
+ }
+
+ CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
+ }
+ return true;
+}
+
+// From salgdicommon.cxx
+
+void AquaGraphicsBackend::copyBits(const SalTwoRect& rPosAry, SalGraphics *pSrcGraphics)
+{
+ //from unix salgdi2.cxx
+ //[FIXME] find a better way to prevent calc from crashing when width and height are negative
+ if( rPosAry.mnSrcWidth <= 0 ||
+ rPosAry.mnSrcHeight <= 0 ||
+ rPosAry.mnDestWidth <= 0 ||
+ rPosAry.mnDestHeight <= 0 )
+ {
+ return;
+ }
+
+ // If called from idle layout, maContextHolder.get() is NULL, no idea what to do
+ if (!mrShared.maContextHolder.isSet())
+ return;
+
+ AquaSharedAttributes* pSrcShared = nullptr;
+
+ if (pSrcGraphics)
+ {
+ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
+ pSrcShared = &pSrc->getAquaGraphicsBackend()->GetShared();
+ }
+ else
+ pSrcShared = &mrShared;
+
+ // accelerate trivial operations
+ const bool bSameGraphics = (pSrcShared == &mrShared);
+
+ if( bSameGraphics &&
+ (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
+ (rPosAry.mnSrcHeight == rPosAry.mnDestHeight))
+ {
+ // short circuit if there is nothing to do
+ if( (rPosAry.mnSrcX == rPosAry.mnDestX) &&
+ (rPosAry.mnSrcY == rPosAry.mnDestY))
+ {
+ return;
+ }
+ // use copyArea() if source and destination context are identical
+ copyArea( rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, false/*bWindowInvalidate*/ );
+ return;
+ }
+
+ mrShared.applyXorContext();
+ if (!bSameGraphics)
+ pSrcShared->applyXorContext();
+
+ SAL_WARN_IF (!pSrcShared->maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
+
+ const CGPoint aDstPoint = CGPointMake(+rPosAry.mnDestX - rPosAry.mnSrcX, rPosAry.mnDestY - rPosAry.mnSrcY);
+ if ((rPosAry.mnSrcWidth == rPosAry.mnDestWidth &&
+ rPosAry.mnSrcHeight == rPosAry.mnDestHeight) &&
+ (!mrShared.mnBitmapDepth || (aDstPoint.x + pSrcShared->mnWidth) <= mrShared.mnWidth)
+ && pSrcShared->maLayer.isSet()) // workaround for a Quartz crash
+ {
+ // in XOR mode the drawing context is redirected to the XOR mask
+ // if source and target are identical then copyBits() paints onto the target context though
+ CGContextHolder aCopyContext = mrShared.maContextHolder;
+ if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
+ {
+ if (bSameGraphics)
+ {
+ aCopyContext.set(mrShared.mpXorEmulation->GetTargetContext());
+ }
+ }
+ aCopyContext.saveState();
+
+ const CGRect aDstRect = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ CGContextClipToRect(aCopyContext.get(), aDstRect);
+
+ // draw at new destination
+ // NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down
+ if (pSrcShared->isFlipped())
+ {
+ CGContextTranslateCTM(aCopyContext.get(), 0, +mrShared.mnHeight);
+ CGContextScaleCTM(aCopyContext.get(), +1, -1);
+ }
+
+ // TODO: pSrc->size() != this->size()
+ CGContextDrawLayerAtPoint(aCopyContext.get(), aDstPoint, pSrcShared->maLayer.get());
+
+ aCopyContext.restoreState();
+ // mark the destination rectangle as updated
+ refreshRect(aDstRect);
+ }
+ else
+ {
+ std::shared_ptr<SalBitmap> pBitmap;
+ if (pSrcGraphics)
+ pBitmap = pSrcGraphics->GetImpl()->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ else
+ pBitmap = getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+
+ if (pBitmap)
+ {
+ SalTwoRect aPosAry( rPosAry );
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap(aPosAry, *pBitmap);
+ }
+ }
+}
+
+void AquaGraphicsBackend::copyArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool /*bWindowInvalidate*/)
+{
+ SAL_WARN_IF (!mrShared.maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyArea() for non-layered graphics this=" << this);
+
+ if (!mrShared.maLayer.isSet())
+ return;
+
+ float fScale = mrShared.maLayer.getScale();
+
+ tools::Long nScaledSourceX = nSrcX * fScale;
+ tools::Long nScaledSourceY = nSrcY * fScale;
+
+ tools::Long nScaledTargetX = nDstX * fScale;
+ tools::Long nScaledTargetY = nDstY * fScale;
+
+ tools::Long nScaledSourceWidth = nSrcWidth * fScale;
+ tools::Long nScaledSourceHeight = nSrcHeight * fScale;
+
+ mrShared.applyXorContext();
+
+ mrShared.maContextHolder.saveState();
+
+ // in XOR mode the drawing context is redirected to the XOR mask
+ // copyArea() always works on the target context though
+ CGContextRef xCopyContext = mrShared.maContextHolder.get();
+
+ if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
+ {
+ xCopyContext = mrShared.mpXorEmulation->GetTargetContext();
+ }
+
+ // If we have a scaled layer, we need to revert the scaling or else
+ // it will interfere with the coordinate calculation
+ CGContextScaleCTM(xCopyContext, 1.0 / fScale, 1.0 / fScale);
+
+ // drawing a layer onto its own context causes trouble on OSX => copy it first
+ // TODO: is it possible to get rid of this unneeded copy more often?
+ // e.g. on OSX>=10.5 only this situation causes problems:
+ // mnBitmapDepth && (aDstPoint.x + pSrc->mnWidth) > mnWidth
+
+ CGLayerHolder sSourceLayerHolder(mrShared.maLayer);
+ {
+ const CGSize aSrcSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
+ sSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSrcSize, nullptr));
+
+ const CGContextRef xSrcContext = CGLayerGetContext(sSourceLayerHolder.get());
+
+ CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
+ if (mrShared.isFlipped())
+ {
+ CGContextTranslateCTM(xSrcContext, 0, +nScaledSourceHeight);
+ CGContextScaleCTM(xSrcContext, +1, -1);
+ aSrcPoint.y = (nScaledSourceY + nScaledSourceHeight) - (mrShared.mnHeight * fScale);
+ }
+ CGContextSetBlendMode(xSrcContext, kCGBlendModeCopy);
+
+ CGContextDrawLayerAtPoint(xSrcContext, aSrcPoint, mrShared.maLayer.get());
+ }
+
+ // draw at new destination
+ const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
+ CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
+ CGContextDrawLayerInRect(xCopyContext, aTargetRect, sSourceLayerHolder.get());
+
+ mrShared.maContextHolder.restoreState();
+
+ // cleanup
+ if (sSourceLayerHolder.get() != mrShared.maLayer.get())
+ {
+ CGLayerRelease(sSourceLayerHolder.get());
+ }
+
+ // mark the destination rectangle as updated
+ mrShared.refreshRect(nDstX, nDstY, nSrcWidth, nSrcHeight);
+}
+
+void AquaSalGraphics::SetVirDevGraphics(SalVirtualDevice* pVirDev, CGLayerHolder const & rLayer, CGContextRef xContext,
+ int nBitmapDepth)
+{
+ SAL_INFO( "vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext );
+
+ maShared.mbPrinter = false;
+ maShared.mbVirDev = true;
+
+ // set graphics properties
+ maShared.maLayer = rLayer;
+ maShared.maContextHolder.set(xContext);
+
+ maShared.mnBitmapDepth = nBitmapDepth;
+
+ maShared.mbForeignContext = xContext != NULL;
+
+ mpBackend->UpdateGeometryProvider(pVirDev);
+
+ // return early if the virdev is being destroyed
+ if (!xContext)
+ return;
+
+ // get new graphics properties
+ if (!maShared.maLayer.isSet())
+ {
+ maShared.mnWidth = CGBitmapContextGetWidth(maShared.maContextHolder.get());
+ maShared.mnHeight = CGBitmapContextGetHeight(maShared.maContextHolder.get());
+ }
+ else
+ {
+ const CGSize aSize = CGLayerGetSize(maShared.maLayer.get());
+ maShared.mnWidth = static_cast<int>(aSize.width);
+ maShared.mnHeight = static_cast<int>(aSize.height);
+ }
+
+ // prepare graphics for drawing
+ const CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGContextSetFillColorSpace(maShared.maContextHolder.get(), aCGColorSpace);
+ CGContextSetStrokeColorSpace(maShared.maContextHolder.get(), aCGColorSpace);
+
+ // re-enable XorEmulation for the new context
+ if (maShared.mpXorEmulation)
+ {
+ maShared.mpXorEmulation->SetTarget(maShared.mnWidth, maShared.mnHeight, maShared.mnBitmapDepth, maShared.maContextHolder.get(), maShared.maLayer.get());
+ if (maShared.mpXorEmulation->IsEnabled())
+ {
+ maShared.maContextHolder.set(maShared.mpXorEmulation->GetMaskContext());
+ }
+ }
+
+ // initialize stack of CGContext states
+ maShared.maContextHolder.saveState();
+ maShared.setState();
+}
+
+void XorEmulation::SetTarget( int nWidth, int nHeight, int nTargetDepth,
+ CGContextRef xTargetContext, CGLayerRef xTargetLayer )
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
+ " (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
+ " context=" << xTargetContext << " layer=" << xTargetLayer );
+
+ // prepare to replace old mask+temp context
+ if( m_xMaskContext )
+ {
+ // cleanup the mask context
+ CGContextRelease( m_xMaskContext );
+ delete[] m_pMaskBuffer;
+ m_xMaskContext = nullptr;
+ m_pMaskBuffer = nullptr;
+
+ // cleanup the temp context if needed
+ if( m_xTempContext )
+ {
+ CGContextRelease( m_xTempContext );
+ delete[] m_pTempBuffer;
+ m_xTempContext = nullptr;
+ m_pTempBuffer = nullptr;
+ }
+ }
+
+ // return early if there is nothing more to do
+ if( !xTargetContext )
+ {
+ return;
+ }
+ // retarget drawing operations to the XOR mask
+ m_xTargetLayer = xTargetLayer;
+ m_xTargetContext = xTargetContext;
+
+ // prepare creation of matching CGBitmaps
+ CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
+ int nBitDepth = nTargetDepth;
+ if( !nBitDepth )
+ {
+ nBitDepth = 32;
+ }
+ int nBytesPerRow = 4;
+ const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
+ if( nBitDepth <= 8 )
+ {
+ aCGColorSpace = GetSalData()->mxGraySpace;
+ aCGBmpInfo = kCGImageAlphaNone;
+ nBytesPerRow = 1;
+ }
+ nBytesPerRow *= nWidth;
+ m_nBufferLongs = (nHeight * nBytesPerRow + sizeof(sal_uLong)-1) / sizeof(sal_uLong);
+
+ // create a XorMask context
+ m_pMaskBuffer = new sal_uLong[ m_nBufferLongs ];
+ m_xMaskContext = CGBitmapContextCreate( m_pMaskBuffer,
+ nWidth, nHeight,
+ nBitsPerComponent, nBytesPerRow,
+ aCGColorSpace, aCGBmpInfo );
+ SAL_WARN_IF( !m_xMaskContext, "vcl.quartz", "mask context creation failed" );
+
+ // reset the XOR mask to black
+ memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) );
+
+ // a bitmap context will be needed for manual XORing
+ // create one unless the target context is a bitmap context
+ if( nTargetDepth )
+ {
+ m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData( m_xTargetContext ));
+ }
+ if( !m_pTempBuffer )
+ {
+ // create a bitmap context matching to the target context
+ m_pTempBuffer = new sal_uLong[ m_nBufferLongs ];
+ m_xTempContext = CGBitmapContextCreate( m_pTempBuffer,
+ nWidth, nHeight,
+ nBitsPerComponent, nBytesPerRow,
+ aCGColorSpace, aCGBmpInfo );
+ SAL_WARN_IF( !m_xTempContext, "vcl.quartz", "temp context creation failed" );
+ }
+
+ // initialize XOR mask context for drawing
+ CGContextSetFillColorSpace( m_xMaskContext, aCGColorSpace );
+ CGContextSetStrokeColorSpace( m_xMaskContext, aCGColorSpace );
+ CGContextSetShouldAntialias( m_xMaskContext, false );
+
+ // improve the XorMask's XOR emulation a little
+ // NOTE: currently only enabled for monochrome contexts
+ if( aCGColorSpace == GetSalData()->mxGraySpace )
+ {
+ CGContextSetBlendMode( m_xMaskContext, kCGBlendModeDifference );
+ }
+ // initialize the transformation matrix to the drawing target
+ const CGAffineTransform aCTM = CGContextGetCTM( xTargetContext );
+ CGContextConcatCTM( m_xMaskContext, aCTM );
+ if( m_xTempContext )
+ {
+ CGContextConcatCTM( m_xTempContext, aCTM );
+ }
+ // initialize the default XorMask graphics state
+ CGContextSaveGState( m_xMaskContext );
+}
+
+bool XorEmulation::UpdateTarget()
+{
+ SAL_INFO( "vcl.quartz", "XorEmulation::UpdateTarget() this=" << this );
+
+ if( !IsEnabled() )
+ {
+ return false;
+ }
+ // update the temp bitmap buffer if needed
+ if( m_xTempContext )
+ {
+ SAL_WARN_IF( m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
+ CGContextDrawLayerAtPoint( m_xTempContext, CGPointZero, m_xTargetLayer );
+ }
+ // do a manual XOR with the XorMask
+ // this approach suffices for simple color manipulations
+ // and also the complex-clipping-XOR-trick used in metafiles
+ const sal_uLong* pSrc = m_pMaskBuffer;
+ sal_uLong* pDst = m_pTempBuffer;
+ for( int i = m_nBufferLongs; --i >= 0;)
+ {
+ *(pDst++) ^= *(pSrc++);
+ }
+ // write back the XOR results to the target context
+ if( m_xTempContext )
+ {
+ CGImageRef xXorImage = CGBitmapContextCreateImage( m_xTempContext );
+ const int nWidth = static_cast<int>(CGImageGetWidth( xXorImage ));
+ const int nHeight = static_cast<int>(CGImageGetHeight( xXorImage ));
+ // TODO: update minimal changerect
+ const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
+ CGContextDrawImage( m_xTargetContext, aFullRect, xXorImage );
+ CGImageRelease( xXorImage );
+ }
+
+ // reset the XorMask to black again
+ // TODO: not needed for last update
+ memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) );
+
+ // TODO: return FALSE if target was not changed
+ return true;
+}
+
+/// From salvd.cxx
+
+void AquaSalVirtualDevice::Destroy()
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
+
+ if( mbForeignContext )
+ {
+ // Do not delete mxContext that we have received from outside VCL
+ maLayer.set(nullptr);
+ return;
+ }
+
+ if (maLayer.isSet())
+ {
+ if( mpGraphics )
+ {
+ mpGraphics->SetVirDevGraphics(this, nullptr, nullptr);
+ }
+ CGLayerRelease(maLayer.get());
+ maLayer.set(nullptr);
+ }
+
+ if (maBitmapContext.isSet())
+ {
+ void* pRawData = CGBitmapContextGetData(maBitmapContext.get());
+ std::free(pRawData);
+ CGContextRelease(maBitmapContext.get());
+ maBitmapContext.set(nullptr);
+ }
+}
+
+bool AquaSalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY )
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
+
+ if( mbForeignContext )
+ {
+ // Do not delete/resize mxContext that we have received from outside VCL
+ return true;
+ }
+
+ if (maLayer.isSet())
+ {
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ if( (nDX == aSize.width) && (nDY == aSize.height) )
+ {
+ // Yay, we do not have to do anything :)
+ return true;
+ }
+ }
+
+ Destroy();
+
+ mnWidth = nDX;
+ mnHeight = nDY;
+
+ // create a CGLayer matching to the intended virdev usage
+ CGContextHolder xCGContextHolder;
+ if( mnBitmapDepth && (mnBitmapDepth < 16) )
+ {
+ mnBitmapDepth = 8; // TODO: are 1bit vdevs worth it?
+ const int nBytesPerRow = (mnBitmapDepth * nDX + 7) / 8;
+
+ void* pRawData = std::malloc( nBytesPerRow * nDY );
+ maBitmapContext.set(CGBitmapContextCreate( pRawData, nDX, nDY,
+ mnBitmapDepth, nBytesPerRow,
+ GetSalData()->mxGraySpace, kCGImageAlphaNone));
+ xCGContextHolder = maBitmapContext;
+ }
+ else
+ {
+ if (!xCGContextHolder.isSet())
+ {
+ // assert(Application::IsBitmapRendering());
+ mnBitmapDepth = 32;
+
+ const int nBytesPerRow = (mnBitmapDepth * nDX) / 8;
+ void* pRawData = std::malloc( nBytesPerRow * nDY );
+ const int nFlags = kCGImageAlphaNoneSkipFirst | kCGImageByteOrder32Little;
+ maBitmapContext.set(CGBitmapContextCreate(pRawData, nDX, nDY, 8, nBytesPerRow,
+ GetSalData()->mxRGBSpace, nFlags));
+ xCGContextHolder = maBitmapContext;
+ }
+ }
+
+ SAL_WARN_IF(!xCGContextHolder.isSet(), "vcl.quartz", "No context");
+
+ const CGSize aNewSize = { static_cast<CGFloat>(nDX), static_cast<CGFloat>(nDY) };
+ maLayer.set(CGLayerCreateWithContext(xCGContextHolder.get(), aNewSize, nullptr));
+
+ if (maLayer.isSet() && mpGraphics)
+ {
+ // get the matching Quartz context
+ CGContextRef xDrawContext = CGLayerGetContext( maLayer.get() );
+
+ // Here we pass the CGLayerRef that the CGLayerHolder maLayer holds as the first parameter
+ // to SetVirDevGraphics(). That parameter is of type CGLayerHolder, so what we actually pass
+ // is an implicitly constructed *separate* CGLayerHolder. Is that what we want? No idea.
+ // Possibly we could pass just maLayer as such? But doing that does not fix tdf#138122.
+ mpGraphics->SetVirDevGraphics(this, maLayer.get(), xDrawContext, mnBitmapDepth);
+ }
+
+ return maLayer.isSet();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */