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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <o3tl/sorted_vector.hxx>
21 #include <tools/fontenum.hxx>
22 #include <xmloff/xmlnamespace.hxx>
23 #include <xmloff/xmltoken.hxx>
24 #include <xmloff/xmluconv.hxx>
25 #include "fonthdl.hxx"
26 #include <xmloff/xmlexp.hxx>
27 #include <xmloff/XMLFontAutoStylePool.hxx>
28 #include <vcl/embeddedfontshelper.hxx>
29 #include <osl/file.hxx>
30 #include <sal/log.hxx>
31 #include <tools/diagnose_ex.h>
33 #include <com/sun/star/beans/XPropertySet.hpp>
34 #include <com/sun/star/embed/ElementModes.hpp>
35 #include <com/sun/star/embed/XTransactedObject.hpp>
36 #include <com/sun/star/embed/XStorage.hpp>
37 #include <com/sun/star/frame/XModel.hpp>
38 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
39 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
40 #include <com/sun/star/style/XStyle.hpp>
41 #include <com/sun/star/container/XNameAccess.hpp>
43 #include <XMLBase64Export.hxx>
44 #include <AutoStyleEntry.hxx>
45 #include <comphelper/hash.hxx>
47 using namespace ::com::sun::star
;
48 using namespace ::com::sun::star::uno
;
49 using namespace ::xmloff::token
;
53 class XMLFontAutoStylePoolEntry_Impl
60 rtl_TextEncoding eEnc
;
64 inline XMLFontAutoStylePoolEntry_Impl(
65 const OUString
& rName
,
66 const OUString
& rFamilyName
,
67 const OUString
& rStyleName
,
70 rtl_TextEncoding eEnc
);
72 inline XMLFontAutoStylePoolEntry_Impl(
73 const OUString
& rFamilyName
,
74 const OUString
& rStyleName
,
77 rtl_TextEncoding eEnc
);
79 const OUString
& GetName() const { return sName
; }
80 const OUString
& GetFamilyName() const { return sFamilyName
; }
81 const OUString
& GetStyleName() const { return sStyleName
; }
82 FontFamily
GetFamily() const { return nFamily
; }
83 FontPitch
GetPitch() const { return nPitch
; }
84 rtl_TextEncoding
GetEncoding() const { return eEnc
; }
89 inline XMLFontAutoStylePoolEntry_Impl::XMLFontAutoStylePoolEntry_Impl(
90 const OUString
& rName
,
91 const OUString
& rFamilyName
,
92 const OUString
& rStyleName
,
95 rtl_TextEncoding eE
) :
97 sFamilyName( rFamilyName
),
98 sStyleName( rStyleName
),
105 inline XMLFontAutoStylePoolEntry_Impl::XMLFontAutoStylePoolEntry_Impl(
106 const OUString
& rFamilyName
,
107 const OUString
& rStyleName
,
110 rtl_TextEncoding eE
) :
111 sFamilyName( rFamilyName
),
112 sStyleName( rStyleName
),
121 struct XMLFontAutoStylePoolEntryCmp_Impl
{
123 std::unique_ptr
<XMLFontAutoStylePoolEntry_Impl
> const& r1
,
124 std::unique_ptr
<XMLFontAutoStylePoolEntry_Impl
> const& r2
) const
126 bool bEnc1(r1
->GetEncoding() != RTL_TEXTENCODING_SYMBOL
);
127 bool bEnc2(r2
->GetEncoding() != RTL_TEXTENCODING_SYMBOL
);
129 return bEnc1
< bEnc2
;
130 else if( r1
->GetPitch() != r2
->GetPitch() )
131 return r1
->GetPitch() < r2
->GetPitch();
132 else if( r1
->GetFamily() != r2
->GetFamily() )
133 return r1
->GetFamily() < r2
->GetFamily();
136 sal_Int32 nCmp
= r1
->GetFamilyName().compareTo( r2
->GetFamilyName() );
138 return r1
->GetStyleName().compareTo( r2
->GetStyleName() ) < 0;
147 class XMLFontAutoStylePool_Impl
: public o3tl::sorted_vector
<std::unique_ptr
<XMLFontAutoStylePoolEntry_Impl
>, XMLFontAutoStylePoolEntryCmp_Impl
>
151 XMLFontAutoStylePool::XMLFontAutoStylePool(SvXMLExport
& rExp
, bool bTryToEmbedFonts
) :
153 m_pFontAutoStylePool( new XMLFontAutoStylePool_Impl
),
154 m_bTryToEmbedFonts( bTryToEmbedFonts
),
155 m_bEmbedUsedOnly(false),
156 m_bEmbedLatinScript(true),
157 m_bEmbedAsianScript(true),
158 m_bEmbedComplexScript(true)
162 XMLFontAutoStylePool::~XMLFontAutoStylePool()
166 OUString
XMLFontAutoStylePool::Add(
167 const OUString
& rFamilyName
,
168 const OUString
& rStyleName
,
171 rtl_TextEncoding eEnc
)
174 XMLFontAutoStylePoolEntry_Impl
aTmp( rFamilyName
, rStyleName
, nFamily
,
176 XMLFontAutoStylePool_Impl::const_iterator it
= m_pFontAutoStylePool
->find( &aTmp
);
177 if( it
!= m_pFontAutoStylePool
->end() )
179 sPoolName
= (*it
)->GetName();
184 sal_Int32 nLen
= rFamilyName
.indexOf( ';' );
191 sName
= rFamilyName
.copy( 0, nLen
);
192 sName
= sName
.trim();
195 if( sName
.isEmpty() )
198 if( m_aNames
.find(sName
) != m_aNames
.end() )
200 sal_Int32 nCount
= 1;
201 OUString
sPrefix( sName
);
202 sName
= sPrefix
+ OUString::number( nCount
);
203 while( m_aNames
.find(sName
) != m_aNames
.end() )
205 sName
= sPrefix
+ OUString::number( ++nCount
);
209 std::unique_ptr
<XMLFontAutoStylePoolEntry_Impl
> pEntry(
210 new XMLFontAutoStylePoolEntry_Impl( sName
, rFamilyName
, rStyleName
,
211 nFamily
, nPitch
, eEnc
));
212 m_pFontAutoStylePool
->insert( std::move(pEntry
) );
213 m_aNames
.insert(sName
);
219 OUString
XMLFontAutoStylePool::Find(
220 const OUString
& rFamilyName
,
221 const OUString
& rStyleName
,
224 rtl_TextEncoding eEnc
) const
227 XMLFontAutoStylePoolEntry_Impl
aTmp( rFamilyName
, rStyleName
, nFamily
,
229 XMLFontAutoStylePool_Impl::const_iterator it
= m_pFontAutoStylePool
->find( &aTmp
);
230 if( it
!= m_pFontAutoStylePool
->end() )
232 sName
= (*it
)->GetName();
241 OUString
lcl_checkFontFile( const OUString
&fileUrl
)
243 osl::DirectoryItem aDirItem
;
244 if( osl::DirectoryItem::get( fileUrl
, aDirItem
) == osl::File::E_None
)
246 osl::FileStatus
aStatus( osl_FileStatus_Mask_Type
);
247 if( aDirItem
.getFileStatus( aStatus
) == osl::File::E_None
)
249 if( !aStatus
.isDirectory() )
256 /// Contains information about a single variant of an embedded font.
257 struct EmbeddedFontInfo
260 FontWeight eWeight
= WEIGHT_NORMAL
;
261 FontItalic eItalic
= ITALIC_NONE
;
264 /// Converts FontWeight to CSS-compatible string representation.
265 OUString
FontWeightToString(FontWeight eWeight
)
282 /// Converts FontItalic to CSS-compatible string representation.
283 OUString
FontItalicToString(FontItalic eWeight
)
302 std::unordered_set
<OUString
> XMLFontAutoStylePool::getUsedFontList()
304 std::unordered_set
<OUString
> aReturnSet
;
306 uno::Reference
<style::XStyleFamiliesSupplier
> xFamiliesSupp(GetExport().GetModel(), UNO_QUERY
);
307 if (!xFamiliesSupp
.is())
310 // Check styles first
311 uno::Reference
<container::XNameAccess
> xFamilies(xFamiliesSupp
->getStyleFamilies());
314 const uno::Sequence
<OUString
> aFamilyNames
= xFamilies
->getElementNames();
315 for (OUString
const & sFamilyName
: aFamilyNames
)
317 uno::Reference
<container::XNameAccess
> xStyleContainer
;
318 xFamilies
->getByName(sFamilyName
) >>= xStyleContainer
;
320 if (xStyleContainer
.is())
322 const uno::Sequence
<OUString
> aStyleNames
= xStyleContainer
->getElementNames();
323 for (OUString
const & rName
: aStyleNames
)
325 uno::Reference
<style::XStyle
> xStyle
;
326 xStyleContainer
->getByName(rName
) >>= xStyle
;
327 if (xStyle
->isInUse())
329 uno::Reference
<beans::XPropertySet
> xPropertySet(xStyle
, UNO_QUERY
);
330 if (xPropertySet
.is())
332 uno::Reference
<beans::XPropertySetInfo
> xInfo(xPropertySet
->getPropertySetInfo());
333 if (m_bEmbedLatinScript
&& xInfo
->hasPropertyByName("CharFontName"))
335 OUString sCharFontName
;
336 Any aFontAny
= xPropertySet
->getPropertyValue("CharFontName");
337 aFontAny
>>= sCharFontName
;
338 if (!sCharFontName
.isEmpty())
339 aReturnSet
.insert(sCharFontName
);
341 if (m_bEmbedAsianScript
&& xInfo
->hasPropertyByName("CharFontNameAsian"))
343 OUString sCharFontNameAsian
;
344 Any aFontAny
= xPropertySet
->getPropertyValue("CharFontNameAsian");
345 aFontAny
>>= sCharFontNameAsian
;
346 if (!sCharFontNameAsian
.isEmpty())
347 aReturnSet
.insert(sCharFontNameAsian
);
349 if (m_bEmbedComplexScript
&& xInfo
->hasPropertyByName("CharFontNameComplex"))
351 OUString sCharFontNameComplex
;
352 Any aFontAny
= xPropertySet
->getPropertyValue("CharFontNameComplex");
353 aFontAny
>>= sCharFontNameComplex
;
354 if (!sCharFontNameComplex
.isEmpty())
355 aReturnSet
.insert(sCharFontNameComplex
);
364 // make sure auto-styles are collected
365 GetExport().collectAutoStyles();
367 // Check auto-styles for fonts
368 std::vector
<xmloff::AutoStyleEntry
> aAutoStyleEntries
= GetExport().GetAutoStylePool()->GetAutoStyleEntries();
369 for (auto const & rAutoStyleEntry
: aAutoStyleEntries
)
371 for (auto const & rPair
: rAutoStyleEntry
.m_aXmlProperties
)
373 if (rPair
.first
== "font-name" ||
374 rPair
.first
== "font-weight-asian" ||
375 rPair
.first
== "font-weight-complex")
377 if (rPair
.second
.has
<OUString
>())
379 OUString sFontName
= rPair
.second
.get
<OUString
>();
380 if (!sFontName
.isEmpty())
381 aReturnSet
.insert(sFontName
);
390 void XMLFontAutoStylePool::exportXML()
392 SvXMLElementExport
aElem(GetExport(), XML_NAMESPACE_OFFICE
,
397 XMLFontFamilyNamePropHdl aFamilyNameHdl
;
398 XMLFontFamilyPropHdl aFamilyHdl
;
399 XMLFontPitchPropHdl aPitchHdl
;
400 XMLFontEncodingPropHdl aEncHdl
;
401 const SvXMLUnitConverter
& rUnitConv
= GetExport().GetMM100UnitConverter();
403 std::map
<OUString
, OUString
> fontFilesMap
; // our url to document url
405 std::unordered_set
<OUString
> aUsedFontNames
;
406 if (m_bEmbedUsedOnly
)
407 aUsedFontNames
= getUsedFontList();
409 for (const auto& pEntry
: *m_pFontAutoStylePool
)
411 GetExport().AddAttribute(XML_NAMESPACE_STYLE
, XML_NAME
, pEntry
->GetName());
413 aAny
<<= pEntry
->GetFamilyName();
414 if (aFamilyNameHdl
.exportXML(sTmp
, aAny
, rUnitConv
))
415 GetExport().AddAttribute(XML_NAMESPACE_SVG
,
416 XML_FONT_FAMILY
, sTmp
);
418 const OUString
& rStyleName
= pEntry
->GetStyleName();
419 if (!rStyleName
.isEmpty())
420 GetExport().AddAttribute(XML_NAMESPACE_STYLE
,
424 aAny
<<= static_cast<sal_Int16
>(pEntry
->GetFamily());
425 if (aFamilyHdl
.exportXML(sTmp
, aAny
, rUnitConv
))
427 GetExport().AddAttribute(XML_NAMESPACE_STYLE
,
428 XML_FONT_FAMILY_GENERIC
, sTmp
);
430 aAny
<<= static_cast<sal_Int16
>(pEntry
->GetPitch());
431 if (aPitchHdl
.exportXML(sTmp
, aAny
, rUnitConv
))
433 GetExport().AddAttribute(XML_NAMESPACE_STYLE
,
434 XML_FONT_PITCH
, sTmp
);
437 aAny
<<= static_cast<sal_Int16
>(pEntry
->GetEncoding());
438 if (aEncHdl
.exportXML( sTmp
, aAny
, rUnitConv
))
440 GetExport().AddAttribute(XML_NAMESPACE_STYLE
,
441 XML_FONT_CHARSET
, sTmp
);
444 SvXMLElementExport
aElement(GetExport(), XML_NAMESPACE_STYLE
,
445 XML_FONT_FACE
, true, true);
447 if (m_bTryToEmbedFonts
)
449 const bool bExportFlat(GetExport().getExportFlags() & SvXMLExportFlags::EMBEDDED
);
450 std::vector
<EmbeddedFontInfo
> aEmbeddedFonts
;
451 static const std::vector
<std::pair
<FontWeight
, FontItalic
>> aCombinations
=
453 { WEIGHT_NORMAL
, ITALIC_NONE
},
454 { WEIGHT_BOLD
, ITALIC_NONE
},
455 { WEIGHT_NORMAL
, ITALIC_NORMAL
},
456 { WEIGHT_BOLD
, ITALIC_NORMAL
},
459 for (auto const & aCombinationPair
: aCombinations
)
461 // Embed font if at least viewing is allowed (in which case the opening app must check
462 // the font license rights too and open either read-only or not use the font for editing).
463 OUString sFileUrl
= EmbeddedFontsHelper::fontFileUrl(
464 pEntry
->GetFamilyName(), pEntry
->GetFamily(),
465 aCombinationPair
.second
, aCombinationPair
.first
, pEntry
->GetPitch(),
466 EmbeddedFontsHelper::FontRights::ViewingAllowed
);
467 if (sFileUrl
.isEmpty())
470 // When embedded only is not set or font is used
471 if (!m_bEmbedUsedOnly
||
472 aUsedFontNames
.find(pEntry
->GetFamilyName()) != aUsedFontNames
.end())
474 if (!fontFilesMap
.count(sFileUrl
))
476 const OUString docUrl
= bExportFlat
?
477 lcl_checkFontFile(sFileUrl
) : embedFontFile(sFileUrl
, pEntry
->GetFamilyName());
478 if (!docUrl
.isEmpty())
479 fontFilesMap
[sFileUrl
] = docUrl
;
481 continue; // --> failed to embed
483 EmbeddedFontInfo aEmbeddedFont
;
484 aEmbeddedFont
.aURL
= sFileUrl
;
485 aEmbeddedFont
.eWeight
= aCombinationPair
.first
;
486 aEmbeddedFont
.eItalic
= aCombinationPair
.second
;
487 aEmbeddedFonts
.push_back(aEmbeddedFont
);
490 if (!aEmbeddedFonts
.empty())
492 SvXMLElementExport
fontFaceSrc(GetExport(), XML_NAMESPACE_SVG
, XML_FONT_FACE_SRC
, true, true);
493 for (EmbeddedFontInfo
const & rEmbeddedFont
: aEmbeddedFonts
)
495 if (fontFilesMap
.count(rEmbeddedFont
.aURL
))
499 GetExport().AddAttribute(XML_NAMESPACE_XLINK
, XML_HREF
,
500 fontFilesMap
[rEmbeddedFont
.aURL
]);
501 GetExport().AddAttribute(XML_NAMESPACE_XLINK
, XML_TYPE
, "simple");
504 // Help consumers of our output by telling them which
505 // font file is which one.
506 GetExport().AddAttribute(XML_NAMESPACE_LO_EXT
, XML_FONT_STYLE
,
507 FontItalicToString(rEmbeddedFont
.eItalic
));
508 GetExport().AddAttribute(XML_NAMESPACE_LO_EXT
, XML_FONT_WEIGHT
,
509 FontWeightToString(rEmbeddedFont
.eWeight
));
511 SvXMLElementExport
fontFaceUri(GetExport(), XML_NAMESPACE_SVG
,
512 XML_FONT_FACE_URI
, true, true);
516 const uno::Reference
<ucb::XSimpleFileAccess
> xFileAccess(
517 ucb::SimpleFileAccess::create(GetExport().getComponentContext()));
520 const uno::Reference
<io::XInputStream
> xInput(xFileAccess
->openFileRead(fontFilesMap
[rEmbeddedFont
.aURL
]));
521 XMLBase64Export
aBase64Exp(GetExport());
522 aBase64Exp
.exportOfficeBinaryDataElement(xInput
);
524 catch (const uno::Exception
&)
526 // opening the file failed, ignore
530 GetExport().AddAttribute(XML_NAMESPACE_SVG
, XML_STRING
, "truetype");
531 SvXMLElementExport
fontFaceFormat(GetExport(), XML_NAMESPACE_SVG
,
532 XML_FONT_FACE_FORMAT
, true, true);
540 static OUString
getFreeFontName(uno::Reference
<embed::XStorage
> const & rxStorage
, OUString
const & rFamilyName
)
547 rFamilyName
.replaceAll(" ", "_") + "_" +
548 OUString::number(nIndex
) + ".ttf";
550 } while (rxStorage
->hasByName(sName
));
555 static OString
convertToHashString(std::vector
<unsigned char> const & rHash
)
557 std::stringstream aStringStream
;
558 for (auto const & rByte
: rHash
)
560 aStringStream
<< std::setw(2) << std::setfill('0') << std::hex
<< int(rByte
);
563 return aStringStream
.str().c_str();
566 static OString
getFileHash(OUString
const & rFileUrl
)
569 osl::File
aFile(rFileUrl
);
570 if (aFile
.open(osl_File_OpenFlag_Read
) != osl::File::E_None
)
573 comphelper::Hash
aHashEngine(comphelper::HashType::SHA512
);
576 sal_Int8 aBuffer
[4096];
577 sal_uInt64 nReadSize
;
579 if (aFile
.isEndOfFile(&bEof
) != osl::File::E_None
)
581 SAL_WARN("xmloff", "Error reading font file " << rFileUrl
);
586 if (aFile
.read(aBuffer
, 4096, nReadSize
) != osl::File::E_None
)
588 SAL_WARN("xmloff", "Error reading font file " << rFileUrl
);
593 aHashEngine
.update(reinterpret_cast<unsigned char*>(aBuffer
), nReadSize
);
595 return convertToHashString(aHashEngine
.finalize());
598 OUString
XMLFontAutoStylePool::embedFontFile(OUString
const & fileUrl
, OUString
const & rFamilyName
)
602 OString sHashString
= getFileHash(fileUrl
);
603 if (m_aEmbeddedFontFiles
.find(sHashString
) != m_aEmbeddedFontFiles
.end())
604 return m_aEmbeddedFontFiles
.at(sHashString
);
606 osl::File
file( fileUrl
);
607 if( file
.open( osl_File_OpenFlag_Read
) != osl::File::E_None
)
610 if ( !GetExport().GetTargetStorage().is() )
613 uno::Reference
< embed::XStorage
> storage
;
614 storage
.set( GetExport().GetTargetStorage()->openStorageElement( "Fonts",
615 ::embed::ElementModes::WRITE
), uno::UNO_SET_THROW
);
617 OUString name
= getFreeFontName(storage
, rFamilyName
);
619 uno::Reference
< io::XOutputStream
> outputStream
;
620 outputStream
.set( storage
->openStreamElement( name
, ::embed::ElementModes::WRITE
), UNO_QUERY_THROW
);
621 uno::Reference
< beans::XPropertySet
> propertySet( outputStream
, uno::UNO_QUERY
);
622 assert( propertySet
.is());
623 propertySet
->setPropertyValue( "MediaType", uno::makeAny( OUString( "application/x-font-ttf" ))); // TODO
626 sal_Int8 buffer
[ 4096 ];
629 if( file
.isEndOfFile( &eof
) != osl::File::E_None
)
631 SAL_WARN( "xmloff", "Error reading font file " << fileUrl
);
632 outputStream
->closeOutput();
637 if( file
.read( buffer
, 4096, readSize
) != osl::File::E_None
)
639 SAL_WARN( "xmloff", "Error reading font file " << fileUrl
);
640 outputStream
->closeOutput();
645 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
646 outputStream
->writeBytes(uno::Sequence
<sal_Int8
>(buffer
, readSize
));
648 outputStream
->closeOutput();
651 Reference
< embed::XTransactedObject
> transaction( storage
, UNO_QUERY
);
652 if( transaction
.is())
654 transaction
->commit();
655 OUString sInternalName
= "Fonts/" + name
;
656 m_aEmbeddedFontFiles
.emplace(sHashString
, sInternalName
);
657 return sInternalName
;
660 } catch( const Exception
& )
662 TOOLS_WARN_EXCEPTION( "xmloff", "Exception when embedding a font file" );
668 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */