Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / vcl / source / gdi / embeddedfontshelper.cxx
blobd370c381b1f4383d7890c3cc52129e79e15dbad8
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 <vcl/svapp.hxx>
17 #include <vcl/embeddedfontshelper.hxx>
19 #include "fontsubset.hxx"
20 #include "outdev.h"
21 #include "PhysicalFontCollection.hxx"
22 #include "salgdi.hxx"
23 #include "sft.hxx"
26 #if ENABLE_EOT
27 extern "C"
29 namespace libeot
31 #include <libeot/libeot.h>
32 } // namespace libeot
33 } // extern "C"
34 #endif
36 using namespace com::sun::star;
37 using namespace vcl;
39 static void clearDir( const OUString& path )
41 osl::Directory dir( path );
42 if( dir.reset() == osl::Directory::E_None )
44 for(;;)
46 osl::DirectoryItem item;
47 if( dir.getNextItem( item ) != osl::Directory::E_None )
48 break;
49 osl::FileStatus status( osl_FileStatus_Mask_FileURL );
50 if( item.getFileStatus( status ) == osl::File::E_None )
51 osl::File::remove( status.getFileURL());
56 void EmbeddedFontsHelper::clearTemporaryFontFiles()
58 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
59 rtl::Bootstrap::expandMacros( path );
60 path += "/user/temp/embeddedfonts/";
61 clearDir( path + "fromdocs/" );
62 clearDir( path + "fromsystem/" );
65 bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName,
66 const char* extra, std::vector< unsigned char > key, bool eot )
68 OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra );
69 osl::File file( fileUrl );
70 switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write ))
72 case osl::File::E_None:
73 break; // ok
74 case osl::File::E_EXIST:
75 return true; // Assume it's already been added correctly.
76 default:
77 SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" );
78 return false;
80 size_t keyPos = 0;
81 std::vector< char > fontData;
82 fontData.reserve( 1000000 );
83 for(;;)
85 uno::Sequence< sal_Int8 > buffer;
86 sal_uInt64 read = stream->readBytes( buffer, 1024 );
87 for( sal_uInt64 pos = 0;
88 pos < read && keyPos < key.size();
89 ++pos )
90 buffer[ pos ] ^= key[ keyPos++ ];
91 // if eot, don't write the file out yet, since we need to unpack it first.
92 if( !eot && read > 0 )
94 sal_uInt64 writtenTotal = 0;
95 while( writtenTotal < read )
97 sal_uInt64 written;
98 file.write( buffer.getConstArray(), read, written );
99 writtenTotal += written;
102 fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
103 if( read <= 0 )
104 break;
106 bool sufficientFontRights(false);
107 #if ENABLE_EOT
108 if( eot )
110 unsigned uncompressedFontSize = 0;
111 unsigned char *nakedPointerToUncompressedFont = nullptr;
112 libeot::EOTMetadata eotMetadata;
113 libeot::EOTError uncompressError =
114 libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(&fontData[0]), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize );
115 std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer );
116 if( uncompressError != libeot::EOT_SUCCESS )
118 SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
119 osl::File::remove( fileUrl );
120 return false;
122 sal_uInt64 writtenTotal = 0;
123 while( writtenTotal < uncompressedFontSize )
125 sal_uInt64 written;
126 if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None )
128 SAL_WARN( "vcl.fonts", "Error writing temporary font file" );
129 osl::File::remove( fileUrl );
130 return false;
132 writtenTotal += written;
134 sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata );
135 libeot::EOTfreeMetadata( &eotMetadata );
137 #endif
139 if( file.close() != osl::File::E_None )
141 SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
142 osl::File::remove( fileUrl );
143 return false;
145 if( !eot )
147 sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed);
149 if( !sufficientFontRights )
151 // It would be actually better to open the document in read-only mode in this case,
152 // warn the user about this, and provide a button to drop the font(s) in order
153 // to switch to editing.
154 SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" );
155 osl::File::remove( fileUrl );
156 return false;
158 EmbeddedFontsHelper::activateFont( fontName, fileUrl );
159 return true;
162 OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, const char* extra )
164 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
165 rtl::Bootstrap::expandMacros( path );
166 path += "/user/temp/embeddedfonts/fromdocs/";
167 osl::Directory::createPath( path );
168 OUString filename = fontName;
169 static int uniqueCounter = 0;
170 if( strcmp( extra, "?" ) == 0 )
171 filename += OUString::number( uniqueCounter++ );
172 else
173 filename += OStringToOUString( extra, RTL_TEXTENCODING_ASCII_US );
174 filename += ".ttf"; // TODO is it always ttf?
175 return path + filename;
178 void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl )
180 OutputDevice *pDevice = Application::GetDefaultDevice();
181 pDevice->AddTempDevFont(fileUrl, fontName);
184 // Check if it's (legally) allowed to embed the font file into a document
185 // (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
186 // to have a different meaning (guessing from code, IsSubsettable() might
187 // possibly mean it's ttf, while IsEmbeddable() might mean it's type1).
188 // So just try to open the data as ttf and see.
189 bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, long size, FontRights rights )
191 TrueTypeFont* font;
192 if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SF_OK )
194 TTGlobalFontInfo info;
195 GetTTGlobalFontInfo( font, &info );
196 CloseTTFont( font );
197 // http://www.microsoft.com/typography/tt/ttf_spec/ttch02.doc
198 int copyright = info.typeFlags & TYPEFLAG_COPYRIGHT_MASK;
199 switch( rights )
201 case FontRights::ViewingAllowed:
202 // Embedding not restricted completely.
203 return ( copyright & 0x02 ) != 0x02;
204 case FontRights::EditingAllowed:
205 // Font is installable or editable.
206 return copyright == 0 || ( copyright & 0x08 );
209 return true; // no known restriction
212 OUString EmbeddedFontsHelper::fontFileUrl( const OUString& familyName, FontFamily family, FontItalic italic,
213 FontWeight weight, FontPitch pitch, FontRights rights )
215 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
216 rtl::Bootstrap::expandMacros( path );
217 path += "/user/temp/embeddedfonts/fromsystem/";
218 osl::Directory::createPath( path );
219 OUString filename = familyName + "_" + OUString::number( family ) + "_" + OUString::number( italic )
220 + "_" + OUString::number( weight ) + "_" + OUString::number( pitch );
221 filename += ".ttf"; // TODO is it always ttf?
222 OUString url = path + filename;
223 if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists()
225 // File with contents of the font file already exists, assume it's been created by a previous call.
226 return url;
228 bool ok = false;
229 SalGraphics* graphics = Application::GetDefaultDevice()->GetGraphics();
230 PhysicalFontCollection fonts;
231 graphics->GetDevFontList( &fonts );
232 std::unique_ptr< ImplDeviceFontList > fontInfo( fonts.GetDeviceFontList());
233 PhysicalFontFace* selected = nullptr;
234 for( int i = 0;
235 i < fontInfo->Count();
236 ++i )
238 PhysicalFontFace* f = fontInfo->Get( i );
239 if( f->GetFamilyName() == familyName )
241 // Ignore comparing text encodings, at least for now. They cannot be trivially compared
242 // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding,
243 // and just having a unicode font doesn't say what glyphs it actually contains).
244 // It is possible that it still may be needed to do at least some checks here
245 // for some encodings (can one font have more font files for more encodings?).
246 if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
247 && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
248 && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
249 && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
250 { // Exact match, return it immediately.
251 selected = f;
252 break;
254 if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
255 && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
256 && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
257 && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
258 { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one.
259 selected = f;
263 if( selected != nullptr )
265 long size;
266 if (const void* data = graphics->GetEmbedFontData(selected, &size))
268 if( sufficientTTFRights( data, size, rights ))
270 osl::File file( url );
271 if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
273 sal_uInt64 written = 0;
274 sal_uInt64 totalSize = size;
275 bool error = false;
276 while( written < totalSize && !error)
278 sal_uInt64 nowWritten;
279 switch( file.write( static_cast< const char* >( data ) + written, size - written, nowWritten ))
281 case osl::File::E_None:
282 written += nowWritten;
283 break;
284 case osl::File::E_AGAIN:
285 case osl::File::E_INTR:
286 break;
287 default:
288 error = true;
289 break;
292 file.close();
293 if( error )
294 osl::File::remove( url );
295 else
296 ok = true;
299 graphics->FreeEmbedFontData( data, size );
302 return ok ? url : "";
305 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */