diff options
Diffstat (limited to 'vcl/source/gdi/embeddedfontshelper.cxx')
-rw-r--r-- | vcl/source/gdi/embeddedfontshelper.cxx | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/vcl/source/gdi/embeddedfontshelper.cxx b/vcl/source/gdi/embeddedfontshelper.cxx new file mode 100644 index 0000000000..a8a4f7e983 --- /dev/null +++ b/vcl/source/gdi/embeddedfontshelper.cxx @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <memory> +#include <config_folders.h> +#include <config_eot.h> + +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/embeddedfontshelper.hxx> +#include <com/sun/star/io/XInputStream.hpp> + +#include <font/PhysicalFontFaceCollection.hxx> +#include <font/PhysicalFontCollection.hxx> +#include <salgdi.hxx> +#include <sft.hxx> + + +#if ENABLE_EOT +extern "C" +{ +namespace libeot +{ +#include <libeot/libeot.h> +} // namespace libeot +} // extern "C" +#endif + +using namespace com::sun::star; +using namespace vcl; + +static void clearDir( const OUString& path ) +{ + osl::Directory dir( path ); + if( dir.reset() == osl::Directory::E_None ) + { + for(;;) + { + osl::DirectoryItem item; + if( dir.getNextItem( item ) != osl::Directory::E_None ) + break; + osl::FileStatus status( osl_FileStatus_Mask_FileURL ); + if( item.getFileStatus( status ) == osl::File::E_None ) + osl::File::remove( status.getFileURL()); + } + } +} + +void EmbeddedFontsHelper::clearTemporaryFontFiles() +{ + OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros( path ); + path += "/user/temp/embeddedfonts/"; + clearDir( path + "fromdocs/" ); + clearDir( path + "fromsystem/" ); +} + +bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName, + std::u16string_view extra, std::vector< unsigned char > const & key, bool eot ) +{ + OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra ); + osl::File file( fileUrl ); + switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write )) + { + case osl::File::E_None: + break; // ok + case osl::File::E_EXIST: + return true; // Assume it's already been added correctly. + default: + SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" ); + return false; + } + size_t keyPos = 0; + std::vector< char > fontData; + fontData.reserve( 1000000 ); + for(;;) + { + uno::Sequence< sal_Int8 > buffer; + sal_uInt64 read = stream->readBytes( buffer, 1024 ); + auto bufferRange = asNonConstRange(buffer); + for( sal_uInt64 pos = 0; + pos < read && keyPos < key.size(); + ++pos ) + bufferRange[ pos ] ^= key[ keyPos++ ]; + // if eot, don't write the file out yet, since we need to unpack it first. + if( !eot && read > 0 ) + { + sal_uInt64 writtenTotal = 0; + while( writtenTotal < read ) + { + sal_uInt64 written; + file.write( buffer.getConstArray(), read, written ); + writtenTotal += written; + } + } + fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read ); + if( read <= 0 ) + break; + } + bool sufficientFontRights(false); +#if ENABLE_EOT + if( eot ) + { + unsigned uncompressedFontSize = 0; + unsigned char *nakedPointerToUncompressedFont = nullptr; + libeot::EOTMetadata eotMetadata; + libeot::EOTError uncompressError = + libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize ); + std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer ); + if( uncompressError != libeot::EOT_SUCCESS ) + { + SAL_WARN( "vcl.fonts", "Failed to uncompress font" ); + osl::File::remove( fileUrl ); + return false; + } + sal_uInt64 writtenTotal = 0; + while( writtenTotal < uncompressedFontSize ) + { + sal_uInt64 written; + if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None ) + { + SAL_WARN( "vcl.fonts", "Error writing temporary font file" ); + osl::File::remove( fileUrl ); + return false; + } + writtenTotal += written; + } + sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata ); + libeot::EOTfreeMetadata( &eotMetadata ); + } +#endif + + if( file.close() != osl::File::E_None ) + { + SAL_WARN( "vcl.fonts", "Writing temporary font file failed" ); + osl::File::remove( fileUrl ); + return false; + } + if( !eot ) + { + sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed); + } + if( !sufficientFontRights ) + { + // It would be actually better to open the document in read-only mode in this case, + // warn the user about this, and provide a button to drop the font(s) in order + // to switch to editing. + SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" ); + osl::File::remove( fileUrl ); + return false; + } + m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl)); + return true; +} + +namespace +{ + struct UpdateFontsGuard + { + UpdateFontsGuard() + { + OutputDevice::ImplClearAllFontData(true); + } + + ~UpdateFontsGuard() + { + OutputDevice::ImplRefreshAllFontData(true); + } + }; +} + +void EmbeddedFontsHelper::activateFonts() +{ + if (m_aAccumulatedFonts.empty()) + return; + UpdateFontsGuard aUpdateFontsGuard; + for (const auto& rEntry : m_aAccumulatedFonts) + EmbeddedFontsHelper::activateFont(rEntry.first, rEntry.second); + m_aAccumulatedFonts.clear(); +} + +OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, std::u16string_view extra ) +{ + OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros( path ); + path += "/user/temp/embeddedfonts/fromdocs/"; + osl::Directory::createPath( path ); + OUString filename = fontName; + static int uniqueCounter = 0; + if( extra == u"?" ) + filename += OUString::number( uniqueCounter++ ); + else + filename += extra; + filename += ".ttf"; // TODO is it always ttf? + return path + filename; +} + +void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl ) +{ + OutputDevice *pDevice = Application::GetDefaultDevice(); + pDevice->AddTempDevFont(fileUrl, fontName); +} + +// Check if it's (legally) allowed to embed the font file into a document +// (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears +// to have a different meaning (guessing from code, IsSubsettable() might +// possibly mean it's ttf, while IsEmbeddable() might mean it's type1). +// So just try to open the data as ttf and see. +bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, tools::Long size, FontRights rights ) +{ + TrueTypeFont* font; + if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SFErrCodes::Ok ) + { + TTGlobalFontInfo info; + GetTTGlobalFontInfo( font, &info ); + CloseTTFont( font ); + // https://www.microsoft.com/typography/otspec/os2.htm#fst + int copyright = info.typeFlags; + switch( rights ) + { + case FontRights::ViewingAllowed: + // Embedding not restricted completely. + return ( copyright & 0x02 ) != 0x02; + case FontRights::EditingAllowed: + // Font is installable or editable. + return copyright == 0 || ( copyright & 0x08 ); + } + } + return true; // no known restriction +} + +OUString EmbeddedFontsHelper::fontFileUrl( std::u16string_view familyName, FontFamily family, FontItalic italic, + FontWeight weight, FontPitch pitch, FontRights rights ) +{ + OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros( path ); + path += "/user/temp/embeddedfonts/fromsystem/"; + osl::Directory::createPath( path ); + OUString filename = OUString::Concat(familyName) + "_" + OUString::number( family ) + "_" + OUString::number( italic ) + + "_" + OUString::number( weight ) + "_" + OUString::number( pitch ) + + ".ttf"; // TODO is it always ttf? + OUString url = path + filename; + if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists() + { + // File with contents of the font file already exists, assume it's been created by a previous call. + return url; + } + bool ok = false; + SalGraphics* graphics = Application::GetDefaultDevice()->GetGraphics(); + vcl::font::PhysicalFontCollection fonts; + graphics->GetDevFontList( &fonts ); + std::unique_ptr< vcl::font::PhysicalFontFaceCollection > fontInfo( fonts.GetFontFaceCollection()); + vcl::font::PhysicalFontFace* selected = nullptr; + + // Maybe we don't find the perfect match for the font. E.G. we have fonts with the same family name + // but not same bold or italic etc... + // In this case we add all the fonts having the family name of the used font: + // - we store all these fonts in familyNameFonts during loop + // - if we haven't found the perfect match we store all fonts in familyNameFonts + typedef std::vector<vcl::font::PhysicalFontFace*> FontList; + FontList familyNameFonts; + + for( int i = 0; + i < fontInfo->Count(); + ++i ) + { + vcl::font::PhysicalFontFace* f = fontInfo->Get( i ); + if( f->GetFamilyName() == familyName ) + { + // Ignore comparing text encodings, at least for now. They cannot be trivially compared + // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding, + // and just having a unicode font doesn't say what glyphs it actually contains). + // It is possible that it still may be needed to do at least some checks here + // for some encodings (can one font have more font files for more encodings?). + if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family ) + && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic ) + && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight ) + && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch )) + { // Exact match, return it immediately. + selected = f; + break; + } + if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family ) + && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic ) + && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight ) + && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch )) + { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one. + selected = f; + } + // adding "not perfect match" to familyNameFonts vector + familyNameFonts.push_back(f); + + } + } + + // if we have found a perfect match we will add only "selected", otherwise all familyNameFonts + FontList fontsToAdd = (selected ? FontList(1, selected) : std::move(familyNameFonts)); + + for (vcl::font::PhysicalFontFace* f : fontsToAdd) + { + if (!selected) { // recalculate file not for "not perfect match" + filename = OUString::Concat(familyName) + "_" + OUString::number(f->GetFamilyType()) + "_" + + OUString::number(f->GetItalic()) + "_" + OUString::number(f->GetWeight()) + "_" + + OUString::number(f->GetPitch()) + ".ttf"; // TODO is it always ttf? + url = path + filename; + if (osl::File(url).open(osl_File_OpenFlag_Read) == osl::File::E_None) // = exists() + { + // File with contents of the font file already exists, assume it's been created by a previous call. + continue; + } + } + auto aFontData(f->GetRawFontData(0)); + if (!aFontData.empty()) + { + auto data = aFontData.data(); + auto size = aFontData.size(); + if( sufficientTTFRights( data, size, rights )) + { + osl::File file( url ); + if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None ) + { + sal_uInt64 written = 0; + sal_uInt64 totalSize = size; + bool error = false; + while( written < totalSize && !error) + { + sal_uInt64 nowWritten; + switch( file.write( data + written, size - written, nowWritten )) + { + case osl::File::E_None: + written += nowWritten; + break; + case osl::File::E_AGAIN: + case osl::File::E_INTR: + break; + default: + error = true; + break; + } + } + file.close(); + if( error ) + osl::File::remove( url ); + else + ok = true; + } + } + } + } + return ok ? url : ""; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ |