Line data Source code
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 <config_folders.h>
11 : #include <config_eot.h>
12 :
13 : #include <osl/file.hxx>
14 : #include <rtl/bootstrap.hxx>
15 : #include <vcl/outdev.hxx>
16 : #include <vcl/svapp.hxx>
17 :
18 : #include "fontsubset.hxx"
19 : #include "outdev.h"
20 : #include "outfont.hxx"
21 : #include "PhysicalFontCollection.hxx"
22 : #include "salgdi.hxx"
23 : #include "sft.hxx"
24 :
25 : #include <vcl/embeddedfontshelper.hxx>
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 974 : static void clearDir( const OUString& path )
41 : {
42 974 : osl::Directory dir( path );
43 974 : if( dir.reset() == osl::Directory::E_None )
44 : {
45 : for(;;)
46 : {
47 77 : osl::DirectoryItem item;
48 77 : if( dir.getNextItem( item ) != osl::Directory::E_None )
49 64 : break;
50 26 : osl::FileStatus status( osl_FileStatus_Mask_FileURL );
51 13 : if( item.getFileStatus( status ) == osl::File::E_None )
52 13 : osl::File::remove( status.getFileURL());
53 13 : }
54 974 : }
55 974 : }
56 :
57 487 : void EmbeddedFontsHelper::clearTemporaryFontFiles()
58 : {
59 487 : OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
60 487 : rtl::Bootstrap::expandMacros( path );
61 487 : path += "/user/temp/embeddedfonts/";
62 487 : clearDir( path + "fromdocs/" );
63 487 : clearDir( path + "fromsystem/" );
64 487 : }
65 :
66 13 : bool EmbeddedFontsHelper::addEmbeddedFont( uno::Reference< io::XInputStream > stream, const OUString& fontName,
67 : const char* extra, std::vector< unsigned char > key, bool eot )
68 : {
69 13 : OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra );
70 26 : osl::File file( fileUrl );
71 13 : switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write ))
72 : {
73 : case osl::File::E_None:
74 13 : break; // ok
75 : case osl::File::E_EXIST:
76 0 : return true; // Assume it's already been added correctly.
77 : default:
78 : SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" );
79 0 : return false;
80 : }
81 13 : size_t keyPos = 0;
82 13 : std::vector< char > fontData;
83 13 : fontData.reserve( 1000000 );
84 : for(;;)
85 : {
86 4038 : uno::Sequence< sal_Int8 > buffer;
87 4038 : sal_uInt64 read = stream->readBytes( buffer, 1024 );
88 8076 : for( sal_uInt64 pos = 0;
89 4038 : pos < read && keyPos < key.size();
90 : ++pos )
91 0 : buffer[ pos ] ^= key[ keyPos++ ];
92 : // if eot, don't write the file out yet, since we need to unpack it first.
93 4038 : if( !eot && read > 0 )
94 : {
95 4025 : sal_uInt64 writtenTotal = 0;
96 12075 : while( writtenTotal < read )
97 : {
98 : sal_uInt64 written;
99 4025 : file.write( buffer.getConstArray(), read, written );
100 4025 : writtenTotal += written;
101 : }
102 : }
103 4038 : fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
104 4038 : if( read <= 0 )
105 13 : break;
106 4025 : }
107 13 : bool sufficientFontRights(false);
108 : #if ENABLE_EOT
109 : if( eot )
110 : {
111 : unsigned uncompressedFontSize = 0;
112 : unsigned char *nakedPointerToUncompressedFont = NULL;
113 : libeot::EOTMetadata eotMetadata;
114 : libeot::EOTError uncompressError =
115 : libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(&fontData[0]), 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 13 : if( file.close() != osl::File::E_None )
141 : {
142 : SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
143 0 : osl::File::remove( fileUrl );
144 0 : return false;
145 : }
146 13 : if( !eot )
147 : {
148 13 : sufficientFontRights = sufficientTTFRights( &fontData.front(), fontData.size(), EditingAllowed );
149 : }
150 13 : 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 0 : osl::File::remove( fileUrl );
157 0 : return false;
158 : }
159 13 : EmbeddedFontsHelper::activateFont( fontName, fileUrl );
160 26 : return true;
161 : }
162 :
163 13 : OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, const char* extra )
164 : {
165 13 : OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
166 13 : rtl::Bootstrap::expandMacros( path );
167 13 : path += "/user/temp/embeddedfonts/fromdocs/";
168 13 : osl::Directory::createPath( path );
169 26 : OUString filename = fontName;
170 : static int uniqueCounter = 0;
171 13 : if( strcmp( extra, "?" ) == 0 )
172 13 : filename += OUString::number( uniqueCounter++ );
173 : else
174 0 : filename += OStringToOUString( extra, RTL_TEXTENCODING_ASCII_US );
175 13 : filename += ".ttf"; // TODO is it always ttf?
176 26 : return path + filename;
177 : }
178 :
179 13 : void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl )
180 : {
181 13 : OutputDevice *pDevice = Application::GetDefaultDevice();
182 13 : pDevice->AddTempDevFont(fileUrl, fontName);
183 13 : }
184 :
185 : // Check if it's (legally) allowed to embed the font file into a document
186 : // (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
187 : // to have a different meaning (guessing from code, IsSubsettable() might
188 : // possibly mean it's ttf, while IsEmbeddable() might mean it's type1).
189 : // So just try to open the data as ttf and see.
190 13 : bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, long size, FontRights rights )
191 : {
192 : TrueTypeFont* font;
193 13 : if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SF_OK )
194 : {
195 : TTGlobalFontInfo info;
196 13 : GetTTGlobalFontInfo( font, &info );
197 13 : CloseTTFont( font );
198 : // http://www.microsoft.com/typography/tt/ttf_spec/ttch02.doc
199 13 : int copyright = info.typeFlags & TYPEFLAG_COPYRIGHT_MASK;
200 13 : switch( rights )
201 : {
202 : case ViewingAllowed:
203 : // Embedding not restricted completely.
204 13 : return ( copyright & 0x02 ) != 0x02;
205 : case EditingAllowed:
206 : // Font is installable or editable.
207 13 : return copyright == 0 || ( copyright & 0x08 );
208 : }
209 : }
210 0 : return true; // no known restriction
211 : }
212 :
213 0 : OUString EmbeddedFontsHelper::fontFileUrl( const OUString& familyName, FontFamily family, FontItalic italic,
214 : FontWeight weight, FontPitch pitch, rtl_TextEncoding, FontRights rights )
215 : {
216 0 : OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
217 0 : rtl::Bootstrap::expandMacros( path );
218 0 : path += "/user/temp/embeddedfonts/fromsystem/";
219 0 : osl::Directory::createPath( path );
220 0 : OUString filename = familyName + "_" + OUString::number( family ) + "_" + OUString::number( italic )
221 0 : + "_" + OUString::number( weight ) + "_" + OUString::number( pitch );
222 0 : filename += ".ttf"; // TODO is it always ttf?
223 0 : OUString url = path + filename;
224 0 : if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists()
225 : {
226 : // File with contents of the font file already exists, assume it's been created by a previous call.
227 0 : return url;
228 : }
229 0 : bool ok = false;
230 0 : SalGraphics* graphics = Application::GetDefaultDevice()->GetGraphics();
231 0 : PhysicalFontCollection fonts;
232 0 : graphics->GetDevFontList( &fonts );
233 0 : std::unique_ptr< ImplGetDevFontList > fontInfo( fonts.GetDevFontList());
234 0 : PhysicalFontFace* selected = NULL;
235 0 : for( int i = 0;
236 0 : i < fontInfo->Count();
237 : ++i )
238 : {
239 0 : PhysicalFontFace* f = fontInfo->Get( i );
240 0 : if( f->GetFamilyName() == familyName )
241 : {
242 : // Ignore comparing text encodings, at least for now. They cannot be trivially compared
243 : // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding,
244 : // and just having a unicode font doesn't say what glyphs it actually contains).
245 : // It is possible that it still may be needed to do at least some checks here
246 : // for some encodings (can one font have more font files for more encodings?).
247 0 : if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
248 0 : && ( italic == ITALIC_DONTKNOW || f->GetSlant() == italic )
249 0 : && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
250 0 : && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
251 : { // Exact match, return it immediately.
252 0 : selected = f;
253 0 : break;
254 : }
255 0 : if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
256 0 : && ( f->GetSlant() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetSlant() == italic )
257 0 : && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
258 0 : && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
259 : { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one.
260 0 : selected = f;
261 : }
262 : }
263 : }
264 0 : if( selected != NULL )
265 : {
266 0 : FontSubsetInfo info;
267 : long size;
268 0 : if( const void* data = graphics->GetEmbedFontData( selected, NULL, NULL, 0, info, &size ))
269 : {
270 0 : if( sufficientTTFRights( data, size, rights ))
271 : {
272 0 : osl::File file( url );
273 0 : if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
274 : {
275 0 : sal_uInt64 written = 0;
276 0 : sal_uInt64 totalSize = size;
277 0 : bool error = false;
278 0 : while( written < totalSize && !error)
279 : {
280 : sal_uInt64 nowWritten;
281 0 : switch( file.write( static_cast< const char* >( data ) + written, size - written, nowWritten ))
282 : {
283 : case osl::File::E_None:
284 0 : written += nowWritten;
285 0 : break;
286 : case osl::File::E_AGAIN:
287 : case osl::File::E_INTR:
288 0 : break;
289 : default:
290 0 : error = true;
291 0 : break;
292 : }
293 : }
294 0 : file.close();
295 0 : if( error )
296 0 : osl::File::remove( url );
297 : else
298 0 : ok = true;
299 0 : }
300 : }
301 0 : graphics->FreeEmbedFontData( data, size );
302 0 : }
303 : }
304 0 : return ok ? url : "";
305 801 : }
306 :
307 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|