LibreOffice Module vcl (master)  1
embeddedfontshelper.cxx
Go to the documentation of this file.
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>
19 #include <com/sun/star/io/XInputStream.hpp>
20 
21 #include <outdev.h>
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 
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 
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 
66 bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName,
67  const char* extra, std::vector< unsigned char > 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  EmbeddedFontsHelper::activateFont( fontName, fileUrl );
160  return true;
161 }
162 
163 OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, const char* extra )
164 {
165  OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
166  rtl::Bootstrap::expandMacros( path );
167  path += "/user/temp/embeddedfonts/fromdocs/";
168  osl::Directory::createPath( path );
169  OUString filename = fontName;
170  static int uniqueCounter = 0;
171  if( strcmp( extra, "?" ) == 0 )
172  filename += OUString::number( uniqueCounter++ );
173  else
174  filename += OStringToOUString( extra, RTL_TEXTENCODING_ASCII_US );
175  filename += ".ttf"; // TODO is it always ttf?
176  return path + filename;
177 }
178 
179 void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl )
180 {
182  pDevice->AddTempDevFont(fileUrl, fontName);
183 }
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 bool EmbeddedFontsHelper::sufficientTTFRights( const void* data, long size, FontRights rights )
191 {
192  TrueTypeFont* font;
193  if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SFErrCodes::Ok )
194  {
195  TTGlobalFontInfo info;
196  GetTTGlobalFontInfo( font, &info );
197  CloseTTFont( font );
198  // https://www.microsoft.com/typography/otspec/os2.htm#fst
199  int copyright = info.typeFlags;
200  switch( rights )
201  {
202  case FontRights::ViewingAllowed:
203  // Embedding not restricted completely.
204  return ( copyright & 0x02 ) != 0x02;
205  case FontRights::EditingAllowed:
206  // Font is installable or editable.
207  return copyright == 0 || ( copyright & 0x08 );
208  }
209  }
210  return true; // no known restriction
211 }
212 
213 OUString EmbeddedFontsHelper::fontFileUrl( const OUString& familyName, FontFamily family, FontItalic italic,
214  FontWeight weight, FontPitch pitch, FontRights rights )
215 {
216  OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
217  rtl::Bootstrap::expandMacros( path );
218  path += "/user/temp/embeddedfonts/fromsystem/";
219  osl::Directory::createPath( path );
220  OUString filename = familyName + "_" + OUString::number( family ) + "_" + OUString::number( italic )
221  + "_" + OUString::number( weight ) + "_" + OUString::number( pitch )
222  + ".ttf"; // TODO is it always ttf?
223  OUString url = path + filename;
224  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  return url;
228  }
229  bool ok = false;
232  graphics->GetDevFontList( &fonts );
233  std::unique_ptr< ImplDeviceFontList > fontInfo( fonts.GetDeviceFontList());
234  PhysicalFontFace* selected = nullptr;
235  for( int i = 0;
236  i < fontInfo->Count();
237  ++i )
238  {
239  PhysicalFontFace* f = fontInfo->Get( i );
240  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  if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
248  && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
249  && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
250  && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
251  { // Exact match, return it immediately.
252  selected = f;
253  break;
254  }
255  if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
256  && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
257  && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
258  && ( 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  selected = f;
261  }
262  }
263  }
264  if( selected != nullptr )
265  {
266  long size;
267  if (const void* data = graphics->GetEmbedFontData(selected, &size))
268  {
269  if( sufficientTTFRights( data, size, rights ))
270  {
271  osl::File file( url );
272  if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
273  {
274  sal_uInt64 written = 0;
275  sal_uInt64 totalSize = size;
276  bool error = false;
277  while( written < totalSize && !error)
278  {
279  sal_uInt64 nowWritten;
280  switch( file.write( static_cast< const char* >( data ) + written, size - written, nowWritten ))
281  {
282  case osl::File::E_None:
283  written += nowWritten;
284  break;
285  case osl::File::E_AGAIN:
286  case osl::File::E_INTR:
287  break;
288  default:
289  error = true;
290  break;
291  }
292  }
293  file.close();
294  if( error )
295  osl::File::remove( url );
296  else
297  ok = true;
298  }
299  }
300  graphics->FreeEmbedFontData( data, size );
301  }
302  }
303  return ok ? url : "";
304 }
305 
306 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static void clearDir(const OUString &path)
SFErrCodes OpenTTFontBuffer(const void *pBuffer, sal_uInt32 nLen, sal_uInt32 facenum, TrueTypeFont **ttf)
TrueTypeFont constructor.
Definition: sft.cxx:1425
virtual const void * GetEmbedFontData(const PhysicalFontFace *pFont, long *pDataLen)=0
void CloseTTFont(TrueTypeFont *ttf)
TrueTypeFont destructor.
Definition: sft.cxx:1671
FAMILY_DONTKNOW
Return value of GetTTGlobalFontInfo()
Definition: sft.hxx:146
sal_uInt32 typeFlags
type flags (copyright bits)
Definition: sft.hxx:172
FontRights
Specification of what kind of operation is allowed when embedding a font.
static OutputDevice * GetDefaultDevice()
Get the default "device" (in this case the default window).
Definition: svapp.cxx:1054
static OUString fontFileUrl(const OUString &familyName, FontFamily family, FontItalic italic, FontWeight weight, FontPitch pitch, FontRights rights)
Returns URL for a font file for the given font, or empty if it does not exist.
FontItalic GetItalic() const
void GetTTGlobalFontInfo(TrueTypeFont *ttf, TTGlobalFontInfo *info)
Returns global font information about the TrueType font.
Definition: sft.cxx:2363
bool AddTempDevFont(const OUString &rFileURL, const OUString &rFontName)
virtual void FreeEmbedFontData(const void *pData, long nDataLen)=0
abstract base class for physical font faces
WEIGHT_DONTKNOW
FontFamily GetFamilyType() const
#define SAL_CONFIGFILE(name)
static void activateFont(const OUString &fontName, const OUString &fileUrl)
Adds the given font to the list of known fonts.
FontWeight GetWeight() const
int i
static void clearTemporaryFontFiles()
Removes all temporary fonts in the path used by fileUrlForTemporaryFont().
Some things multiple-inherit from VclAbstractDialog and OutputDevice, so we need to use virtual inher...
Definition: outdev.hxx:304
static OUString fileUrlForTemporaryFont(const OUString &fontName, const char *extra)
Returns a URL for a file where to store contents of a given temporary font.
fonts
PITCH_DONTKNOW
size
FontPitch
std::unique_ptr< ImplDeviceFontList > GetDeviceFontList() const
static bool addEmbeddedFont(const css::uno::Reference< css::io::XInputStream > &stream, const OUString &fontName, const char *extra, std::vector< unsigned char > key, bool eot=false)
Reads a font from the input stream, saves it to a temporary font file and activates the font...
FontFamily
FontWeight
static bool sufficientTTFRights(const void *data, long size, FontRights rights)
Returns if the restrictions specified in the font (if present) allow embedding the font for a particu...
#define SAL_INFO(area, stream)
Sun Font Tools.
ITALIC_DONTKNOW
SalGraphics const * GetGraphics() const
Get the graphic context that the output device uses to draw on.
Definition: outdev.cxx:198
#define SAL_WARN(area, stream)
void(* f)(TrueTypeTable *)
Definition: ttcr.cxx:466
FontPitch GetPitch() const
const OUString & GetFamilyName() const
FontItalic
virtual void GetDevFontList(PhysicalFontCollection *)=0