1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 */ 9 10 #include <memory> 11 #include <config_folders.h> 12 #include <config_eot.h> 13 14 #include <osl/file.hxx> 15 #include <rtl/bootstrap.hxx> 16 #include <sal/log.hxx> 17 #include <vcl/svapp.hxx> 18 #include <vcl/embeddedfontshelper.hxx> 19 #include <com/sun/star/io/XInputStream.hpp> 20 21 #include <outdev.h> 22 #include <PhysicalFontCollection.hxx> 23 #include <salgdi.hxx> 24 #include <sft.hxx> 25 26 27 #if ENABLE_EOT 28 extern "C" 29 { 30 namespace libeot 31 { 32 #include <libeot/libeot.h> 33 } // namespace libeot 34 } // extern "C" 35 #endif 36 37 using namespace com::sun::star; 38 using namespace vcl; 39 40 static void clearDir( const OUString& path ) 41 { 42 osl::Directory dir( path ); 43 if( dir.reset() == osl::Directory::E_None ) 44 { 45 for(;;) 46 { 47 osl::DirectoryItem item; 48 if( dir.getNextItem( item ) != osl::Directory::E_None ) 49 break; 50 osl::FileStatus status( osl_FileStatus_Mask_FileURL ); 51 if( item.getFileStatus( status ) == osl::File::E_None ) 52 osl::File::remove( status.getFileURL()); 53 } 54 } 55 } 56 57 void EmbeddedFontsHelper::clearTemporaryFontFiles() 58 { 59 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; 60 rtl::Bootstrap::expandMacros( path ); 61 path += "/user/temp/embeddedfonts/"; 62 clearDir( path + "fromdocs/" ); 63 clearDir( path + "fromsystem/" ); 64 } 65 66 bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName, 67 const char* extra, std::vector< unsigned char > const & key, bool eot ) 68 { 69 OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra ); 70 osl::File file( fileUrl ); 71 switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write )) 72 { 73 case osl::File::E_None: 74 break; // ok 75 case osl::File::E_EXIST: 76 return true; // Assume it's already been added correctly. 77 default: 78 SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" ); 79 return false; 80 } 81 size_t keyPos = 0; 82 std::vector< char > fontData; 83 fontData.reserve( 1000000 ); 84 for(;;) 85 { 86 uno::Sequence< sal_Int8 > buffer; 87 sal_uInt64 read = stream->readBytes( buffer, 1024 ); 88 for( sal_uInt64 pos = 0; 89 pos < read && keyPos < key.size(); 90 ++pos ) 91 buffer[ pos ] ^= key[ keyPos++ ]; 92 // if eot, don't write the file out yet, since we need to unpack it first. 93 if( !eot && read > 0 ) 94 { 95 sal_uInt64 writtenTotal = 0; 96 while( writtenTotal < read ) 97 { 98 sal_uInt64 written; 99 file.write( buffer.getConstArray(), read, written ); 100 writtenTotal += written; 101 } 102 } 103 fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read ); 104 if( read <= 0 ) 105 break; 106 } 107 bool sufficientFontRights(false); 108 #if ENABLE_EOT 109 if( eot ) 110 { 111 unsigned uncompressedFontSize = 0; 112 unsigned char *nakedPointerToUncompressedFont = nullptr; 113 libeot::EOTMetadata eotMetadata; 114 libeot::EOTError uncompressError = 115 libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize ); 116 std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer ); 117 if( uncompressError != libeot::EOT_SUCCESS ) 118 { 119 SAL_WARN( "vcl.fonts", "Failed to uncompress font" ); 120 osl::File::remove( fileUrl ); 121 return false; 122 } 123 sal_uInt64 writtenTotal = 0; 124 while( writtenTotal < uncompressedFontSize ) 125 { 126 sal_uInt64 written; 127 if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None ) 128 { 129 SAL_WARN( "vcl.fonts", "Error writing temporary font file" ); 130 osl::File::remove( fileUrl ); 131 return false; 132 } 133 writtenTotal += written; 134 } 135 sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata ); 136 libeot::EOTfreeMetadata( &eotMetadata ); 137 } 138 #endif 139 140 if( file.close() != osl::File::E_None ) 141 { 142 SAL_WARN( "vcl.fonts", "Writing temporary font file failed" ); 143 osl::File::remove( fileUrl ); 144 return false; 145 } 146 if( !eot ) 147 { 148 sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed); 149 } 150 if( !sufficientFontRights ) 151 { 152 // It would be actually better to open the document in read-only mode in this case, 153 // warn the user about this, and provide a button to drop the font(s) in order 154 // to switch to editing. 155 SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" ); 156 osl::File::remove( fileUrl ); 157 return false; 158 } 159 m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl)); 160 return true; 161 } 162 163 namespace 164 { 165 struct UpdateFontsGuard 166 { 167 UpdateFontsGuard() 168 { 169 OutputDevice::ImplClearAllFontData(true); 170 } 171 172 ~UpdateFontsGuard() 173 { 174 OutputDevice::ImplRefreshAllFontData(true); 175 } 176 }; 177 } 178 179 void EmbeddedFontsHelper::activateFonts() 180 { 181 if (m_aAccumulatedFonts.empty()) 182 return; 183 UpdateFontsGuard aUpdateFontsGuard; 184 for (const auto& rEntry : m_aAccumulatedFonts) 185 EmbeddedFontsHelper::activateFont(rEntry.first, rEntry.second); 186 m_aAccumulatedFonts.clear(); 187 } 188 189 OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, const char* extra ) 190 { 191 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; 192 rtl::Bootstrap::expandMacros( path ); 193 path += "/user/temp/embeddedfonts/fromdocs/"; 194 osl::Directory::createPath( path ); 195 OUString filename = fontName; 196 static int uniqueCounter = 0; 197 if( strcmp( extra, "?" ) == 0 ) 198 filename += OUString::number( uniqueCounter++ ); 199 else 200 filename += OStringToOUString( extra, RTL_TEXTENCODING_ASCII_US ); 201 filename += ".ttf"; // TODO is it always ttf? 202 return path + filename; 203 } 204 205 void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl ) 206 { 207 OutputDevice *pDevice = Application::GetDefaultDevice(); 208 pDevice->AddTempDevFont(fileUrl, fontName); 209 } 210 211 // Check if it's (legally) allowed to embed the font file into a document 212 // (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears 213 // to have a different meaning (guessing from code, IsSubsettable() might 214 // possibly mean it's ttf, while IsEmbeddable() might mean it's type1). 215 // So just try to open the data as ttf and see. 216 bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, tools::Long size, FontRights rights ) 217 { 218 TrueTypeFont* font; 219 if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SFErrCodes::Ok ) 220 { 221 TTGlobalFontInfo info; 222 GetTTGlobalFontInfo( font, &info ); 223 CloseTTFont( font ); 224 // https://www.microsoft.com/typography/otspec/os2.htm#fst 225 int copyright = info.typeFlags; 226 switch( rights ) 227 { 228 case FontRights::ViewingAllowed: 229 // Embedding not restricted completely. 230 return ( copyright & 0x02 ) != 0x02; 231 case FontRights::EditingAllowed: 232 // Font is installable or editable. 233 return copyright == 0 || ( copyright & 0x08 ); 234 } 235 } 236 return true; // no known restriction 237 } 238 239 OUString EmbeddedFontsHelper::fontFileUrl( std::u16string_view familyName, FontFamily family, FontItalic italic, 240 FontWeight weight, FontPitch pitch, FontRights rights ) 241 { 242 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; 243 rtl::Bootstrap::expandMacros( path ); 244 path += "/user/temp/embeddedfonts/fromsystem/"; 245 osl::Directory::createPath( path ); 246 OUString filename = OUString::Concat(familyName) + "_" + OUString::number( family ) + "_" + OUString::number( italic ) 247 + "_" + OUString::number( weight ) + "_" + OUString::number( pitch ) 248 + ".ttf"; // TODO is it always ttf? 249 OUString url = path + filename; 250 if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists() 251 { 252 // File with contents of the font file already exists, assume it's been created by a previous call. 253 return url; 254 } 255 bool ok = false; 256 SalGraphics* graphics = Application::GetDefaultDevice()->GetGraphics(); 257 PhysicalFontCollection fonts; 258 graphics->GetDevFontList( &fonts ); 259 std::unique_ptr< ImplDeviceFontList > fontInfo( fonts.GetDeviceFontList()); 260 PhysicalFontFace* selected = nullptr; 261 for( int i = 0; 262 i < fontInfo->Count(); 263 ++i ) 264 { 265 PhysicalFontFace* f = fontInfo->Get( i ); 266 if( f->GetFamilyName() == familyName ) 267 { 268 // Ignore comparing text encodings, at least for now. They cannot be trivially compared 269 // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding, 270 // and just having a unicode font doesn't say what glyphs it actually contains). 271 // It is possible that it still may be needed to do at least some checks here 272 // for some encodings (can one font have more font files for more encodings?). 273 if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family ) 274 && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic ) 275 && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight ) 276 && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch )) 277 { // Exact match, return it immediately. 278 selected = f; 279 break; 280 } 281 if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family ) 282 && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic ) 283 && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight ) 284 && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch )) 285 { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one. 286 selected = f; 287 } 288 } 289 } 290 if( selected != nullptr ) 291 { 292 tools::Long size; 293 if (const void* data = graphics->GetEmbedFontData(selected, &size)) 294 { 295 if( sufficientTTFRights( data, size, rights )) 296 { 297 osl::File file( url ); 298 if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None ) 299 { 300 sal_uInt64 written = 0; 301 sal_uInt64 totalSize = size; 302 bool error = false; 303 while( written < totalSize && !error) 304 { 305 sal_uInt64 nowWritten; 306 switch( file.write( static_cast< const char* >( data ) + written, size - written, nowWritten )) 307 { 308 case osl::File::E_None: 309 written += nowWritten; 310 break; 311 case osl::File::E_AGAIN: 312 case osl::File::E_INTR: 313 break; 314 default: 315 error = true; 316 break; 317 } 318 } 319 file.close(); 320 if( error ) 321 osl::File::remove( url ); 322 else 323 ok = true; 324 } 325 } 326 graphics->FreeEmbedFontData( data, size ); 327 } 328 } 329 return ok ? url : ""; 330 } 331 332 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 333
