1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
11 #include <config_folders.h>
12 #include <config_eot.h>
14 #include <osl/file.hxx>
15 #include <rtl/bootstrap.hxx>
16 #include <sal/log.hxx>
17 #include <vcl/svapp.hxx>
18 #include <vcl/embeddedfontshelper.hxx>
19 #include <com/sun/star/io/XInputStream.hpp>
22 #include <PhysicalFontCollection.hxx>
32 #include <libeot/libeot.h>
37 using namespace com::sun::star
;
40 static void clearDir( const OUString
& path
)
42 osl::Directory
dir( path
);
43 if( dir
.reset() == osl::Directory::E_None
)
47 osl::DirectoryItem item
;
48 if( dir
.getNextItem( item
) != osl::Directory::E_None
)
50 osl::FileStatus
status( osl_FileStatus_Mask_FileURL
);
51 if( item
.getFileStatus( status
) == osl::File::E_None
)
52 osl::File::remove( status
.getFileURL());
57 void EmbeddedFontsHelper::clearTemporaryFontFiles()
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/" );
66 bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference
< io::XInputStream
>& stream
, const OUString
& fontName
,
67 const char* extra
, std::vector
< unsigned char > const & key
, bool eot
)
69 OUString fileUrl
= EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName
, extra
);
70 osl::File
file( fileUrl
);
71 switch( file
.open( osl_File_OpenFlag_Create
| osl_File_OpenFlag_Write
))
73 case osl::File::E_None
:
75 case osl::File::E_EXIST
:
76 return true; // Assume it's already been added correctly.
78 SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" );
82 std::vector
< char > fontData
;
83 fontData
.reserve( 1000000 );
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();
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 )
95 sal_uInt64 writtenTotal
= 0;
96 while( writtenTotal
< read
)
99 file
.write( buffer
.getConstArray(), read
, written
);
100 writtenTotal
+= written
;
103 fontData
.insert( fontData
.end(), buffer
.getConstArray(), buffer
.getConstArray() + read
);
107 bool sufficientFontRights(false);
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
)
119 SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
120 osl::File::remove( fileUrl
);
123 sal_uInt64 writtenTotal
= 0;
124 while( writtenTotal
< uncompressedFontSize
)
127 if( file
.write( uncompressedFont
.get() + writtenTotal
, uncompressedFontSize
- writtenTotal
, written
) != osl::File::E_None
)
129 SAL_WARN( "vcl.fonts", "Error writing temporary font file" );
130 osl::File::remove( fileUrl
);
133 writtenTotal
+= written
;
135 sufficientFontRights
= libeot::EOTcanLegallyEdit( &eotMetadata
);
136 libeot::EOTfreeMetadata( &eotMetadata
);
140 if( file
.close() != osl::File::E_None
)
142 SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
143 osl::File::remove( fileUrl
);
148 sufficientFontRights
= sufficientTTFRights(fontData
.data(), fontData
.size(), FontRights::EditingAllowed
);
150 if( !sufficientFontRights
)
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
);
159 m_aAccumulatedFonts
.emplace_back(std::make_pair(fontName
, fileUrl
));
165 struct UpdateFontsGuard
169 OutputDevice::ImplClearAllFontData(true);
174 OutputDevice::ImplRefreshAllFontData(true);
179 void EmbeddedFontsHelper::activateFonts()
181 if (m_aAccumulatedFonts
.empty())
183 UpdateFontsGuard aUpdateFontsGuard
;
184 for (const auto& rEntry
: m_aAccumulatedFonts
)
185 EmbeddedFontsHelper::activateFont(rEntry
.first
, rEntry
.second
);
186 m_aAccumulatedFonts
.clear();
189 OUString
EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString
& fontName
, const char* extra
)
191 OUString path
= "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
192 rtl::Bootstrap::expandMacros( path
);
193 path
+= "/user/temp/embeddedfonts/fromdocs/";
194 osl::Directory::createPath( path
);
195 OUString filename
= fontName
;
196 static int uniqueCounter
= 0;
197 if( strcmp( extra
, "?" ) == 0 )
198 filename
+= OUString::number( uniqueCounter
++ );
200 filename
+= OStringToOUString( extra
, RTL_TEXTENCODING_ASCII_US
);
201 filename
+= ".ttf"; // TODO is it always ttf?
202 return path
+ filename
;
205 void EmbeddedFontsHelper::activateFont( const OUString
& fontName
, const OUString
& fileUrl
)
207 OutputDevice
*pDevice
= Application::GetDefaultDevice();
208 pDevice
->AddTempDevFont(fileUrl
, fontName
);
211 // Check if it's (legally) allowed to embed the font file into a document
212 // (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
213 // to have a different meaning (guessing from code, IsSubsettable() might
214 // possibly mean it's ttf, while IsEmbeddable() might mean it's type1).
215 // So just try to open the data as ttf and see.
216 bool EmbeddedFontsHelper::sufficientTTFRights( const void* data
, tools::Long size
, FontRights rights
)
219 if( OpenTTFontBuffer( data
, size
, 0 /*TODO*/, &font
) == SFErrCodes::Ok
)
221 TTGlobalFontInfo info
;
222 GetTTGlobalFontInfo( font
, &info
);
224 // https://www.microsoft.com/typography/otspec/os2.htm#fst
225 int copyright
= info
.typeFlags
;
228 case FontRights::ViewingAllowed
:
229 // Embedding not restricted completely.
230 return ( copyright
& 0x02 ) != 0x02;
231 case FontRights::EditingAllowed
:
232 // Font is installable or editable.
233 return copyright
== 0 || ( copyright
& 0x08 );
236 return true; // no known restriction
239 OUString
EmbeddedFontsHelper::fontFileUrl( std::u16string_view familyName
, FontFamily family
, FontItalic italic
,
240 FontWeight weight
, FontPitch pitch
, FontRights rights
)
242 OUString path
= "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}";
243 rtl::Bootstrap::expandMacros( path
);
244 path
+= "/user/temp/embeddedfonts/fromsystem/";
245 osl::Directory::createPath( path
);
246 OUString filename
= OUString::Concat(familyName
) + "_" + OUString::number( family
) + "_" + OUString::number( italic
)
247 + "_" + OUString::number( weight
) + "_" + OUString::number( pitch
)
248 + ".ttf"; // TODO is it always ttf?
249 OUString url
= path
+ filename
;
250 if( osl::File( url
).open( osl_File_OpenFlag_Read
) == osl::File::E_None
) // = exists()
252 // File with contents of the font file already exists, assume it's been created by a previous call.
256 SalGraphics
* graphics
= Application::GetDefaultDevice()->GetGraphics();
257 PhysicalFontCollection fonts
;
258 graphics
->GetDevFontList( &fonts
);
259 std::unique_ptr
< ImplDeviceFontList
> fontInfo( fonts
.GetDeviceFontList());
260 PhysicalFontFace
* selected
= nullptr;
262 i
< fontInfo
->Count();
265 PhysicalFontFace
* f
= fontInfo
->Get( i
);
266 if( f
->GetFamilyName() == familyName
)
268 // Ignore comparing text encodings, at least for now. They cannot be trivially compared
269 // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding,
270 // and just having a unicode font doesn't say what glyphs it actually contains).
271 // It is possible that it still may be needed to do at least some checks here
272 // for some encodings (can one font have more font files for more encodings?).
273 if(( family
== FAMILY_DONTKNOW
|| f
->GetFamilyType() == family
)
274 && ( italic
== ITALIC_DONTKNOW
|| f
->GetItalic() == italic
)
275 && ( weight
== WEIGHT_DONTKNOW
|| f
->GetWeight() == weight
)
276 && ( pitch
== PITCH_DONTKNOW
|| f
->GetPitch() == pitch
))
277 { // Exact match, return it immediately.
281 if(( f
->GetFamilyType() == FAMILY_DONTKNOW
|| family
== FAMILY_DONTKNOW
|| f
->GetFamilyType() == family
)
282 && ( f
->GetItalic() == ITALIC_DONTKNOW
|| italic
== ITALIC_DONTKNOW
|| f
->GetItalic() == italic
)
283 && ( f
->GetWeight() == WEIGHT_DONTKNOW
|| weight
== WEIGHT_DONTKNOW
|| f
->GetWeight() == weight
)
284 && ( f
->GetPitch() == PITCH_DONTKNOW
|| pitch
== PITCH_DONTKNOW
|| f
->GetPitch() == pitch
))
285 { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one.
290 if( selected
!= nullptr )
293 if (const void* data
= graphics
->GetEmbedFontData(selected
, &size
))
295 if( sufficientTTFRights( data
, size
, rights
))
297 osl::File
file( url
);
298 if( file
.open( osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
) == osl::File::E_None
)
300 sal_uInt64 written
= 0;
301 sal_uInt64 totalSize
= size
;
303 while( written
< totalSize
&& !error
)
305 sal_uInt64 nowWritten
;
306 switch( file
.write( static_cast< const char* >( data
) + written
, size
- written
, nowWritten
))
308 case osl::File::E_None
:
309 written
+= nowWritten
;
311 case osl::File::E_AGAIN
:
312 case osl::File::E_INTR
:
321 osl::File::remove( url
);
326 graphics
->FreeEmbedFontData( data
, size
);
329 return ok
? url
: "";
332 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */