bump product version to 7.2.5.1
[LibreOffice.git] / vcl / source / gdi / embeddedfontshelper.cxx
blob7e242d640837dae23168a50c4ad5ab9e759b6a75
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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 */
10 #include <memory>
11 #include <config_folders.h>
12 #include <config_eot.h>
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>
21 #include <outdev.h>
22 #include <PhysicalFontCollection.hxx>
23 #include <salgdi.hxx>
24 #include <sft.hxx>
27 #if ENABLE_EOT
28 extern "C"
30 namespace libeot
32 #include <libeot/libeot.h>
33 } // namespace libeot
34 } // extern "C"
35 #endif
37 using namespace com::sun::star;
38 using namespace vcl;
40 static void clearDir( const OUString& path )
42 osl::Directory dir( path );
43 if( dir.reset() == osl::Directory::E_None )
45 for(;;)
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());
57 void EmbeddedFontsHelper::clearTemporaryFontFiles()
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/" );
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 )
69 OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra );
70 osl::File file( fileUrl );
71 switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write ))
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;
81 size_t keyPos = 0;
82 std::vector< char > fontData;
83 fontData.reserve( 1000000 );
84 for(;;)
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 )
95 sal_uInt64 writtenTotal = 0;
96 while( writtenTotal < read )
98 sal_uInt64 written;
99 file.write( buffer.getConstArray(), read, written );
100 writtenTotal += written;
103 fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
104 if( read <= 0 )
105 break;
107 bool sufficientFontRights(false);
108 #if ENABLE_EOT
109 if( eot )
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 )
119 SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
120 osl::File::remove( fileUrl );
121 return false;
123 sal_uInt64 writtenTotal = 0;
124 while( writtenTotal < uncompressedFontSize )
126 sal_uInt64 written;
127 if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None )
129 SAL_WARN( "vcl.fonts", "Error writing temporary font file" );
130 osl::File::remove( fileUrl );
131 return false;
133 writtenTotal += written;
135 sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata );
136 libeot::EOTfreeMetadata( &eotMetadata );
138 #endif
140 if( file.close() != osl::File::E_None )
142 SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
143 osl::File::remove( fileUrl );
144 return false;
146 if( !eot )
148 sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed);
150 if( !sufficientFontRights )
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;
159 m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl));
160 return true;
163 namespace
165 struct UpdateFontsGuard
167 UpdateFontsGuard()
169 OutputDevice::ImplClearAllFontData(true);
172 ~UpdateFontsGuard()
174 OutputDevice::ImplRefreshAllFontData(true);
179 void EmbeddedFontsHelper::activateFonts()
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();
189 OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, const char* extra )
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;
205 void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl )
207 OutputDevice *pDevice = Application::GetDefaultDevice();
208 pDevice->AddTempDevFont(fileUrl, fontName);
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 )
218 TrueTypeFont* font;
219 if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SFErrCodes::Ok )
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 )
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 );
236 return true; // no known restriction
239 OUString EmbeddedFontsHelper::fontFileUrl( std::u16string_view familyName, FontFamily family, FontItalic italic,
240 FontWeight weight, FontPitch pitch, FontRights rights )
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()
252 // File with contents of the font file already exists, assume it's been created by a previous call.
253 return url;
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 )
265 PhysicalFontFace* f = fontInfo->Get( i );
266 if( f->GetFamilyName() == familyName )
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;
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;
290 if( selected != nullptr )
292 tools::Long size;
293 if (const void* data = graphics->GetEmbedFontData(selected, &size))
295 if( sufficientTTFRights( data, size, rights ))
297 osl::File file( url );
298 if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
300 sal_uInt64 written = 0;
301 sal_uInt64 totalSize = size;
302 bool error = false;
303 while( written < totalSize && !error)
305 sal_uInt64 nowWritten;
306 switch( file.write( static_cast< const char* >( data ) + written, size - written, nowWritten ))
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;
319 file.close();
320 if( error )
321 osl::File::remove( url );
322 else
323 ok = true;
326 graphics->FreeEmbedFontData( data, size );
329 return ok ? url : "";
332 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */