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