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 <sal/config.h>
11
12#include <memory>
13#include <config_folders.h>
14#include <config_eot.h>
15
16#include <osl/file.hxx>
17#include <rtl/bootstrap.hxx>
18#include <sal/log.hxx>
19#include <vcl/svapp.hxx>
21#include <com/sun/star/io/XInputStream.hpp>
22
25#include <salgdi.hxx>
26#include <sft.hxx>
27
28
29#if ENABLE_EOT
30extern "C"
31{
32namespace libeot
33{
34#include <libeot/libeot.h>
35} // namespace libeot
36} // extern "C"
37#endif
38
39using namespace com::sun::star;
40using namespace vcl;
41
42static void clearDir( const OUString& path )
43{
44 osl::Directory dir( path );
45 if( dir.reset() == osl::Directory::E_None )
46 {
47 for(;;)
48 {
49 osl::DirectoryItem item;
50 if( dir.getNextItem( item ) != osl::Directory::E_None )
51 break;
52 osl::FileStatus status( osl_FileStatus_Mask_FileURL );
53 if( item.getFileStatus( status ) == osl::File::E_None )
54 osl::File::remove( status.getFileURL());
55 }
56 }
57}
58
60{
61 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
62 rtl::Bootstrap::expandMacros( path );
63 path += "/user/temp/embeddedfonts/";
64 clearDir( path + "fromdocs/" );
65 clearDir( path + "fromsystem/" );
66}
67
68bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName,
69 std::u16string_view extra, std::vector< unsigned char > const & key, bool eot )
70{
71 OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra );
72 osl::File file( fileUrl );
73 switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write ))
74 {
75 case osl::File::E_None:
76 break; // ok
77 case osl::File::E_EXIST:
78 return true; // Assume it's already been added correctly.
79 default:
80 SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" );
81 return false;
82 }
83 size_t keyPos = 0;
84 std::vector< char > fontData;
85 fontData.reserve( 1000000 );
86 for(;;)
87 {
88 uno::Sequence< sal_Int8 > buffer;
89 sal_uInt64 read = stream->readBytes( buffer, 1024 );
90 auto bufferRange = asNonConstRange(buffer);
91 for( sal_uInt64 pos = 0;
92 pos < read && keyPos < key.size();
93 ++pos )
94 bufferRange[ pos ] ^= key[ keyPos++ ];
95 // if eot, don't write the file out yet, since we need to unpack it first.
96 if( !eot && read > 0 )
97 {
98 sal_uInt64 writtenTotal = 0;
99 while( writtenTotal < read )
100 {
101 sal_uInt64 written;
102 file.write( buffer.getConstArray(), read, written );
103 writtenTotal += written;
104 }
105 }
106 fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
107 if( read <= 0 )
108 break;
109 }
110 bool sufficientFontRights(false);
111#if ENABLE_EOT
112 if( eot )
113 {
114 unsigned uncompressedFontSize = 0;
115 unsigned char *nakedPointerToUncompressedFont = nullptr;
116 libeot::EOTMetadata eotMetadata;
117 libeot::EOTError uncompressError =
118 libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize );
119 std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer );
120 if( uncompressError != libeot::EOT_SUCCESS )
121 {
122 SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
123 osl::File::remove( fileUrl );
124 return false;
125 }
126 sal_uInt64 writtenTotal = 0;
127 while( writtenTotal < uncompressedFontSize )
128 {
129 sal_uInt64 written;
130 if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None )
131 {
132 SAL_WARN( "vcl.fonts", "Error writing temporary font file" );
133 osl::File::remove( fileUrl );
134 return false;
135 }
136 writtenTotal += written;
137 }
138 sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata );
139 libeot::EOTfreeMetadata( &eotMetadata );
140 }
141#endif
142
143 if( file.close() != osl::File::E_None )
144 {
145 SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
146 osl::File::remove( fileUrl );
147 return false;
148 }
149 if( !eot )
150 {
151 sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed);
152 }
153 if( !sufficientFontRights )
154 {
155 // It would be actually better to open the document in read-only mode in this case,
156 // warn the user about this, and provide a button to drop the font(s) in order
157 // to switch to editing.
158 SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" );
159 osl::File::remove( fileUrl );
160 return false;
161 }
162 m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl));
163 return true;
164}
165
166namespace
167{
168 struct UpdateFontsGuard
169 {
170 UpdateFontsGuard()
171 {
173 }
174
175 ~UpdateFontsGuard()
176 {
178 }
179 };
180}
181
183{
184 if (m_aAccumulatedFonts.empty())
185 return;
186 UpdateFontsGuard aUpdateFontsGuard;
187 for (const auto& rEntry : m_aAccumulatedFonts)
188 EmbeddedFontsHelper::activateFont(rEntry.first, rEntry.second);
189 m_aAccumulatedFonts.clear();
190}
191
192OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, std::u16string_view extra )
193{
194 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
195 rtl::Bootstrap::expandMacros( path );
196 path += "/user/temp/embeddedfonts/fromdocs/";
197 osl::Directory::createPath( path );
198 OUString filename = fontName;
199 static int uniqueCounter = 0;
200 if( extra == u"?" )
201 filename += OUString::number( uniqueCounter++ );
202 else
203 filename += extra;
204 filename += ".ttf"; // TODO is it always ttf?
205 return path + filename;
206}
207
208void EmbeddedFontsHelper::activateFont( const OUString& fontName, const OUString& fileUrl )
209{
211 pDevice->AddTempDevFont(fileUrl, fontName);
212}
213
214// Check if it's (legally) allowed to embed the font file into a document
215// (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
216// to have a different meaning (guessing from code, IsSubsettable() might
217// possibly mean it's ttf, while IsEmbeddable() might mean it's type1).
218// So just try to open the data as ttf and see.
220{
221 TrueTypeFont* font;
222 if( OpenTTFontBuffer( data, size, 0 /*TODO*/, &font ) == SFErrCodes::Ok )
223 {
224 TTGlobalFontInfo info;
225 GetTTGlobalFontInfo( font, &info );
226 CloseTTFont( font );
227 // https://www.microsoft.com/typography/otspec/os2.htm#fst
228 int copyright = info.typeFlags;
229 switch( rights )
230 {
232 // Embedding not restricted completely.
233 return ( copyright & 0x02 ) != 0x02;
235 // Font is installable or editable.
236 return copyright == 0 || ( copyright & 0x08 );
237 }
238 }
239 return true; // no known restriction
240}
241
242OUString EmbeddedFontsHelper::fontFileUrl( std::u16string_view familyName, FontFamily family, FontItalic italic,
243 FontWeight weight, FontPitch pitch, FontRights rights )
244{
245 OUString path = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
246 rtl::Bootstrap::expandMacros( path );
247 path += "/user/temp/embeddedfonts/fromsystem/";
248 osl::Directory::createPath( path );
249 OUString filename = OUString::Concat(familyName) + "_" + OUString::number( family ) + "_" + OUString::number( italic )
250 + "_" + OUString::number( weight ) + "_" + OUString::number( pitch )
251 + ".ttf"; // TODO is it always ttf?
252 OUString url = path + filename;
253 if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists()
254 {
255 // File with contents of the font file already exists, assume it's been created by a previous call.
256 return url;
257 }
258 bool ok = false;
261 graphics->GetDevFontList( &fonts );
262 std::unique_ptr< vcl::font::PhysicalFontFaceCollection > fontInfo( fonts.GetFontFaceCollection());
263 vcl::font::PhysicalFontFace* selected = nullptr;
264
265 // Maybe we don't find the perfect match for the font. E.G. we have fonts with the same family name
266 // but not same bold or italic etc...
267 // In this case we add all the fonts having the family name of the used font:
268 // - we store all these fonts in familyNameFonts during loop
269 // - if we haven't found the perfect match we store all fonts in familyNameFonts
270 typedef std::vector<vcl::font::PhysicalFontFace*> FontList;
271 FontList familyNameFonts;
272
273 for( int i = 0;
274 i < fontInfo->Count();
275 ++i )
276 {
277 vcl::font::PhysicalFontFace* f = fontInfo->Get( i );
278 if( f->GetFamilyName() == familyName )
279 {
280 // Ignore comparing text encodings, at least for now. They cannot be trivially compared
281 // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding,
282 // and just having a unicode font doesn't say what glyphs it actually contains).
283 // It is possible that it still may be needed to do at least some checks here
284 // for some encodings (can one font have more font files for more encodings?).
285 if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
286 && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
287 && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
288 && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
289 { // Exact match, return it immediately.
290 selected = f;
291 break;
292 }
293 if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
294 && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
295 && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
296 && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
297 { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one.
298 selected = f;
299 }
300 // adding "not perfect match" to familyNameFonts vector
301 familyNameFonts.push_back(f);
302
303 }
304 }
305
306 // if we have found a perfect match we will add only "selected", otherwise all familyNameFonts
307 FontList fontsToAdd = (selected ? FontList(1, selected) : std::move(familyNameFonts));
308
309 for (vcl::font::PhysicalFontFace* f : fontsToAdd)
310 {
311 if (!selected) { // recalculate file not for "not perfect match"
312 filename = OUString::Concat(familyName) + "_" + OUString::number(f->GetFamilyType()) + "_" +
313 OUString::number(f->GetItalic()) + "_" + OUString::number(f->GetWeight()) + "_" +
314 OUString::number(f->GetPitch()) + ".ttf"; // TODO is it always ttf?
315 url = path + filename;
316 if (osl::File(url).open(osl_File_OpenFlag_Read) == osl::File::E_None) // = exists()
317 {
318 // File with contents of the font file already exists, assume it's been created by a previous call.
319 continue;
320 }
321 }
322 auto aFontData(f->GetRawFontData(0));
323 if (!aFontData.empty())
324 {
325 auto data = aFontData.data();
326 auto size = aFontData.size();
327 if( sufficientTTFRights( data, size, rights ))
328 {
329 osl::File file( url );
330 if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
331 {
332 sal_uInt64 written = 0;
333 sal_uInt64 totalSize = size;
334 bool error = false;
335 while( written < totalSize && !error)
336 {
337 sal_uInt64 nowWritten;
338 switch( file.write( data + written, size - written, nowWritten ))
339 {
340 case osl::File::E_None:
341 written += nowWritten;
342 break;
343 case osl::File::E_AGAIN:
344 case osl::File::E_INTR:
345 break;
346 default:
347 error = true;
348 break;
349 }
350 }
351 file.close();
352 if( error )
353 osl::File::remove( url );
354 else
355 ok = true;
356 }
357 }
358 }
359 }
360 return ok ? url : "";
361}
362
363/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
static OutputDevice * GetDefaultDevice()
Get the default "device" (in this case the default window).
Definition: svapp.cxx:1043
std::vector< std::pair< OUString, OUString > > m_aAccumulatedFonts
static OUString fileUrlForTemporaryFont(const OUString &fontName, std::u16string_view extra)
Returns a URL for a file where to store contents of a given temporary font.
bool addEmbeddedFont(const css::uno::Reference< css::io::XInputStream > &stream, const OUString &fontName, std::u16string_view extra, std::vector< unsigned char > const &key, bool eot=false)
Reads a font from the input stream, saves it to a temporary font file and adds it to the list of font...
void activateFonts()
Adds the accumulated fonts to the list of known fonts.
static OUString fontFileUrl(std::u16string_view 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.
static bool sufficientTTFRights(const void *data, tools::Long size, FontRights rights)
Returns if the restrictions specified in the font (if present) allow embedding the font for a particu...
static void clearTemporaryFontFiles()
Removes all temporary fonts in the path used by fileUrlForTemporaryFont().
static void activateFont(const OUString &fontName, const OUString &fileUrl)
Adds the given font to the list of known fonts.
FontRights
Specification of what kind of operation is allowed when embedding a font.
@ ViewingAllowed
Font may be embedded for viewing the document (but not editing)
@ EditingAllowed
Font may be embedded for editing document (implies viewing)
FontFamily GetFamilyType() const
FontItalic GetItalic() const
FontWeight GetWeight() const
const OUString & GetFamilyName() const
FontPitch GetPitch() const
Some things multiple-inherit from VclAbstractDialog and OutputDevice, so we need to use virtual inher...
Definition: outdev.hxx:170
SalGraphics const * GetGraphics() const
Get the graphic context that the output device uses to draw on.
Definition: outdev.cxx:200
static void ImplClearAllFontData(bool bNewFontLists)
bool AddTempDevFont(const OUString &rFileURL, const OUString &rFontName)
static void ImplRefreshAllFontData(bool bNewFontLists)
virtual void GetDevFontList(vcl::font::PhysicalFontCollection *)=0
abstract base class for physical font faces
#define SAL_CONFIGFILE(name)
Reference< XOutputStream > stream
float u
static void clearDir(const OUString &path)
FontPitch
PITCH_DONTKNOW
FontItalic
ITALIC_DONTKNOW
FontFamily
FAMILY_DONTKNOW
WEIGHT_DONTKNOW
void GetTTGlobalFontInfo(AbstractTrueTypeFont *ttf, TTGlobalFontInfo *info)
Returns global font information about the TrueType font.
Definition: sft.cxx:2281
SFErrCodes OpenTTFontBuffer(const void *pBuffer, sal_uInt32 nLen, sal_uInt32 facenum, TrueTypeFont **ttf, const FontCharMapRef xCharMap)
TrueTypeFont constructor.
Definition: sft.cxx:1194
void CloseTTFont(TrueTypeFont *ttf)
TrueTypeFont destructor.
Definition: sft.cxx:1258
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
fonts
size
int i
FontWeight
long Long
Sun Font Tools.
Return value of GetTTGlobalFontInfo()
Definition: sft.hxx:150
sal_uInt32 typeFlags
type flags (copyright bits)
Definition: sft.hxx:176
size_t pos