Don't write multiple copies of identical embedded font files

Before the change, ODF import created unique font name every time,
using '?' as extra; that meant that every document with embedded
fonts would create and register its fonts, even when it was already
registered by a different document. On the other hand, OOXML import
would rely on file name match, which might not guarantee that the
files were actually identical.

This change compares the existing file content, which would only
happen when the files are of the same size. This also writes the
file only after doing all the checks.

Change-Id: I2377bd6c24291d9b4113607b4d48a223187b635c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189270
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Jenkins
This commit is contained in:
Mike Kaganski
2025-08-10 10:04:02 +02:00
parent 48ff1bbbe9
commit 086f3adda1
4 changed files with 97 additions and 79 deletions
-10
View File
@@ -28,16 +28,6 @@ class VCL_DLLPUBLIC EmbeddedFontsHelper
private:
std::vector<std::pair<OUString, OUString>> m_aAccumulatedFonts;
/**
Returns a URL for a file where to store contents of a given temporary font.
The file may or not may not exist yet, and will be cleaned up automatically as appropriate.
Use activateTemporaryFont() to actually enable usage of the font.
@param fontName name of the font (e.g. 'Times New Roman')
@param extra additional text to use for name (e.g. to distinguish regular from bold, italic,...), "?" for unique
*/
static OUString fileUrlForTemporaryFont(const OUString& fontName, std::u16string_view extra);
/**
Adds the accumulated fonts to the list of known fonts. The fonts are used only until application
exit.
+87 -67
View File
@@ -70,6 +70,85 @@ void clearDir( const OUString& path )
}
}
}
// Returns a URL for a file where to store contents of a given temporary font.
// The file may or not may not exist yet, and will be cleaned up automatically as appropriate.
// Use activateFonts() to actually enable usage of the font.
OUString fileUrlForTemporaryFont(std::u16string_view name)
{
OUString filename = OUString::Concat(name) + ".ttf"; // TODO is it always ttf?
if (!comphelper::OStorageHelper::IsValidZipEntryFileName(filename, false))
{
SAL_WARN("vcl.fonts", "Cannot use filename: " << filename << " for temporary font");
static int uniqueCounter = 0;
filename = "font" + OUString::number(uniqueCounter++) + ".ttf";
}
OUString path = GetEmbeddedFontsRoot() + "fromdocs/";
osl::Directory::createPath(path);
return path
+ rtl::Uri::encode(filename, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
RTL_TEXTENCODING_UTF8);
}
// Returns true when the file was written, or exactly same data was existing.
//
// @param name name of the font file
// @param url returns URL of a new file, or empty string (when the same file already existed).
bool writeFontBytesToFile(const std::vector<char>& bytes, std::u16string_view name, OUString& url)
{
url = fileUrlForTemporaryFont(name);
std::optional<osl::File> file(url);
auto rc = file->open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write);
// Check if existing file is the same
for (int counter = 0; rc == osl::File::E_EXIST;)
{
if (file->open(osl_File_OpenFlag_Read | osl_File_OpenFlag_NoLock) == osl::File::E_None)
{
if (sal_uInt64 size; file->getSize(size) == osl::File::E_None && size == bytes.size())
{
std::vector<char> bytes2(bytes.size());
sal_uInt64 readTotal = 0;
while (readTotal < bytes.size())
{
sal_uInt64 read = 0;
rc = file->read(bytes2.data() + readTotal, bytes.size() - readTotal, read);
if (read == 0)
break;
readTotal += read;
}
if (rc == osl::File::E_None && bytes2 == bytes)
{
url.clear();
return true; // OK, it's the same bytes
}
}
}
url = fileUrlForTemporaryFont(Concat2View(name + OUString::number(counter++)));
file.emplace(url);
rc = file->open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write);
}
if (rc != osl::File::E_None)
return false;
sal_uInt64 writtenTotal = 0;
while (writtenTotal < bytes.size())
{
sal_uInt64 written = 0;
file->write(bytes.data() + writtenTotal, bytes.size() - writtenTotal, written);
if (written == 0)
{
file->close();
osl::File::remove(file->getURL());
return false;
}
writtenTotal += written;
}
return true;
}
}
void EmbeddedFontsHelper::clearTemporaryFontFiles()
@@ -83,18 +162,6 @@ bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStrea
std::u16string_view extra, std::vector< unsigned char > const & key, bool eot,
bool bSubsetted )
{
OUString fileUrl = EmbeddedFontsHelper::fileUrlForTemporaryFont( fontName, extra );
osl::File file( fileUrl );
switch( file.open( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write ))
{
case osl::File::E_None:
break; // ok
case osl::File::E_EXIST:
return true; // Assume it's already been added correctly.
default:
SAL_WARN( "vcl.fonts", "Cannot open file for temporary font" );
return false;
}
size_t keyPos = 0;
std::vector< char > fontData;
fontData.reserve( 1000000 );
@@ -108,16 +175,6 @@ bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStrea
++pos )
bufferRange[ pos ] ^= key[ keyPos++ ];
// if eot, don't write the file out yet, since we need to unpack it first.
if( !eot && read > 0 )
{
sal_uInt64 writtenTotal = 0;
while( writtenTotal < read )
{
sal_uInt64 written;
file.write( buffer.getConstArray(), read, written );
writtenTotal += written;
}
}
fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
if( read <= 0 )
break;
@@ -135,32 +192,16 @@ bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStrea
if( uncompressError != libeot::EOT_SUCCESS )
{
SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
osl::File::remove( fileUrl );
return false;
}
sal_uInt64 writtenTotal = 0;
while( writtenTotal < uncompressedFontSize )
{
sal_uInt64 written;
if( file.write( uncompressedFont.get() + writtenTotal, uncompressedFontSize - writtenTotal, written ) != osl::File::E_None )
{
SAL_WARN( "vcl.fonts", "Error writing temporary font file" );
osl::File::remove( fileUrl );
return false;
}
writtenTotal += written;
}
fontData.clear();
fontData.insert(fontData.end(), reinterpret_cast<char*>(uncompressedFont.get()),
reinterpret_cast<char*>(uncompressedFont.get() + uncompressedFontSize));
sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata );
libeot::EOTfreeMetadata( &eotMetadata );
}
#endif
if( file.close() != osl::File::E_None )
{
SAL_WARN( "vcl.fonts", "Writing temporary font file failed" );
osl::File::remove( fileUrl );
return false;
}
if( !eot )
{
sufficientFontRights = sufficientTTFRights(fontData.data(), fontData.size(), FontRights::EditingAllowed);
@@ -171,7 +212,6 @@ bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStrea
// warn the user about this, and provide a button to drop the font(s) in order
// to switch to editing.
SAL_INFO( "vcl.fonts", "Ignoring embedded font that is not usable for editing" );
osl::File::remove( fileUrl );
return false;
}
@@ -200,12 +240,15 @@ bool EmbeddedFontsHelper::addEmbeddedFont( const uno::Reference< io::XInputStrea
if (nGlyphs < 26)
{
SAL_INFO("vcl.fonts", "Ignoring embedded font that only provides " << nGlyphs << " non-empty glyphs");
osl::File::remove(fileUrl);
return false;
}
}
m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl));
OUString fileUrl;
if (!writeFontBytesToFile(fontData, Concat2View(fontName + extra), fileUrl))
return false;
if (!fileUrl.isEmpty())
m_aAccumulatedFonts.emplace_back(std::make_pair(fontName, fileUrl));
return true;
}
@@ -236,29 +279,6 @@ void EmbeddedFontsHelper::activateFonts()
m_aAccumulatedFonts.clear();
}
OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, std::u16string_view extra )
{
OUString filename = fontName;
static int uniqueCounter = 0;
if( extra == u"?" )
filename += OUString::number( uniqueCounter++ );
else
filename += extra;
filename += ".ttf"; // TODO is it always ttf?
if (!::comphelper::OStorageHelper::IsValidZipEntryFileName(filename, false))
{
SAL_WARN( "vcl.fonts", "Cannot use filename: " << filename << " for temporary font");
filename = "font" + OUString::number(uniqueCounter++) + ".ttf";
}
OUString path = GetEmbeddedFontsRoot() + "fromdocs/";
osl::Directory::createPath( path );
return path
+ rtl::Uri::encode(filename, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
RTL_TEXTENCODING_UTF8);
}
// Check if it's (legally) allowed to embed the font file into a document
// (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
// to have a different meaning (guessing from code, IsSubsettable() might
+9 -2
View File
@@ -155,6 +155,13 @@ OUString XMLFontStyleContextFontFace::familyName() const
return ret;
}
OUString XMLFontStyleContextFontFace::styleName() const
{
OUString ret;
aStyleName >>= ret;
return ret;
}
XMLFontStyleContextFontFaceFormat::XMLFontStyleContextFontFaceFormat( SvXMLImport& rImport,
XMLFontStyleContextFontFaceUri& _uri )
@@ -289,7 +296,7 @@ void XMLFontStyleContextFontFaceUri::handleEmbeddedFont( const OUString& url, bo
uno::Reference< io::XInputStream > inputStream;
inputStream.set( storage->openStreamElement( url.copy( url.indexOf( '/' ) + 1 ), ::embed::ElementModes::READ ),
UNO_QUERY_THROW );
if (GetImport().addEmbeddedFont(inputStream, fontName, u"?", std::vector< unsigned char >(), eot))
if (GetImport().addEmbeddedFont(inputStream, fontName, font.styleName(), std::vector< unsigned char >(), eot))
GetImport().NotifyContainsEmbeddedFont();
inputStream->closeInput();
}
@@ -301,7 +308,7 @@ void XMLFontStyleContextFontFaceUri::handleEmbeddedFont( const ::css::uno::Seque
{
const uno::Reference< io::XInputStream > xInput( new comphelper::SequenceInputStream( rData ) );
const OUString fontName = font.familyName();
if (GetImport().addEmbeddedFont(xInput, fontName, u"?", std::vector< unsigned char >(), eot))
if (GetImport().addEmbeddedFont(xInput, fontName, font.styleName(), std::vector< unsigned char >(), eot))
GetImport().NotifyContainsEmbeddedFont();
xInput->closeInput();
}
@@ -60,6 +60,7 @@ public:
sal_Int32 nCharsetIdx ) const;
OUString familyName() const;
OUString styleName() const;
virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext(
sal_Int32 nElement,