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 
clearDir(const OUString & path)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 
clearTemporaryFontFiles()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 
addEmbeddedFont(const uno::Reference<io::XInputStream> & stream,const OUString & fontName,const char * extra,std::vector<unsigned char> const & key,bool eot)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     {
UpdateFontsGuard__anon54a1d1390111::UpdateFontsGuard167         UpdateFontsGuard()
168         {
169             OutputDevice::ImplClearAllFontData(true);
170         }
171 
~UpdateFontsGuard__anon54a1d1390111::UpdateFontsGuard172         ~UpdateFontsGuard()
173         {
174             OutputDevice::ImplRefreshAllFontData(true);
175         }
176     };
177 }
178 
activateFonts()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 
fileUrlForTemporaryFont(const OUString & fontName,const char * extra)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 
activateFont(const OUString & fontName,const OUString & fileUrl)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.
sufficientTTFRights(const void * data,tools::Long size,FontRights rights)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 
fontFileUrl(std::u16string_view familyName,FontFamily family,FontItalic italic,FontWeight weight,FontPitch pitch,FontRights rights)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