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>
23 #include <xmloff/xmlnamespace.hxx>
24 #include <xmloff/xmltoken.hxx>
25 #include <xmloff/xmluconv.hxx>
26 #include "fonthdl.hxx"
27 #include <xmloff/xmlexp.hxx>
28 #include <xmloff/XMLFontAutoStylePool.hxx>
29 #include <vcl/embeddedfontshelper.hxx>
30 #include <osl/file.hxx>
31 #include <sal/log.hxx>
32 #include <comphelper/diagnose_ex.hxx>
34 #include <com/sun/star/beans/XPropertySet.hpp>
35 #include <com/sun/star/embed/ElementModes.hpp>
36 #include <com/sun/star/embed/XTransactedObject.hpp>
37 #include <com/sun/star/embed/XStorage.hpp>
38 #include <com/sun/star/frame/XModel.hpp>
39 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
40 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
41 #include <com/sun/star/style/XStyle.hpp>
42 #include <com/sun/star/container/XNameAccess.hpp>
44 #include <XMLBase64Export.hxx>
45 #include <AutoStyleEntry.hxx>
46 #include <comphelper/hash.hxx>
48 using namespace ::com::sun::star
;
49 using namespace ::com::sun::star::uno
;
50 using namespace ::xmloff::token
;
54 class XMLFontAutoStylePoolEntry_Impl
61 rtl_TextEncoding eEnc
;
65 inline XMLFontAutoStylePoolEntry_Impl(
71 rtl_TextEncoding eEnc
);
73 inline XMLFontAutoStylePoolEntry_Impl(
78 rtl_TextEncoding eEnc
);
80 const OUString
& GetName() const { return sName
; }
81 const OUString
& GetFamilyName() const { return sFamilyName
; }
82 const OUString
& GetStyleName() const { return sStyleName
; }
83 FontFamily
GetFamily() const { return nFamily
; }
84 FontPitch
GetPitch() const { return nPitch
; }
85 rtl_TextEncoding
GetEncoding() const { return eEnc
; }
90 inline XMLFontAutoStylePoolEntry_Impl::XMLFontAutoStylePoolEntry_Impl(
96 rtl_TextEncoding eE
) :
97 sName(std::move( aName
)),
98 sFamilyName(std::move( aFamilyName
)),
99 sStyleName(std::move( aStyleName
)),
106 inline XMLFontAutoStylePoolEntry_Impl::XMLFontAutoStylePoolEntry_Impl(
107 OUString rFamilyName
,
111 rtl_TextEncoding eE
) :
112 sFamilyName(std::move( rFamilyName
)),
113 sStyleName(std::move( rStyleName
)),
122 struct XMLFontAutoStylePoolEntryCmp_Impl
{
124 std::unique_ptr
<XMLFontAutoStylePoolEntry_Impl
> const& r1
,
125 std::unique_ptr
<XMLFontAutoStylePoolEntry_Impl
> const& r2
) const
127 bool bEnc1(r1
->GetEncoding() != RTL_TEXTENCODING_SYMBOL
);
128 bool bEnc2(r2
->GetEncoding() != RTL_TEXTENCODING_SYMBOL
);
130 return bEnc1
< bEnc2
;
131 else if( r1
->GetPitch() != r2
->GetPitch() )
132 return r1
->GetPitch() < r2
->GetPitch();
133 else if( r1
->GetFamily() != r2
->GetFamily() )
134 return r1
->GetFamily() < r2
->GetFamily();
137 sal_Int32 nCmp
= r1
->GetFamilyName().compareTo( r2
->GetFamilyName() );
139 return r1
->GetStyleName().compareTo( r2
->GetStyleName() ) < 0;
148 class XMLFontAutoStylePool_Impl
: public o3tl::sorted_vector
<std::unique_ptr
<XMLFontAutoStylePoolEntry_Impl
>, XMLFontAutoStylePoolEntryCmp_Impl
>
152 XMLFontAutoStylePool::XMLFontAutoStylePool(SvXMLExport
& rExp
, bool bTryToEmbedFonts
) :
154 m_pFontAutoStylePool( new XMLFontAutoStylePool_Impl
),
155 m_bTryToEmbedFonts( bTryToEmbedFonts
),
156 m_bEmbedUsedOnly(false),
157 m_bEmbedLatinScript(true),
158 m_bEmbedAsianScript(true),
159 m_bEmbedComplexScript(true)
163 XMLFontAutoStylePool::~XMLFontAutoStylePool()
167 OUString
XMLFontAutoStylePool::Add(
168 const OUString
& rFamilyName
,
169 const OUString
& rStyleName
,
172 rtl_TextEncoding eEnc
)
175 XMLFontAutoStylePoolEntry_Impl
aTmp( rFamilyName
, rStyleName
, nFamily
,
177 XMLFontAutoStylePool_Impl::const_iterator it
= m_pFontAutoStylePool
->find( &aTmp
);
178 if( it
!= m_pFontAutoStylePool
->end() )
180 sPoolName
= (*it
)->GetName();
185 sal_Int32 nLen
= rFamilyName
.indexOf( ';' );
192 sName
= rFamilyName
.copy( 0, nLen
);
193 sName
= sName
.trim();
196 if( sName
.isEmpty() )
199 if( m_aNames
.find(sName
) != m_aNames
.end() )
201 sal_Int32 nCount
= 1;
202 OUString
sPrefix( sName
);
203 sName
= sPrefix
+ OUString::number( nCount
);
204 while( m_aNames
.find(sName
) != m_aNames
.end() )
206 sName
= sPrefix
+ OUString::number( ++nCount
);
210 std::unique_ptr
<XMLFontAutoStylePoolEntry_Impl
> pEntry(
211 new XMLFontAutoStylePoolEntry_Impl( sName
, rFamilyName
, rStyleName
,
212 nFamily
, nPitch
, eEnc
));
213 m_pFontAutoStylePool
->insert( std::move(pEntry
) );
214 m_aNames
.insert(sName
);
220 OUString
XMLFontAutoStylePool::Find(
221 const OUString
& rFamilyName
,
222 const OUString
& rStyleName
,
225 rtl_TextEncoding eEnc
) const
228 XMLFontAutoStylePoolEntry_Impl
aTmp( rFamilyName
, rStyleName
, nFamily
,
230 XMLFontAutoStylePool_Impl::const_iterator it
= m_pFontAutoStylePool
->find( &aTmp
);
231 if( it
!= m_pFontAutoStylePool
->end() )
233 sName
= (*it
)->GetName();
242 OUString
lcl_checkFontFile( const OUString
&fileUrl
)
244 osl::DirectoryItem aDirItem
;
245 if( osl::DirectoryItem::get( fileUrl
, aDirItem
) == osl::File::E_None
)
247 osl::FileStatus
aStatus( osl_FileStatus_Mask_Type
);
248 if( aDirItem
.getFileStatus( aStatus
) == osl::File::E_None
)
250 if( !aStatus
.isDirectory() )
257 /// Contains information about a single variant of an embedded font.
258 struct EmbeddedFontInfo
261 FontWeight eWeight
= WEIGHT_NORMAL
;
262 FontItalic eItalic
= ITALIC_NONE
;
265 /// Converts FontWeight to CSS-compatible string representation.
266 OUString
FontWeightToString(FontWeight eWeight
)
283 /// Converts FontItalic to CSS-compatible string representation.
284 OUString
FontItalicToString(FontItalic eWeight
)
303 std::unordered_set
<OUString
> XMLFontAutoStylePool::getUsedFontList()
305 std::unordered_set
<OUString
> aReturnSet
;
307 uno::Reference
<style::XStyleFamiliesSupplier
> xFamiliesSupp(GetExport().GetModel(), UNO_QUERY
);
308 if (!xFamiliesSupp
.is())
311 // Check styles first
312 uno::Reference
<container::XNameAccess
> xFamilies(xFamiliesSupp
->getStyleFamilies());
315 const uno::Sequence
<OUString
> aFamilyNames
= xFamilies
->getElementNames();
316 for (OUString
const & sFamilyName
: aFamilyNames
)
318 uno::Reference
<container::XNameAccess
> xStyleContainer
;
319 xFamilies
->getByName(sFamilyName
) >>= xStyleContainer
;
321 if (xStyleContainer
.is())
323 const uno::Sequence
<OUString
> aStyleNames
= xStyleContainer
->getElementNames();
324 for (OUString
const & rName
: aStyleNames
)
326 uno::Reference
<style::XStyle
> xStyle
;
327 xStyleContainer
->getByName(rName
) >>= xStyle
;
328 if (xStyle
->isInUse())
330 uno::Reference
<beans::XPropertySet
> xPropertySet(xStyle
, UNO_QUERY
);
331 uno::Reference
<beans::XPropertySetInfo
> xInfo(xPropertySet
? xPropertySet
->getPropertySetInfo() : nullptr);
334 if (m_bEmbedLatinScript
&& xInfo
->hasPropertyByName("CharFontName"))
336 OUString sCharFontName
;
337 Any aFontAny
= xPropertySet
->getPropertyValue("CharFontName");
338 aFontAny
>>= sCharFontName
;
339 if (!sCharFontName
.isEmpty())
340 aReturnSet
.insert(sCharFontName
);
342 if (m_bEmbedAsianScript
&& xInfo
->hasPropertyByName("CharFontNameAsian"))
344 OUString sCharFontNameAsian
;
345 Any aFontAny
= xPropertySet
->getPropertyValue("CharFontNameAsian");
346 aFontAny
>>= sCharFontNameAsian
;
347 if (!sCharFontNameAsian
.isEmpty())
348 aReturnSet
.insert(sCharFontNameAsian
);
350 if (m_bEmbedComplexScript
&& xInfo
->hasPropertyByName("CharFontNameComplex"))
352 OUString sCharFontNameComplex
;
353 Any aFontAny
= xPropertySet
->getPropertyValue("CharFontNameComplex");
354 aFontAny
>>= sCharFontNameComplex
;
355 if (!sCharFontNameComplex
.isEmpty())
356 aReturnSet
.insert(sCharFontNameComplex
);
365 // make sure auto-styles are collected
366 GetExport().collectAutoStyles();
368 // Check auto-styles for fonts
369 std::vector
<xmloff::AutoStyleEntry
> aAutoStyleEntries
= GetExport().GetAutoStylePool()->GetAutoStyleEntries();
370 for (auto const & rAutoStyleEntry
: aAutoStyleEntries
)
372 for (auto const & rPair
: rAutoStyleEntry
.m_aXmlProperties
)
374 if (rPair
.first
== "font-name" ||
375 rPair
.first
== "font-weight-asian" ||
376 rPair
.first
== "font-weight-complex")
378 if (rPair
.second
.has
<OUString
>())
380 OUString sFontName
= rPair
.second
.get
<OUString
>();
381 if (!sFontName
.isEmpty())
382 aReturnSet
.insert(sFontName
);
391 void XMLFontAutoStylePool::exportXML()
393 SvXMLElementExport
aElem(GetExport(), XML_NAMESPACE_OFFICE
,
398 XMLFontFamilyNamePropHdl aFamilyNameHdl
;
399 XMLFontFamilyPropHdl aFamilyHdl
;
400 XMLFontPitchPropHdl aPitchHdl
;
401 XMLFontEncodingPropHdl aEncHdl
;
402 const SvXMLUnitConverter
& rUnitConv
= GetExport().GetMM100UnitConverter();
404 std::map
<OUString
, OUString
> fontFilesMap
; // our url to document url
406 std::unordered_set
<OUString
> aUsedFontNames
;
407 if (m_bEmbedUsedOnly
)
408 aUsedFontNames
= getUsedFontList();
410 // Sort <style:font-face> elements based on their style:name attribute.
411 std::vector
<XMLFontAutoStylePoolEntry_Impl
*> aFontAutoStyles
;
412 for (const auto& pEntry
: *m_pFontAutoStylePool
)
414 aFontAutoStyles
.push_back(pEntry
.get());
417 aFontAutoStyles
.begin(), aFontAutoStyles
.end(),
418 [](const XMLFontAutoStylePoolEntry_Impl
* pA
, XMLFontAutoStylePoolEntry_Impl
* pB
) -> bool {
419 return pA
->GetName() < pB
->GetName();
422 for (const auto& pEntry
: aFontAutoStyles
)
424 GetExport().AddAttribute(XML_NAMESPACE_STYLE
, XML_NAME
, pEntry
->GetName());
426 aAny
<<= pEntry
->GetFamilyName();
427 if (aFamilyNameHdl
.exportXML(sTmp
, aAny
, rUnitConv
))
428 GetExport().AddAttribute(XML_NAMESPACE_SVG
,
429 XML_FONT_FAMILY
, sTmp
);
431 const OUString
& rStyleName
= pEntry
->GetStyleName();
432 if (!rStyleName
.isEmpty())
433 GetExport().AddAttribute(XML_NAMESPACE_STYLE
,
437 aAny
<<= static_cast<sal_Int16
>(pEntry
->GetFamily());
438 if (aFamilyHdl
.exportXML(sTmp
, aAny
, rUnitConv
))
440 GetExport().AddAttribute(XML_NAMESPACE_STYLE
,
441 XML_FONT_FAMILY_GENERIC
, sTmp
);
443 aAny
<<= static_cast<sal_Int16
>(pEntry
->GetPitch());
444 if (aPitchHdl
.exportXML(sTmp
, aAny
, rUnitConv
))
446 GetExport().AddAttribute(XML_NAMESPACE_STYLE
,
447 XML_FONT_PITCH
, sTmp
);
450 aAny
<<= static_cast<sal_Int16
>(pEntry
->GetEncoding());
451 if (aEncHdl
.exportXML( sTmp
, aAny
, rUnitConv
))
453 GetExport().AddAttribute(XML_NAMESPACE_STYLE
,
454 XML_FONT_CHARSET
, sTmp
);
457 SvXMLElementExport
aElement(GetExport(), XML_NAMESPACE_STYLE
,
458 XML_FONT_FACE
, true, true);
460 if (m_bTryToEmbedFonts
)
462 const bool bExportFlat(GetExport().getExportFlags() & SvXMLExportFlags::EMBEDDED
);
463 std::vector
<EmbeddedFontInfo
> aEmbeddedFonts
;
464 static const std::vector
<std::pair
<FontWeight
, FontItalic
>> aCombinations
=
466 { WEIGHT_NORMAL
, ITALIC_NONE
},
467 { WEIGHT_BOLD
, ITALIC_NONE
},
468 { WEIGHT_NORMAL
, ITALIC_NORMAL
},
469 { WEIGHT_BOLD
, ITALIC_NORMAL
},
472 for (auto const & aCombinationPair
: aCombinations
)
474 // Embed font if at least viewing is allowed (in which case the opening app must check
475 // the font license rights too and open either read-only or not use the font for editing).
476 OUString sFileUrl
= EmbeddedFontsHelper::fontFileUrl(
477 pEntry
->GetFamilyName(), pEntry
->GetFamily(),
478 aCombinationPair
.second
, aCombinationPair
.first
, pEntry
->GetPitch(),
479 EmbeddedFontsHelper::FontRights::ViewingAllowed
);
480 if (sFileUrl
.isEmpty())
483 // When embedded only is not set or font is used
484 if (!m_bEmbedUsedOnly
||
485 aUsedFontNames
.find(pEntry
->GetFamilyName()) != aUsedFontNames
.end())
487 if (!fontFilesMap
.count(sFileUrl
))
489 const OUString docUrl
= bExportFlat
?
490 lcl_checkFontFile(sFileUrl
) : embedFontFile(sFileUrl
, pEntry
->GetFamilyName());
491 if (!docUrl
.isEmpty())
492 fontFilesMap
[sFileUrl
] = docUrl
;
494 continue; // --> failed to embed
496 EmbeddedFontInfo aEmbeddedFont
;
497 aEmbeddedFont
.aURL
= sFileUrl
;
498 aEmbeddedFont
.eWeight
= aCombinationPair
.first
;
499 aEmbeddedFont
.eItalic
= aCombinationPair
.second
;
500 aEmbeddedFonts
.push_back(aEmbeddedFont
);
503 if (!aEmbeddedFonts
.empty())
505 SvXMLElementExport
fontFaceSrc(GetExport(), XML_NAMESPACE_SVG
, XML_FONT_FACE_SRC
, true, true);
506 for (EmbeddedFontInfo
const & rEmbeddedFont
: aEmbeddedFonts
)
508 if (fontFilesMap
.count(rEmbeddedFont
.aURL
))
512 GetExport().AddAttribute(XML_NAMESPACE_XLINK
, XML_HREF
,
513 fontFilesMap
[rEmbeddedFont
.aURL
]);
514 GetExport().AddAttribute(XML_NAMESPACE_XLINK
, XML_TYPE
, "simple");
517 // Help consumers of our output by telling them which
518 // font file is which one.
519 GetExport().AddAttribute(XML_NAMESPACE_LO_EXT
, XML_FONT_STYLE
,
520 FontItalicToString(rEmbeddedFont
.eItalic
));
521 GetExport().AddAttribute(XML_NAMESPACE_LO_EXT
, XML_FONT_WEIGHT
,
522 FontWeightToString(rEmbeddedFont
.eWeight
));
524 SvXMLElementExport
fontFaceUri(GetExport(), XML_NAMESPACE_SVG
,
525 XML_FONT_FACE_URI
, true, true);
529 const uno::Reference
<ucb::XSimpleFileAccess
> xFileAccess(
530 ucb::SimpleFileAccess::create(GetExport().getComponentContext()));
533 const uno::Reference
<io::XInputStream
> xInput(xFileAccess
->openFileRead(fontFilesMap
[rEmbeddedFont
.aURL
]));
534 XMLBase64Export
aBase64Exp(GetExport());
535 aBase64Exp
.exportOfficeBinaryDataElement(xInput
);
537 catch (const uno::Exception
&)
539 // opening the file failed, ignore
543 GetExport().AddAttribute(XML_NAMESPACE_SVG
, XML_STRING
, "truetype");
544 SvXMLElementExport
fontFaceFormat(GetExport(), XML_NAMESPACE_SVG
,
545 XML_FONT_FACE_FORMAT
, true, true);
553 static OUString
getFreeFontName(uno::Reference
<embed::XStorage
> const & rxStorage
, OUString
const & rFamilyName
)
560 rFamilyName
.replaceAll(" ", "_") + "_" +
561 OUString::number(nIndex
) + ".ttf";
563 } while (rxStorage
->hasByName(sName
));
568 static OString
convertToHashString(std::vector
<unsigned char> const & rHash
)
570 std::stringstream aStringStream
;
571 for (auto const & rByte
: rHash
)
573 aStringStream
<< std::setw(2) << std::setfill('0') << std::hex
<< int(rByte
);
576 return OString(aStringStream
.str());
579 static OString
getFileHash(OUString
const & rFileUrl
)
582 osl::File
aFile(rFileUrl
);
583 if (aFile
.open(osl_File_OpenFlag_Read
) != osl::File::E_None
)
586 comphelper::Hash
aHashEngine(comphelper::HashType::SHA512
);
589 sal_Int8 aBuffer
[4096];
590 sal_uInt64 nReadSize
;
592 if (aFile
.isEndOfFile(&bEof
) != osl::File::E_None
)
594 SAL_WARN("xmloff", "Error reading font file " << rFileUrl
);
599 if (aFile
.read(aBuffer
, 4096, nReadSize
) != osl::File::E_None
)
601 SAL_WARN("xmloff", "Error reading font file " << rFileUrl
);
606 aHashEngine
.update(reinterpret_cast<unsigned char*>(aBuffer
), nReadSize
);
608 return convertToHashString(aHashEngine
.finalize());
611 OUString
XMLFontAutoStylePool::embedFontFile(OUString
const & fileUrl
, OUString
const & rFamilyName
)
615 OString sHashString
= getFileHash(fileUrl
);
616 if (m_aEmbeddedFontFiles
.find(sHashString
) != m_aEmbeddedFontFiles
.end())
617 return m_aEmbeddedFontFiles
.at(sHashString
);
619 osl::File
file( fileUrl
);
620 if( file
.open( osl_File_OpenFlag_Read
) != osl::File::E_None
)
623 if ( !GetExport().GetTargetStorage().is() )
626 uno::Reference
< embed::XStorage
> storage
;
627 storage
.set( GetExport().GetTargetStorage()->openStorageElement( "Fonts",
628 ::embed::ElementModes::WRITE
), uno::UNO_SET_THROW
);
630 OUString name
= getFreeFontName(storage
, rFamilyName
);
632 uno::Reference
< io::XOutputStream
> outputStream
;
633 outputStream
.set( storage
->openStreamElement( name
, ::embed::ElementModes::WRITE
), UNO_QUERY_THROW
);
634 uno::Reference
< beans::XPropertySet
> propertySet( outputStream
, uno::UNO_QUERY
);
635 assert( propertySet
.is());
636 propertySet
->setPropertyValue( "MediaType", uno::Any( OUString( "application/x-font-ttf" ))); // TODO
639 sal_Int8 buffer
[ 4096 ];
642 if( file
.isEndOfFile( &eof
) != osl::File::E_None
)
644 SAL_WARN( "xmloff", "Error reading font file " << fileUrl
);
645 outputStream
->closeOutput();
650 if( file
.read( buffer
, 4096, readSize
) != osl::File::E_None
)
652 SAL_WARN( "xmloff", "Error reading font file " << fileUrl
);
653 outputStream
->closeOutput();
658 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
659 outputStream
->writeBytes(uno::Sequence
<sal_Int8
>(buffer
, readSize
));
661 outputStream
->closeOutput();
664 Reference
< embed::XTransactedObject
> transaction( storage
, UNO_QUERY
);
665 if( transaction
.is())
667 transaction
->commit();
668 OUString sInternalName
= "Fonts/" + name
;
669 m_aEmbeddedFontFiles
.emplace(sHashString
, sInternalName
);
670 return sInternalName
;
673 } catch( const Exception
& )
675 TOOLS_WARN_EXCEPTION( "xmloff", "Exception when embedding a font file" );
681 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */