bump product version to 7.6.3.2-android
[LibreOffice.git] / xmloff / source / style / XMLFontAutoStylePool.cxx
blobd7b880208be557b5896d632469ebc41143cacb70
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <utility>
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;
52 namespace {
54 class XMLFontAutoStylePoolEntry_Impl
56 OUString sName;
57 OUString sFamilyName;
58 OUString sStyleName;
59 FontFamily nFamily;
60 FontPitch nPitch;
61 rtl_TextEncoding eEnc;
63 public:
65 inline XMLFontAutoStylePoolEntry_Impl(
66 OUString aName,
67 OUString aFamilyName,
68 OUString aStyleName,
69 FontFamily nFamily,
70 FontPitch nPitch,
71 rtl_TextEncoding eEnc );
73 inline XMLFontAutoStylePoolEntry_Impl(
74 OUString aFamilyName,
75 OUString aStyleName,
76 FontFamily nFamily,
77 FontPitch nPitch,
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(
91 OUString aName,
92 OUString aFamilyName,
93 OUString aStyleName,
94 FontFamily nFam,
95 FontPitch nP,
96 rtl_TextEncoding eE ) :
97 sName(std::move( aName )),
98 sFamilyName(std::move( aFamilyName )),
99 sStyleName(std::move( aStyleName )),
100 nFamily( nFam ),
101 nPitch( nP ),
102 eEnc( eE )
106 inline XMLFontAutoStylePoolEntry_Impl::XMLFontAutoStylePoolEntry_Impl(
107 OUString rFamilyName,
108 OUString rStyleName,
109 FontFamily nFam,
110 FontPitch nP,
111 rtl_TextEncoding eE ) :
112 sFamilyName(std::move( rFamilyName )),
113 sStyleName(std::move( rStyleName )),
114 nFamily( nFam ),
115 nPitch( nP ),
116 eEnc( eE )
120 namespace {
122 struct XMLFontAutoStylePoolEntryCmp_Impl {
123 bool operator()(
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);
129 if( bEnc1 != bEnc2 )
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();
135 else
137 sal_Int32 nCmp = r1->GetFamilyName().compareTo( r2->GetFamilyName() );
138 if( 0 == nCmp )
139 return r1->GetStyleName().compareTo( r2->GetStyleName() ) < 0;
140 else
141 return nCmp < 0;
148 class XMLFontAutoStylePool_Impl : public o3tl::sorted_vector<std::unique_ptr<XMLFontAutoStylePoolEntry_Impl>, XMLFontAutoStylePoolEntryCmp_Impl>
152 XMLFontAutoStylePool::XMLFontAutoStylePool(SvXMLExport& rExp, bool bTryToEmbedFonts) :
153 m_rExport( rExp ),
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,
170 FontFamily nFamily,
171 FontPitch nPitch,
172 rtl_TextEncoding eEnc )
174 OUString sPoolName;
175 XMLFontAutoStylePoolEntry_Impl aTmp( rFamilyName, rStyleName, nFamily,
176 nPitch, eEnc );
177 XMLFontAutoStylePool_Impl::const_iterator it = m_pFontAutoStylePool->find( &aTmp );
178 if( it != m_pFontAutoStylePool->end() )
180 sPoolName = (*it)->GetName();
182 else
184 OUString sName;
185 sal_Int32 nLen = rFamilyName.indexOf( ';' );
186 if( -1 == nLen )
188 sName = rFamilyName;
190 else if( nLen > 0 )
192 sName = rFamilyName.copy( 0, nLen );
193 sName = sName.trim();
196 if( sName.isEmpty() )
197 sName = "F";
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);
217 return sPoolName;
220 OUString XMLFontAutoStylePool::Find(
221 const OUString& rFamilyName,
222 const OUString& rStyleName,
223 FontFamily nFamily,
224 FontPitch nPitch,
225 rtl_TextEncoding eEnc ) const
227 OUString sName;
228 XMLFontAutoStylePoolEntry_Impl aTmp( rFamilyName, rStyleName, nFamily,
229 nPitch, eEnc );
230 XMLFontAutoStylePool_Impl::const_iterator it = m_pFontAutoStylePool->find( &aTmp );
231 if( it != m_pFontAutoStylePool->end() )
233 sName = (*it)->GetName();
236 return sName;
239 namespace
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() )
251 return fileUrl;
254 return OUString();
257 /// Contains information about a single variant of an embedded font.
258 struct EmbeddedFontInfo
260 OUString aURL;
261 FontWeight eWeight = WEIGHT_NORMAL;
262 FontItalic eItalic = ITALIC_NONE;
265 /// Converts FontWeight to CSS-compatible string representation.
266 OUString FontWeightToString(FontWeight eWeight)
268 OUString aRet;
270 switch (eWeight)
272 case WEIGHT_BOLD:
273 aRet = "bold";
274 break;
275 default:
276 aRet = "normal";
277 break;
280 return aRet;
283 /// Converts FontItalic to CSS-compatible string representation.
284 OUString FontItalicToString(FontItalic eWeight)
286 OUString aRet;
288 switch (eWeight)
290 case ITALIC_NORMAL:
291 aRet = "italic";
292 break;
293 default:
294 aRet = "normal";
295 break;
298 return aRet;
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())
309 return aReturnSet;
311 // Check styles first
312 uno::Reference<container::XNameAccess> xFamilies(xFamiliesSupp->getStyleFamilies());
313 if (xFamilies.is())
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);
332 if (xInfo)
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);
388 return aReturnSet;
391 void XMLFontAutoStylePool::exportXML()
393 SvXMLElementExport aElem(GetExport(), XML_NAMESPACE_OFFICE,
394 XML_FONT_FACE_DECLS,
395 true, true);
396 Any aAny;
397 OUString sTmp;
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());
416 std::sort(
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,
434 XML_FONT_ADORNMENTS,
435 rStyleName);
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())
481 continue;
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;
493 else
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))
510 if (!bExportFlat)
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);
527 if (bExportFlat)
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)
555 OUString sName;
556 int nIndex = 1;
559 sName = "Font_" +
560 rFamilyName.replaceAll(" ", "_") + "_" +
561 OUString::number(nIndex) + ".ttf";
562 nIndex++;
563 } while (rxStorage->hasByName(sName));
565 return 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)
581 OString aHash;
582 osl::File aFile(rFileUrl);
583 if (aFile.open(osl_File_OpenFlag_Read) != osl::File::E_None)
584 return aHash;
586 comphelper::Hash aHashEngine(comphelper::HashType::SHA512);
587 for (;;)
589 sal_Int8 aBuffer[4096];
590 sal_uInt64 nReadSize;
591 sal_Bool bEof;
592 if (aFile.isEndOfFile(&bEof) != osl::File::E_None)
594 SAL_WARN("xmloff", "Error reading font file " << rFileUrl);
595 return aHash;
597 if (bEof)
598 break;
599 if (aFile.read(aBuffer, 4096, nReadSize) != osl::File::E_None)
601 SAL_WARN("xmloff", "Error reading font file " << rFileUrl);
602 return aHash;
604 if (nReadSize == 0)
605 break;
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 )
621 return OUString();
623 if ( !GetExport().GetTargetStorage().is() )
624 return OUString();
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
637 for(;;)
639 sal_Int8 buffer[ 4096 ];
640 sal_uInt64 readSize;
641 sal_Bool eof;
642 if( file.isEndOfFile( &eof ) != osl::File::E_None )
644 SAL_WARN( "xmloff", "Error reading font file " << fileUrl );
645 outputStream->closeOutput();
646 return OUString();
648 if( eof )
649 break;
650 if( file.read( buffer, 4096, readSize ) != osl::File::E_None )
652 SAL_WARN( "xmloff", "Error reading font file " << fileUrl );
653 outputStream->closeOutput();
654 return OUString();
656 if( readSize == 0 )
657 break;
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();
662 if( storage.is() )
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" );
677 return OUString();
681 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */