/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ENABLE_EOT extern "C" { namespace libeot { #include } // 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 = u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"_ustr; 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, bool bSubsetted ) { 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(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize ); std::shared_ptr 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; } if (bSubsetted) { TrueTypeFont* font; sal_uInt32 nGlyphs = 0; if (OpenTTFontBuffer(fontData.data(), fontData.size(), 0, &font) == SFErrCodes::Ok) { sal_uInt32 nGlyphCount = font->glyphCount(); for (sal_uInt32 i = 0; i < nGlyphCount; ++i) { sal_uInt32 nOffset = font->glyphOffset(i); sal_uInt32 nNextOffset = font->glyphOffset(i + 1); if (nOffset == nNextOffset) { // GetTTGlyphComponents() says this is an empty glyph, ignore it. continue; } ++nGlyphs; } CloseTTFont(font); } // Check if it has reasonable amount of glyphs, set the limit to the number of glyphs in the // English alphabet (not differentiating lowercase and uppercase). if (nGlyphs < 26) { SAL_INFO("vcl.fonts", "Ignoring embedded font that only provides " << nGlyphs << " non-empty glyphs"); 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 filename = fontName; static int uniqueCounter = 0; if( extra == u"?" ) filename += OUString::number( uniqueCounter++ ); else filename += extra; filename += ".ttf"; // TODO is it always ttf? if (!::comphelper::OStorageHelper::IsValidZipEntryFileName(filename, false)) { SAL_WARN( "vcl.fonts", "Cannot use filename: " << filename << " for temporary font"); filename = "font" + OUString::number(uniqueCounter++) + ".ttf"; } OUString path = u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"_ustr; rtl::Bootstrap::expandMacros( path ); path += "/user/temp/embeddedfonts/fromdocs/"; osl::Directory::createPath( path ); 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 = u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"_ustr; 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 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 : u""_ustr; } /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */