diff options
Diffstat (limited to '')
-rw-r--r-- | vcl/ios/DataFlavorMapping.cxx | 576 | ||||
-rw-r--r-- | vcl/ios/DataFlavorMapping.hxx | 124 | ||||
-rw-r--r-- | vcl/ios/HtmlFmtFlt.cxx | 172 | ||||
-rw-r--r-- | vcl/ios/HtmlFmtFlt.hxx | 38 | ||||
-rw-r--r-- | vcl/ios/clipboard.cxx | 183 | ||||
-rw-r--r-- | vcl/ios/clipboard.hxx | 107 | ||||
-rw-r--r-- | vcl/ios/dummies.cxx | 120 | ||||
-rw-r--r-- | vcl/ios/iOSTransferable.cxx | 174 | ||||
-rw-r--r-- | vcl/ios/iOSTransferable.hxx | 69 | ||||
-rw-r--r-- | vcl/ios/iosinst.cxx | 165 | ||||
-rw-r--r-- | vcl/ios/salios.cxx | 584 |
11 files changed, 2312 insertions, 0 deletions
diff --git a/vcl/ios/DataFlavorMapping.cxx b/vcl/ios/DataFlavorMapping.cxx new file mode 100644 index 000000000..05abf2b8a --- /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 ~SwIndexReg, 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 000000000..dd115575f --- /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 000000000..4f90ced3b --- /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 000000000..6a2cd6f64 --- /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 000000000..46dfa92bf --- /dev/null +++ b/vcl/ios/clipboard.cxx @@ -0,0 +1,183 @@ +/* -*- 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 "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(static_cast<OWeakObject*>(this), + 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 css::uno::Reference<css::uno::XInterface>( + static_cast<cppu::OWeakObject*>(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 000000000..086840912 --- /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 000000000..9e03f598f --- /dev/null +++ b/vcl/ios/dummies.cxx @@ -0,0 +1,120 @@ +/* -*- 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 "salprn.hxx" +#include "quartz/salgdi.h" +#include "headless/svpinst.hxx" +#include "unx/fontmanager.hxx" +#include "unx/gendata.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::jobStartedPrinterUpdate() +{ +} + +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 000000000..ece771e7f --- /dev/null +++ b/vcl/ios/iOSTransferable.cxx @@ -0,0 +1,174 @@ +/* -*- 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]; + 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 000000000..91f4e440e --- /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 000000000..a9fa27e5e --- /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 "headless/svpdummies.hxx" +#include <osx/saldata.hxx> +#include "quartz/utils.h" +#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( tools::Rectangle& rRect ) +{ + rRect = tools::Rectangle( Point( 0, 0 ), + Size( 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( tools::Rectangle& 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 ) +{ + pParent = NULL; + return new IosSalFrame( this, NULL, nStyle ); +} + +SalFrame *IosSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) +{ + return new IosSalFrame( this, pParent, nStyle ); +} + +SalData::SalData() : + mpFontList( 0 ), + mxRGBSpace( CGColorSpaceCreateDeviceRGB() ), + mxGraySpace( CGColorSpaceCreateDeviceGray() ) +{ +} + +SalData::~SalData() +{ + CGColorSpaceRelease(mxRGBSpace); + CGColorSpaceRelease(mxGraySpace); +} + +void SalData::ensureThreadAutoreleasePool() {} + +extern "C" SalInstance *create_SalInstance() +{ + IosSalInstance* pInstance = new IosSalInstance( std::make_unique<SvpSalYieldMutex>() ); + new SvpSalData(pInstance); + 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 000000000..52e94b109 --- /dev/null +++ b/vcl/ios/salios.cxx @@ -0,0 +1,584 @@ +/* -*- 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 <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: */ |