include/o3tl/BigEndianTypes.hxx | 62 ++++++ include/vcl/font/EOTConverter.hxx | 67 +++++++ include/vcl/font/FontDataContainer.hxx | 36 ++++ o3tl/CppunitTest_o3tl_tests.mk | 1 o3tl/qa/BigEndianTypesTest.cxx | 107 +++++++++++ package/source/zippackage/ZipPackage.cxx | 3 sd/Library_sd.mk | 1 sd/inc/ModelTraverser.hxx | 53 +++++ sd/source/core/ModelTraverser.cxx | 47 +++++ sd/source/filter/eppt/epptooxml.hxx | 8 sd/source/filter/eppt/pptx-epptooxml.cxx | 223 ++++++++++++++++++++++++- sd/source/ui/tools/GraphicSizeCheck.cxx | 59 ------ vcl/CppunitTest_vcl_font_ttf_structure_test.mk | 44 ++++ vcl/Library_vcl.mk | 1 vcl/Module_vcl.mk | 1 vcl/inc/font/TTFReader.hxx | 201 ++++++++++++++++++++++ vcl/inc/font/TTFStructure.hxx | 183 ++++++++++++++++++++ vcl/inc/sft.hxx | 20 -- vcl/qa/cppunit/font/TTFStructureTest.cxx | 87 +++++++++ vcl/qa/cppunit/font/data/Ahem.ttf |binary vcl/source/font/EOTConverter.cxx | 166 ++++++++++++++++++ 21 files changed, 1292 insertions(+), 78 deletions(-)
New commits: commit f6ad4397dc7cee97020d8d2e89345157dbfc7a0f Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Fri Apr 25 14:41:35 2025 +0900 Commit: Tomaž Vajngerl <qui...@gmail.com> CommitDate: Wed Apr 30 08:30:53 2025 +0200 sd: move ModelTraverser to own file so it can be reused Change-Id: I8d487fd009ca3fa5fb9de8aabe65fd3e5e21b41d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/184692 Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> Tested-by: Jenkins diff --git a/sd/Library_sd.mk b/sd/Library_sd.mk index df3309c6d78d..a05aba888eb6 100644 --- a/sd/Library_sd.mk +++ b/sd/Library_sd.mk @@ -154,6 +154,7 @@ $(eval $(call gb_Library_add_exception_objects,sd,\ sd/source/core/CustomAnimationEffect \ sd/source/core/CustomAnimationPreset \ sd/source/core/EffectMigration \ + sd/source/core/ModelTraverser \ sd/source/core/PageListWatcher \ sd/source/core/TransitionPreset \ sd/source/core/ThemeColorChanger \ diff --git a/sd/inc/ModelTraverser.hxx b/sd/inc/ModelTraverser.hxx new file mode 100644 index 000000000000..209ace6047e4 --- /dev/null +++ b/sd/inc/ModelTraverser.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <vector> +#include <memory> +#include "drawdoc.hxx" + +class SdrObject; + +namespace sd +{ +/** + * Interface for the visitor class, which handles each visited SdrObject + * in the DOM. + */ +class ModelTraverseHandler +{ +public: + virtual ~ModelTraverseHandler() {} + virtual void handleSdrObject(SdrObject* pObject) = 0; +}; + +/** + * Traverses the DOM and calls a handler for each object (SdrObject) it + * encounters. + */ +class ModelTraverser +{ +private: + std::vector<std::shared_ptr<ModelTraverseHandler>> m_pNodeHandler; + SdDrawDocument* m_pDocument; + +public: + ModelTraverser(SdDrawDocument* pDocument) + : m_pDocument(pDocument) + { + } + + void traverse(); + void addNodeHandler(std::shared_ptr<ModelTraverseHandler> pHandler); +}; + +} // end sd namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/core/ModelTraverser.cxx b/sd/source/core/ModelTraverser.cxx new file mode 100644 index 000000000000..6eb15de0a6ee --- /dev/null +++ b/sd/source/core/ModelTraverser.cxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <ModelTraverser.hxx> + +#include <svx/svdobj.hxx> +#include <svx/svdpage.hxx> + +using namespace css; + +namespace sd +{ +void ModelTraverser::traverse() +{ + if (!m_pDocument) + return; + + for (sal_uInt16 nPage = 0; nPage < m_pDocument->GetPageCount(); ++nPage) + { + SdrPage* pPage = m_pDocument->GetPage(nPage); + if (pPage) + { + for (const rtl::Reference<SdrObject>& pObject : *pPage) + { + for (auto& pNodeHandler : m_pNodeHandler) + { + pNodeHandler->handleSdrObject(pObject.get()); + } + } + } + } +} + +void ModelTraverser::addNodeHandler(std::shared_ptr<ModelTraverseHandler> pHandler) +{ + m_pNodeHandler.push_back(pHandler); +} + +} // end sd namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/tools/GraphicSizeCheck.cxx b/sd/source/ui/tools/GraphicSizeCheck.cxx index 68bd33a3e194..d145de1ad22d 100644 --- a/sd/source/ui/tools/GraphicSizeCheck.cxx +++ b/sd/source/ui/tools/GraphicSizeCheck.cxx @@ -10,6 +10,7 @@ #include <memory> #include <tools/GraphicSizeCheck.hxx> +#include <ModelTraverser.hxx> #include <svx/strings.hrc> #include <svx/svdobj.hxx> #include <svx/svdpage.hxx> @@ -22,64 +23,6 @@ namespace sd { -namespace -{ -/** - * Interface for the visitor class, which handles each visited SdrObject - * in the DOM. - */ -class ModelTraverseHandler -{ -public: - virtual ~ModelTraverseHandler() {} - - virtual void handleSdrObject(SdrObject* pObject) = 0; -}; - -/** - * Traverses the DOM and calls a handler for each object (SdrObject) it - * encounters. - */ -class ModelTraverser -{ -private: - std::vector<std::shared_ptr<ModelTraverseHandler>> m_pNodeHandler; - SdDrawDocument* m_pDocument; - -public: - ModelTraverser(SdDrawDocument* pDocument) - : m_pDocument(pDocument) - { - } - - void traverse() - { - if (!m_pDocument) - return; - - for (sal_uInt16 nPage = 0; nPage < m_pDocument->GetPageCount(); ++nPage) - { - SdrPage* pPage = m_pDocument->GetPage(nPage); - if (pPage) - { - for (const rtl::Reference<SdrObject>& pObject : *pPage) - { - for (auto& pNodeHandler : m_pNodeHandler) - { - pNodeHandler->handleSdrObject(pObject.get()); - } - } - } - } - } - - void addNodeHandler(std::shared_ptr<ModelTraverseHandler> pHandler) - { - m_pNodeHandler.push_back(pHandler); - } -}; -} - GraphicSizeViolation::GraphicSizeViolation(sal_Int32 nDPI, SdrGrafObj* pGraphicObject) : m_pGraphicObject(pGraphicObject) { commit d0ee08cfbf12145027eee7ad46448a8734693c06 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Thu Feb 27 14:30:51 2025 +0900 Commit: Tomaž Vajngerl <qui...@gmail.com> CommitDate: Wed Apr 30 08:30:48 2025 +0200 oox: export embedded font used in a presentation document This adds support to export the fonts that are used in an presentation document. The fonts are converted to EOT fonts. Adds an EOT converter, which converts a TTF font to EOT font by writing a EOT header and adding the TTF font data. Adds BigEndianTypes so we can read a structure that is defined in big endian and the types get converted (if needed) to system endian when accessed. + test Adds TTF reader which reads the true type font structure in an easy way that is used by the EOTConverter. + test Change-Id: I8b0bcb69e47943c2a8679edf881d7c2bab45c450 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/184288 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> diff --git a/include/o3tl/BigEndianTypes.hxx b/include/o3tl/BigEndianTypes.hxx new file mode 100644 index 000000000000..5b637c4a5d62 --- /dev/null +++ b/include/o3tl/BigEndianTypes.hxx @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> +#include <sal/types.h> +#include <osl/endian.h> + +namespace o3tl +{ +/** 16-bit unsigned integer type that can be used in a struct to read from data that is big endian. + * + * Type can't be instantiated but only used in a struct which is reinterpret_cast from bytes. + */ +class sal_uInt16_BE +{ +private: + sal_uInt16 mnValue; + sal_uInt16_BE() = delete; + +public: + constexpr operator sal_uInt16() const + { +#ifdef OSL_LITENDIAN + return OSL_SWAPWORD(mnValue); +#else + return mnValue; +#endif + } +}; + +/** 32-bit unsigned integer type that can be used in a struct to read from data that is big endian. + * + * Type can't be instantiated but only used in a struct which is reinterpret_cast from bytes. + */ +class sal_uInt32_BE +{ +private: + sal_uInt32 mnValue; + sal_uInt32_BE() = delete; + +public: + constexpr operator sal_uInt32() const + { +#ifdef OSL_LITENDIAN + return OSL_SWAPDWORD(mnValue); +#else + return mnValue; +#endif + } +}; + +} // namespace o3tl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/vcl/font/EOTConverter.hxx b/include/vcl/font/EOTConverter.hxx new file mode 100644 index 000000000000..8695355b3628 --- /dev/null +++ b/include/vcl/font/EOTConverter.hxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <vcl/dllapi.h> +#include <vcl/font/FontDataContainer.hxx> + +namespace font +{ +#pragma pack(push, 1) + +/** Main EOT Header + * + * See: https://www.w3.org/submissions/EOT/ */ +struct EOTHeader +{ + sal_uInt32 nEotSize; + sal_uInt32 nFontDataSize; + sal_uInt32 nVersion; + sal_uInt32 nFlags; + sal_uInt8 nFontPANOSE[10]; + sal_uInt8 nCharset; + sal_uInt8 nItalic; + sal_uInt32 nWeight; + sal_uInt16 nFsType; + sal_uInt16 nMagicNumber; + sal_uInt32 nUnicodeRange1; + sal_uInt32 nUnicodeRange2; + sal_uInt32 nUnicodeRange3; + sal_uInt32 nUnicodeRange4; + sal_uInt32 nCodePageRange1; + sal_uInt32 nCodePageRange2; + sal_uInt32 nCheckSumAdjustment; + sal_uInt32 nReserved1; + sal_uInt32 nReserved2; + sal_uInt32 nReserved3; + sal_uInt32 nReserved4; + // variable length types come after this +}; + +#pragma pack(pop) + +/** Converts TTF Font wrapped in a FontDataContainer to EOT type */ +class VCL_DLLPUBLIC EOTConverter +{ +private: + font::FontDataContainer const& mrFontDataContainer; + +public: + explicit EOTConverter(font::FontDataContainer const& rFontDataContainer) + : mrFontDataContainer(rFontDataContainer) + { + } + + bool convert(std::vector<sal_uInt8>& rEmbeddedOutput); +}; + +} // end font namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/vcl/font/FontDataContainer.hxx b/include/vcl/font/FontDataContainer.hxx new file mode 100644 index 000000000000..159eb4964420 --- /dev/null +++ b/include/vcl/font/FontDataContainer.hxx @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <vector> + +namespace font +{ +/** Font data container wraps binary content of a TTF font */ +class FontDataContainer +{ + std::vector<sal_uInt8> const& mrFontBytes; + +public: + FontDataContainer(std::vector<sal_uInt8> const& rFontBytes) + : mrFontBytes(rFontBytes) + { + } + + const char* getPointer() const { return reinterpret_cast<const char*>(mrFontBytes.data()); } + + size_t size() const { return mrFontBytes.size(); } + + std::vector<sal_uInt8>::const_iterator begin() const { return mrFontBytes.begin(); } + std::vector<sal_uInt8>::const_iterator end() const { return mrFontBytes.end(); } +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/o3tl/CppunitTest_o3tl_tests.mk b/o3tl/CppunitTest_o3tl_tests.mk index 01f6e410a1b2..60dad44ed54a 100644 --- a/o3tl/CppunitTest_o3tl_tests.mk +++ b/o3tl/CppunitTest_o3tl_tests.mk @@ -40,6 +40,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,o3tl_tests,\ o3tl/qa/test-unit_conversion \ o3tl/qa/test-vector_pool \ o3tl/qa/test-hash_combine \ + o3tl/qa/BigEndianTypesTest \ )) # vim: set noet sw=4: diff --git a/o3tl/qa/BigEndianTypesTest.cxx b/o3tl/qa/BigEndianTypesTest.cxx new file mode 100644 index 000000000000..055be80705f5 --- /dev/null +++ b/o3tl/qa/BigEndianTypesTest.cxx @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <o3tl/BigEndianTypes.hxx> +#include <array> + +namespace +{ +// Struct using endian of the system with 16-bit variables +struct SystemEndian16 +{ + sal_uInt16 x; + sal_uInt16 y; + sal_uInt16 z; + sal_uInt16 w; +}; + +// Struct using big endian with 16-bit variables +struct BigEndian16 +{ + o3tl::sal_uInt16_BE x; + o3tl::sal_uInt16_BE y; + o3tl::sal_uInt16_BE z; + o3tl::sal_uInt16_BE w; +}; + +// Struct using endian of the system with 32-bit variables +struct SystemEndian32 +{ + sal_uInt32 x; + sal_uInt32 y; +}; + +// Struct using big endian with 32-bit variables +struct BigEndian32 +{ + o3tl::sal_uInt32_BE x; + o3tl::sal_uInt32_BE y; +}; + +// Test data +constexpr std::array<sal_uInt8, 8> aDataArray = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + +class BigEndianTypesTest : public CppUnit::TestFixture +{ +public: + void testCast() + { + auto* pSystem16 = reinterpret_cast<const SystemEndian16*>(aDataArray.data()); + + // We expect different results depending on the system endian +#ifdef OSL_LITENDIAN + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0201), pSystem16->x); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0403), pSystem16->y); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0605), pSystem16->z); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0807), pSystem16->w); +#else + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0102), pSystem16->x); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0304), pSystem16->y); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0506), pSystem16->z); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0708), pSystem16->w); +#endif + + // Reading in big endian should read the same independent of system endian + auto* pBig16 = reinterpret_cast<const BigEndian16*>(aDataArray.data()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0102), sal_uInt16(pBig16->x)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0304), sal_uInt16(pBig16->y)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0506), sal_uInt16(pBig16->z)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x0708), sal_uInt16(pBig16->w)); + + // We expect different results depending on the system endian + auto* pSystem32 = reinterpret_cast<const SystemEndian32*>(aDataArray.data()); +#ifdef OSL_LITENDIAN + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x04030201), pSystem32->x); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x08070605), pSystem32->y); +#else + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x01020304), pSystem32->x); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x05060708), pSystem32->y); +#endif + + // Reading in big endian should read the same independent of system endian + auto* pBig32 = reinterpret_cast<const BigEndian32*>(aDataArray.data()); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x01020304), sal_uInt32(pBig32->x)); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x05060708), sal_uInt32(pBig32->y)); + } + + CPPUNIT_TEST_SUITE(BigEndianTypesTest); + CPPUNIT_TEST(testCast); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BigEndianTypesTest); + +} // end anonymous namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackage.cxx b/package/source/zippackage/ZipPackage.cxx index 36a48ac7c04e..2364ce12d6ec 100644 --- a/package/source/zippackage/ZipPackage.cxx +++ b/package/source/zippackage/ZipPackage.cxx @@ -1216,7 +1216,8 @@ void ZipPackage::WriteContentTypes( ZipOutputStream& aZipOut, const std::vector< { u"xml"_ustr, u"application/xml"_ustr }, { u"rels"_ustr, u"application/vnd.openxmlformats-package.relationships+xml"_ustr }, { u"png"_ustr, u"image/png"_ustr }, - { u"jpeg"_ustr, u"image/jpeg"_ustr } + { u"jpeg"_ustr, u"image/jpeg"_ustr }, + { u"fntdata"_ustr, u"application/x-fontdata"_ustr } }; uno::Sequence< beans::StringPair > aOverridesSequence(aManList.size()); diff --git a/sd/source/filter/eppt/epptooxml.hxx b/sd/source/filter/eppt/epptooxml.hxx index 7ac7dc938067..b2b472412aea 100644 --- a/sd/source/filter/eppt/epptooxml.hxx +++ b/sd/source/filter/eppt/epptooxml.hxx @@ -201,6 +201,14 @@ private: void WriteVBA(); void WriteModifyVerifier(); + + bool mbEmbedFonts = false; + bool mbEmbedUsedOnly = false; + bool mbEmbedLatinScript = true; + bool mbEmbedAsianScript = true; + bool mbEmbedComplexScript = true; + + void WriteEmbeddedFontList(); }; } diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx b/sd/source/filter/eppt/pptx-epptooxml.cxx index eb88eefdf359..baf6db88883a 100644 --- a/sd/source/filter/eppt/pptx-epptooxml.cxx +++ b/sd/source/filter/eppt/pptx-epptooxml.cxx @@ -27,6 +27,7 @@ #include <oox/export/shapes.hxx> #include <svx/svdlayer.hxx> #include <unokywds.hxx> +#include <osl/file.hxx> #include <comphelper/sequenceashashmap.hxx> #include <comphelper/storagehelper.hxx> @@ -60,6 +61,8 @@ #include <com/sun/star/container/XNamed.hpp> #include <com/sun/star/presentation/XPresentationSupplier.hpp> #include <comphelper/diagnose_ex.hxx> +#include <comphelper/hash.hxx> +#include <vcl/embeddedfontshelper.hxx> #include <oox/export/utils.hxx> #include <oox/export/ThemeExport.hxx> @@ -76,13 +79,18 @@ #include <svx/ColorSets.hxx> #include <sdmod.hxx> #include <sdpage.hxx> +#include <unomodel.hxx> #include <vcl/svapp.hxx> #include <vcl/settings.hxx> +#include <vcl/font/EOTConverter.hxx> #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> #include <com/sun/star/document/XStorageBasedDocument.hpp> #include <utility> +#include <unordered_map> +#include <unordered_set> + #if OSL_DEBUG_LEVEL > 1 #include <com/sun/star/drawing/RectanglePoint.hpp> #endif @@ -393,6 +401,12 @@ void PowerPointExport::writeDocumentProperties() try { xSettings->getPropertyValue(u"LoadReadonly"_ustr) >>= bSecurityOptOpenReadOnly; + + xSettings->getPropertyValue(u"EmbedFonts"_ustr) >>= mbEmbedFonts; + xSettings->getPropertyValue(u"EmbedOnlyUsedFonts"_ustr) >>= mbEmbedUsedOnly; + xSettings->getPropertyValue(u"EmbedLatinScriptFonts"_ustr) >>= mbEmbedLatinScript; + xSettings->getPropertyValue(u"EmbedAsianScriptFonts"_ustr) >>= mbEmbedAsianScript; + xSettings->getPropertyValue(u"EmbedComplexScriptFonts"_ustr) >>= mbEmbedComplexScript; } catch( Exception& ) { @@ -454,7 +468,10 @@ bool PowerPointExport::exportDocument() oox::getRelationship(Relationship::THEME), u"theme/theme1.xml"); - mPresentationFS->startElementNS(XML_p, XML_presentation, presentationNamespaces(*this)); + auto pAttributes = presentationNamespaces(*this); + if (mbEmbedFonts) + pAttributes->add(XML_embedTrueTypeFonts, "1"); + mPresentationFS->startElementNS(XML_p, XML_presentation, pAttributes); mXStatusIndicator = getStatusIndicator(); @@ -473,6 +490,7 @@ bool PowerPointExport::exportDocument() mPresentationFS->singleElementNS(XML_p, XML_notesSz, XML_cx, OString::number(PPTtoEMU(maNotesPageSize.Width)), XML_cy, OString::number(PPTtoEMU(maNotesPageSize.Height))); + WriteEmbeddedFontList(); WriteCustomSlideShow(); @@ -512,6 +530,209 @@ bool PowerPointExport::exportDocument() return new ::oox::ole::VbaProject(getComponentContext(), getModel(), u"Impress"); } +namespace +{ +struct EmbeddedFont +{ + OUString sFamilyName; + + OString aRegularRelID; + OString aBoldRelID; + OString aItalicRelID; + OString aBoldItalicRelID; +}; +} // end anonymous namespace + +// Writers the list of all embedded fonts and reference to the fonts +void PowerPointExport::WriteEmbeddedFontList() +{ + if (!mbEmbedFonts) + return; + + SdDrawDocument* pDocument = nullptr; + if (auto* pSdXImpressDocument = dynamic_cast<SdXImpressDocument*>(mXModel.get())) + pDocument = pSdXImpressDocument->GetDoc(); + + if (!pDocument) + return; + + int nextFontId = 1; + + std::unordered_set<OUString> aFontFamilyNameSet; + std::unordered_map<std::string, OString> aFontDeduplicationMap; + + std::vector<EmbeddedFont> aEmbeddedFontInfo; + + uno::Reference<beans::XPropertySet> xProperties(mXModel, UNO_QUERY); + if (!xProperties.is()) + return; + + uno::Sequence<uno::Any> aAnySeq; + if (!(xProperties->getPropertyValue("Fonts") >>= aAnySeq)) + return; + + if (aAnySeq.getLength() % 5 != 0) + return; + + int nLen = aAnySeq.getLength() / 5; + int nSeqIndex = 0; + + for (int i = 0; i < nLen; i++) + { + OUString sFamilyName; + OUString sStyleName; + sal_uInt16 eFamily = FAMILY_DONTKNOW; + sal_uInt16 ePitch = PITCH_DONTKNOW; + sal_uInt16 eCharSet = RTL_TEXTENCODING_DONTKNOW; + + aAnySeq[nSeqIndex++] >>= sFamilyName; + aAnySeq[nSeqIndex++] >>= sStyleName; + aAnySeq[nSeqIndex++] >>= eFamily; + aAnySeq[nSeqIndex++] >>= ePitch; + aAnySeq[nSeqIndex++] >>= eCharSet; + + if (aFontFamilyNameSet.contains(sFamilyName)) + continue; + + static std::vector<std::pair<FontItalic, FontWeight>> aFontVariantCombinations = + { + { ITALIC_NONE, WEIGHT_NORMAL }, + { ITALIC_NONE, WEIGHT_BOLD }, + { ITALIC_NORMAL, WEIGHT_NORMAL }, + { ITALIC_NORMAL, WEIGHT_BOLD } + }; + + EmbeddedFont aInfo; + aInfo.sFamilyName = sFamilyName; + + for (auto [eItalic, eWeight] : aFontVariantCombinations) + { + OUString sFontUrl = EmbeddedFontsHelper::fontFileUrl( + sFamilyName, FontFamily(eFamily), eItalic, eWeight, FontPitch(ePitch), + EmbeddedFontsHelper::FontRights::ViewingAllowed); + + if (sFontUrl.isEmpty()) + continue; + + osl::File aFile(sFontUrl); + if (aFile.open(osl_File_OpenFlag_Read ) != osl::File::E_None) + continue; + + std::vector<sal_uInt8> rFontData; + + std::array<sal_uInt8, 4096> buffer; + sal_uInt64 readSize; + + comphelper::Hash aHashCalc(comphelper::HashType::SHA256); + + OString uRelID; + + // Read file + for(;;) + { + sal_Bool eof; + + if (aFile.isEndOfFile(&eof) != osl::File::E_None) + { + SAL_WARN("sw.ww8", "Error reading font file " << sFontUrl); + break; + } + if (eof) + break; + + if (aFile.read(buffer.data(), 4096, readSize) != osl::File::E_None) + { + SAL_WARN("sw.ww8", "Error reading font file " << sFontUrl); + break; + } + + if (readSize == 0) + break; + + rFontData.insert(rFontData.end(), buffer.data(), buffer.data() + readSize); + aHashCalc.update(reinterpret_cast<const unsigned char*>(buffer.data()), readSize); + } + + std::string aHash = comphelper::hashToString(aHashCalc.finalize()); + auto iterator = aFontDeduplicationMap.find(aHash); + if (iterator == aFontDeduplicationMap.end()) + { + std::vector<sal_uInt8> rEOT; + + font::FontDataContainer aContainer(rFontData); + font::EOTConverter aConverter(aContainer); + if (!aConverter.convert(rEOT)) + continue; + + OUString sFontFileName = "font" + OUString::number(nextFontId) + ".fntdata"; + OUString sArchivePath = "ppt/fonts/" + sFontFileName; + uno::Reference<css::io::XOutputStream> xOutStream = openFragmentStream(sArchivePath, u"application/x-fontdata"_ustr); + xOutStream->writeBytes(uno::Sequence<sal_Int8>(reinterpret_cast<const sal_Int8*>(rEOT.data()), rEOT.size())); + xOutStream->closeOutput(); + + OUString sRelID = addRelation(mPresentationFS->getOutputStream(), + oox::getRelationship(Relationship::FONT), + Concat2View("fonts/font" + OUString::number(nextFontId) + ".fntdata")); + + ++nextFontId; + + uRelID = OUStringToOString(sRelID, RTL_TEXTENCODING_UTF8); + aFontDeduplicationMap.emplace(aHash, uRelID); + } + else + { + uRelID = iterator->second; + } + + if (eItalic == ITALIC_NONE && eWeight == WEIGHT_NORMAL) + { + aInfo.aRegularRelID = uRelID; + } + else if (eItalic == ITALIC_NONE && eWeight == WEIGHT_BOLD) + { + aInfo.aBoldRelID = uRelID; + } + else if (eItalic == ITALIC_NORMAL && eWeight == WEIGHT_NORMAL) + { + aInfo.aItalicRelID = uRelID; + } + else if (eItalic == ITALIC_NORMAL && eWeight == WEIGHT_BOLD) + { + aInfo.aBoldItalicRelID = uRelID; + } + } + + aEmbeddedFontInfo.push_back(aInfo); + aFontFamilyNameSet.insert(sFamilyName); + } + + // if there are fonts to embed and font embeding enabled + mPresentationFS->startElementNS(XML_p, XML_embeddedFontLst); + for (auto const& rInfo : aEmbeddedFontInfo) + { + mPresentationFS->startElementNS(XML_p, XML_embeddedFont); + + mPresentationFS->singleElementNS(XML_p, XML_font, + //XML_charset, OUString::number(0), + XML_typeface, rInfo.sFamilyName); + + if (!rInfo.aRegularRelID.isEmpty()) + mPresentationFS->singleElementNS(XML_p, XML_regular, FSNS(XML_r, XML_id), rInfo.aRegularRelID); + + if (!rInfo.aBoldRelID.isEmpty()) + mPresentationFS->singleElementNS(XML_p, XML_bold, FSNS(XML_r, XML_id), rInfo.aBoldRelID); + + if (!rInfo.aItalicRelID.isEmpty()) + mPresentationFS->singleElementNS(XML_p, XML_italic, FSNS(XML_r, XML_id), rInfo.aItalicRelID); + + if (!rInfo.aBoldItalicRelID.isEmpty()) + mPresentationFS->singleElementNS(XML_p, XML_boldItalic, FSNS(XML_r, XML_id), rInfo.aBoldItalicRelID); + + mPresentationFS->endElementNS(XML_p, XML_embeddedFont); + } + mPresentationFS->endElementNS(XML_p, XML_embeddedFontLst); +} + void PowerPointExport::WriteCustomSlideShow() { Reference<XCustomPresentationSupplier> aXCPSup(mXModel, css::uno::UNO_QUERY); diff --git a/vcl/CppunitTest_vcl_font_ttf_structure_test.mk b/vcl/CppunitTest_vcl_font_ttf_structure_test.mk new file mode 100644 index 000000000000..8759c800ecb4 --- /dev/null +++ b/vcl/CppunitTest_vcl_font_ttf_structure_test.mk @@ -0,0 +1,44 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,vcl_font_ttf_structure_test)) + +$(eval $(call gb_CppunitTest_set_include,vcl_font_ttf_structure_test,\ + $$(INCLUDE) \ + -I$(SRCDIR)/vcl/inc \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,vcl_font_ttf_structure_test, \ + vcl/qa/cppunit/font/TTFStructureTest \ +)) + +$(eval $(call gb_CppunitTest_use_externals,vcl_font_ttf_structure_test,boost_headers)) + +$(eval $(call gb_CppunitTest_use_libraries,vcl_font_ttf_structure_test, \ + basegfx \ + comphelper \ + cppu \ + cppuhelper \ + sal \ + subsequenttest \ + test \ + unotest \ + utl \ + tl \ + vcl \ + xmlsecurity \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,vcl_font_ttf_structure_test)) +$(eval $(call gb_CppunitTest_use_ure,vcl_font_ttf_structure_test)) +$(eval $(call gb_CppunitTest_use_vcl,vcl_font_ttf_structure_test)) +$(eval $(call gb_CppunitTest_use_rdb,vcl_font_ttf_structure_test,services)) +$(eval $(call gb_CppunitTest_use_configuration,vcl_font_ttf_structure_test)) + +# vim: set noet sw=4 ts=4: diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk index 8edfc3e21f8e..a4402914132e 100644 --- a/vcl/Library_vcl.mk +++ b/vcl/Library_vcl.mk @@ -553,6 +553,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\ vcl/source/font/fontcharmap \ vcl/source/font/fontmetric \ vcl/source/font/font \ + vcl/source/font/EOTConverter \ vcl/source/fontsubset/cff \ vcl/source/fontsubset/fontsubset \ vcl/source/fontsubset/sft \ diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk index cc22693cf512..b2eeb946bf02 100644 --- a/vcl/Module_vcl.mk +++ b/vcl/Module_vcl.mk @@ -229,6 +229,7 @@ $(eval $(call gb_Module_add_check_targets,vcl,\ CppunitTest_vcl_fontmetric \ CppunitTest_vcl_text \ CppunitTest_vcl_textlayout \ + CppunitTest_vcl_font_ttf_structure_test \ CppunitTest_vcl_filters_test \ CppunitTest_vcl_mnemonic \ CppunitTest_vcl_outdev \ diff --git a/vcl/inc/font/TTFReader.hxx b/vcl/inc/font/TTFReader.hxx new file mode 100644 index 000000000000..eb8d2be34de8 --- /dev/null +++ b/vcl/inc/font/TTFReader.hxx @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <font/TTFStructure.hxx> +#include <vcl/font/FontDataContainer.hxx> +#include <rtl/ustrbuf.hxx> + +namespace font +{ +/** Handles reading of the name table */ +class NameTableHandler +{ +private: + FontDataContainer const& mrFontDataContainer; + + const TableDirectoryEntry* mpTableDirectoryEntry; + const char* mpNameTablePointer; + const NameTable* mpNameTable; + + const char* getTablePointer(const TableDirectoryEntry* pEntry) + { + return mrFontDataContainer.getPointer() + pEntry->offset; + } + +public: + NameTableHandler(FontDataContainer const& rFontDataContainer, + const TableDirectoryEntry* pTableDirectoryEntry) + : mrFontDataContainer(rFontDataContainer) + , mpTableDirectoryEntry(pTableDirectoryEntry) + , mpNameTablePointer(getTablePointer(mpTableDirectoryEntry)) + , mpNameTable(reinterpret_cast<const NameTable*>(mpNameTablePointer)) + { + } + + sal_uInt32 getTableOffset() { return mpTableDirectoryEntry->offset; } + + const NameTable* getNameTable() { return mpNameTable; } + + /** Number of tables */ + sal_uInt16 getNumberOfRecords() { return mpNameTable->nCount; } + + /** Get a name table record for index */ + const NameRecord* getNameRecord(sal_uInt32 index) + { + const char* pPointer = mpNameTablePointer + sizeof(NameTable); + pPointer += sizeof(NameRecord) * index; + return reinterpret_cast<const NameRecord*>(pPointer); + } + + /** Get offset to english unicode string + * + * See: https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids + */ + bool findEnglishUnicodeNameOffset(font::NameID eNameID, sal_uInt64& rOffset, + sal_uInt16& rLength) + { + rOffset = 0; + rLength = 0; + + for (int n = 0; n < getNumberOfRecords(); n++) + { + const font::NameRecord* pNameRecord = getNameRecord(n); + + if (pNameRecord->nPlatformID == 3 // Windows + && pNameRecord->nEncodingID == 1 // Unicode BMP + && pNameRecord->nLanguageID == 0x0409 // en-us + && pNameRecord->nNameID == sal_uInt16(eNameID)) + { + rLength = pNameRecord->nLength; + rOffset = getTableOffset() + getNameTable()->nStorageOffset + + pNameRecord->nStringOffset; + return true; + } + } + return false; + } +}; + +/** Handles reading of table entries */ +class TableEntriesHandler +{ +private: + FontDataContainer const& mrFontDataContainer; + const char* mpFirstPosition; + sal_uInt16 mnNumberOfTables; + + const char* getTablePointer(const TableDirectoryEntry* pEntry) + { + return mrFontDataContainer.getPointer() + pEntry->offset; + } + +public: + TableEntriesHandler(FontDataContainer const& rFontDataContainer, const char* pPosition, + sal_uInt16 nNumberOfTables) + : mrFontDataContainer(rFontDataContainer) + , mpFirstPosition(pPosition) + , mnNumberOfTables(nNumberOfTables) + { + } + + const TableDirectoryEntry* getEntry(sal_uInt32 nTag) + { + for (sal_uInt32 i = 0; i < mnNumberOfTables; i++) + { + const char* pPosition = mpFirstPosition + sizeof(TableDirectoryEntry) * i; + const auto* pEntry = reinterpret_cast<const TableDirectoryEntry*>(pPosition); + + if (nTag == pEntry->tag) + return pEntry; + } + return nullptr; + } + + const OS2Table* getOS2Table() + { + const auto* pEntry = getEntry(T_OS2); + if (!pEntry) + return nullptr; + return reinterpret_cast<const OS2Table*>(getTablePointer(pEntry)); + } + + const HeadTable* getHeadTable() + { + const auto* pEntry = getEntry(T_head); + if (!pEntry) + return nullptr; + return reinterpret_cast<const HeadTable*>(getTablePointer(pEntry)); + } + + const NameTable* getNameTable() + { + const auto* pEntry = getEntry(T_name); + if (!pEntry) + return nullptr; + return reinterpret_cast<const NameTable*>(getTablePointer(pEntry)); + } + + std::unique_ptr<NameTableHandler> getNameTableHandler() + { + const auto* pEntry = getEntry(T_name); + if (!pEntry) + return nullptr; + + return std::unique_ptr<NameTableHandler>(new NameTableHandler(mrFontDataContainer, pEntry)); + } +}; + +/** Entry point handler for the TTF Font */ +class TTFFont +{ +private: + FontDataContainer const& mrFontDataContainer; + + const char* getTablePointer(const TableDirectoryEntry* pEntry) + { + return mrFontDataContainer.getPointer() + pEntry->offset; + } + +public: + TTFFont(FontDataContainer const& rFontDataContainer) + : mrFontDataContainer(rFontDataContainer) + { + } + + const TableDirectory* getTableDirector() + { + return reinterpret_cast<const TableDirectory*>(mrFontDataContainer.getPointer()); + } + + std::unique_ptr<TableEntriesHandler> getTableEntriesHandler() + { + auto* pDirectory = getTableDirector(); + const char* pPosition = mrFontDataContainer.getPointer() + sizeof(TableDirectory); + + std::unique_ptr<TableEntriesHandler> pHandler( + new TableEntriesHandler(mrFontDataContainer, pPosition, pDirectory->nNumberOfTables)); + return pHandler; + } + + /** Gets the string from a name table */ + OUString getNameTableString(sal_uInt64 nOffset, sal_uInt16 nLength) + { + const auto* pString = reinterpret_cast<const o3tl::sal_uInt16_BE*>( + mrFontDataContainer.getPointer() + nOffset); + OUStringBuffer aStringBuffer; + for (sal_uInt16 i = 0; i < (nLength / 2); i++) + aStringBuffer.append(sal_Unicode(pString[i])); + return aStringBuffer.makeStringAndClear(); + } +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/inc/font/TTFStructure.hxx b/vcl/inc/font/TTFStructure.hxx new file mode 100644 index 000000000000..3a751c39fca6 --- /dev/null +++ b/vcl/inc/font/TTFStructure.hxx @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <o3tl/BigEndianTypes.hxx> + +namespace font +{ +// make sure no padding bytes are added for the struct +#pragma pack(push, 1) + +/** Table directory of a TTF font, values are all in big endian */ +struct TableDirectory +{ + o3tl::sal_uInt32_BE nSfntVersion; + o3tl::sal_uInt16_BE nNumberOfTables; + o3tl::sal_uInt16_BE nSearchRange; + o3tl::sal_uInt16_BE nEntrySelector; + o3tl::sal_uInt16_BE nRangeShift; +}; + +/** Table directory entry + * + * Array of those follows TableDirectory structure. + */ +struct TableDirectoryEntry +{ + o3tl::sal_uInt32_BE tag; + o3tl::sal_uInt32_BE checkSum; + o3tl::sal_uInt32_BE offset; + o3tl::sal_uInt32_BE length; +}; + +/** Structure of the OS2 table (Version 4) + * + * See: https://learn.microsoft.com/en-us/typography/opentype/spec/os2 + */ +struct OS2Table +{ + o3tl::sal_uInt16_BE nVersion; + o3tl::sal_uInt16_BE nXAvgCharWidth; // FWORD + o3tl::sal_uInt16_BE nWeightClass; + o3tl::sal_uInt16_BE nWidthClass; + o3tl::sal_uInt16_BE nFsType; + o3tl::sal_uInt16_BE nSubscriptXSize; // FWORD + o3tl::sal_uInt16_BE nSubscriptYSize; // FWORD + o3tl::sal_uInt16_BE nSubscriptXOffset; // FWORD + o3tl::sal_uInt16_BE nSubscriptYOffset; // FWORD + o3tl::sal_uInt16_BE nSuperscriptXSize; // FWORD + o3tl::sal_uInt16_BE nSuperscriptYSize; // FWORD + o3tl::sal_uInt16_BE nSuperscriptXOffset; // FWORD + o3tl::sal_uInt16_BE nSuperscriptYOffset; // FWORD + o3tl::sal_uInt16_BE nStrikeoutSize; // FWORD + o3tl::sal_uInt16_BE nStrikeoutPosition; // FWORD + o3tl::sal_uInt16_BE nFamilyClass; + sal_uInt8 nPanose[10]; + o3tl::sal_uInt32_BE nUnicodeRange1; + o3tl::sal_uInt32_BE nUnicodeRange2; + o3tl::sal_uInt32_BE nUnicodeRange3; + o3tl::sal_uInt32_BE nUnicodeRange4; + sal_uInt8 nFontVendorID[4]; // Tag type + o3tl::sal_uInt16_BE nFsSelection; + o3tl::sal_uInt16_BE nFirstCharIndex; + o3tl::sal_uInt16_BE nLastCharIndex; + o3tl::sal_uInt16_BE nTypoAscender; // FWORD + o3tl::sal_uInt16_BE nTypoDescender; // FWORD + o3tl::sal_uInt16_BE nTypoLineGap; // FWORD + o3tl::sal_uInt16_BE nWinAscent; // UFWORD + o3tl::sal_uInt16_BE nWinDescent; // UFWORD + o3tl::sal_uInt32_BE nCodePageRange1; + o3tl::sal_uInt32_BE nCodePageRange2; + o3tl::sal_uInt16_BE nXHeight; // FWORD + o3tl::sal_uInt16_BE nCapHeight; // FWORD + o3tl::sal_uInt16_BE nDefaultChar; + o3tl::sal_uInt16_BE nBreakChar; + o3tl::sal_uInt16_BE nMaxContext; + o3tl::sal_uInt16_BE nLowerOpticalPointSize; + o3tl::sal_uInt16_BE nUpperOpticalPointSize; +}; + +// Check the size of OS2Table struct is as expected +static_assert(sizeof(OS2Table) == 100); + +/** Structure of "head" table. + * + * See: https://learn.microsoft.com/en-us/typography/opentype/spec/head + */ +struct HeadTable +{ + sal_uInt16 nMajorVersion; + sal_uInt16 nMinorVersion; + sal_uInt32 nFontRevision; + o3tl::sal_uInt32_BE nCheckSumAdjustment; + o3tl::sal_uInt32_BE nMagicNumber; + o3tl::sal_uInt16_BE nFlags; + o3tl::sal_uInt16_BE nUnitsPerEm; + sal_Int64 nCreated; // LONGDATETIME - signed 64-bit (TODO: need a BE type) + sal_Int64 nModified; // LONGDATETIME - signed 64-bit (TODO: need a BE type) + o3tl::sal_uInt16_BE nXMin; + o3tl::sal_uInt16_BE nXMax; + o3tl::sal_uInt16_BE nYMin; + o3tl::sal_uInt16_BE nYMax; + o3tl::sal_uInt16_BE nMacStyle; + o3tl::sal_uInt16_BE nLowestRectPPEM; + o3tl::sal_uInt16_BE nFontDirectionHint; + o3tl::sal_uInt16_BE nIndexToLocFormat; + o3tl::sal_uInt16_BE nGlyphDataFormat; +}; + +// Check the size of HeadTable struct is as expected +static_assert(sizeof(HeadTable) == 54); + +/** Structure of "name" table (Version 0) + * + * See: https://learn.microsoft.com/en-us/typography/opentype/spec/name + */ +struct NameTable +{ + o3tl::sal_uInt16_BE nVersion; + o3tl::sal_uInt16_BE nCount; + o3tl::sal_uInt16_BE nStorageOffset; + // Following this are NameRecords -> nCount times +}; + +/** Name record structure + * + * Array of those follows NameTable structure. + */ +struct NameRecord +{ + o3tl::sal_uInt16_BE nPlatformID; + o3tl::sal_uInt16_BE nEncodingID; + o3tl::sal_uInt16_BE nLanguageID; + o3tl::sal_uInt16_BE nNameID; + o3tl::sal_uInt16_BE nLength; // (in bytes) + o3tl::sal_uInt16_BE nStringOffset; // offset from start of storage area (in bytes) +}; + +/** Name IDs + * + * See https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids + */ +enum class NameID : sal_uInt16 +{ + Copyright = 0, // example: "© Copyright..." + FamilyName = 1, // example: "Times New Roman" + SubfamilyName = 2, // example: "Bold" + UniqueID = 3, // example: "Monotype: Times New Roman Bold: 1990" + FullFontName = 4, // example: "Times New Roman Bold" + Version = 5, // example: "Version 1.00 June 1, 1990, initial release" + PostScriptName = 6, // example: "TimesNewRoman-Bold" +}; + +#pragma pack(pop) + +} // end font namespace + +// Standard TrueType table tags +constexpr sal_uInt32 T_maxp = 0x6D617870; +constexpr sal_uInt32 T_glyf = 0x676C7966; +constexpr sal_uInt32 T_head = 0x68656164; +constexpr sal_uInt32 T_loca = 0x6C6F6361; +constexpr sal_uInt32 T_name = 0x6E616D65; +constexpr sal_uInt32 T_hhea = 0x68686561; +constexpr sal_uInt32 T_hmtx = 0x686D7478; +constexpr sal_uInt32 T_cmap = 0x636D6170; +constexpr sal_uInt32 T_vhea = 0x76686561; +constexpr sal_uInt32 T_vmtx = 0x766D7478; +constexpr sal_uInt32 T_OS2 = 0x4F532F32; +constexpr sal_uInt32 T_post = 0x706F7374; +constexpr sal_uInt32 T_cvt = 0x63767420; +constexpr sal_uInt32 T_prep = 0x70726570; +constexpr sal_uInt32 T_fpgm = 0x6670676D; +constexpr sal_uInt32 T_CFF = 0x43464620; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/inc/sft.hxx b/vcl/inc/sft.hxx index 0423c64af2af..0a2697c3e062 100644 --- a/vcl/inc/sft.hxx +++ b/vcl/inc/sft.hxx @@ -53,6 +53,8 @@ #include <memory> #include <vector> +#include "font/TTFStructure.hxx" + class SvStream; namespace vcl @@ -433,24 +435,6 @@ constexpr sal_uInt32 T_true = 0x74727565; /* 'true' */ constexpr sal_uInt32 T_ttcf = 0x74746366; /* 'ttcf' */ constexpr sal_uInt32 T_otto = 0x4f54544f; /* 'OTTO' */ -// standard TrueType table tags -constexpr sal_uInt32 T_maxp = 0x6D617870; -constexpr sal_uInt32 T_glyf = 0x676C7966; -constexpr sal_uInt32 T_head = 0x68656164; -constexpr sal_uInt32 T_loca = 0x6C6F6361; -constexpr sal_uInt32 T_name = 0x6E616D65; -constexpr sal_uInt32 T_hhea = 0x68686561; -constexpr sal_uInt32 T_hmtx = 0x686D7478; -constexpr sal_uInt32 T_cmap = 0x636D6170; -constexpr sal_uInt32 T_vhea = 0x76686561; -constexpr sal_uInt32 T_vmtx = 0x766D7478; -constexpr sal_uInt32 T_OS2 = 0x4F532F32; -constexpr sal_uInt32 T_post = 0x706F7374; -constexpr sal_uInt32 T_cvt = 0x63767420; -constexpr sal_uInt32 T_prep = 0x70726570; -constexpr sal_uInt32 T_fpgm = 0x6670676D; -constexpr sal_uInt32 T_CFF = 0x43464620; - class AbstractTrueTypeFont; class TrueTypeFont; diff --git a/vcl/qa/cppunit/font/TTFStructureTest.cxx b/vcl/qa/cppunit/font/TTFStructureTest.cxx new file mode 100644 index 000000000000..26c5f3aa0538 --- /dev/null +++ b/vcl/qa/cppunit/font/TTFStructureTest.cxx @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <test/bootstrapfixture.hxx> +#include <test/unoapi_test.hxx> + +#include <font/TTFStructure.hxx> +#include <font/TTFReader.hxx> +#include <font/TTFReader.hxx> + +namespace +{ +class TTFTest : public UnoApiTest +{ +public: + TTFTest() + : UnoApiTest("/vcl/qa/cppunit/font/data/") + { + } +}; + +CPPUNIT_TEST_FIXTURE(TTFTest, testReadTTFStructure) +{ + OUString aFileName = createFileURL(u"Ahem.ttf"); + SvFileStream aStream(aFileName, StreamMode::READ); + std::vector<sal_uInt8> aFontBytes(aStream.remainingSize()); + aStream.ReadBytes(aFontBytes.data(), aFontBytes.size()); + CPPUNIT_ASSERT(aStream.good()); + aStream.Close(); + + font::FontDataContainer aContainer(aFontBytes); + font::TTFFont aFont(aContainer); + auto pHanlder = aFont.getTableEntriesHandler(); + const font::OS2Table* pOS2 = pHanlder->getOS2Table(); + CPPUNIT_ASSERT(pOS2); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(400), sal_uInt16(pOS2->nWeightClass)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(5), sal_uInt16(pOS2->nWidthClass)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1000), sal_uInt16(pOS2->nXAvgCharWidth)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0), sal_uInt16(pOS2->nFamilyClass)); + + CPPUNIT_ASSERT_EQUAL(sal_uInt16(175), sal_uInt16(pOS2->nUnicodeRange1)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(8264), sal_uInt16(pOS2->nUnicodeRange2)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0), sal_uInt16(pOS2->nUnicodeRange3)); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0), sal_uInt16(pOS2->nUnicodeRange4)); + + const font::HeadTable* pHeadTable = pHanlder->getHeadTable(); + CPPUNIT_ASSERT(pHeadTable); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1000), sal_uInt16(pHeadTable->nUnitsPerEm)); + + auto pNameTableHanlder = pHanlder->getNameTableHandler(); + CPPUNIT_ASSERT(pNameTableHanlder); + + sal_uInt64 nOffset = 0; + sal_uInt16 nLength = 0; + + CPPUNIT_ASSERT(pNameTableHanlder->findEnglishUnicodeNameOffset(font::NameID::FamilyName, + nOffset, nLength)); + OUString aFamilyName = aFont.getNameTableString(nOffset, nLength); + CPPUNIT_ASSERT_EQUAL(u"Ahem"_ustr, aFamilyName); + + CPPUNIT_ASSERT(pNameTableHanlder->findEnglishUnicodeNameOffset(font::NameID::FullFontName, + nOffset, nLength)); + OUString aFullFontName = aFont.getNameTableString(nOffset, nLength); + CPPUNIT_ASSERT_EQUAL(u"Ahem"_ustr, aFullFontName); + + CPPUNIT_ASSERT(pNameTableHanlder->findEnglishUnicodeNameOffset(font::NameID::SubfamilyName, + nOffset, nLength)); + OUString aSubFamilyName = aFont.getNameTableString(nOffset, nLength); + CPPUNIT_ASSERT_EQUAL(u"Regular"_ustr, aSubFamilyName); + + CPPUNIT_ASSERT( + pNameTableHanlder->findEnglishUnicodeNameOffset(font::NameID::Version, nOffset, nLength)); + OUString aVersion = aFont.getNameTableString(nOffset, nLength); + CPPUNIT_ASSERT_EQUAL(u"Version 1.1"_ustr, aVersion); +} + +} // end anonymous namespace + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/qa/cppunit/font/data/Ahem.ttf b/vcl/qa/cppunit/font/data/Ahem.ttf new file mode 100644 index 000000000000..ac81cb03165a Binary files /dev/null and b/vcl/qa/cppunit/font/data/Ahem.ttf differ diff --git a/vcl/source/font/EOTConverter.cxx b/vcl/source/font/EOTConverter.cxx new file mode 100644 index 000000000000..d8b044dfc413 --- /dev/null +++ b/vcl/source/font/EOTConverter.cxx @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <string.h> +#include <vector> +#include <tools/vcompat.hxx> +#include <cstdio> +#include <tools/stream.hxx> +#include <vcl/font/EOTConverter.hxx> +#include <osl/endian.h> +#include <font/TTFStructure.hxx> +#include <font/TTFReader.hxx> + +namespace font +{ +namespace +{ +// Writes padding, length and string data to font output +void writeNameTableString(font::TTFFont& rFont, + std::unique_ptr<NameTableHandler>& pNameTableHanlder, + font::NameID eNameID, std::vector<sal_uInt8>& rEotOutput) +{ + sal_uInt64 nOffset = 0; + sal_uInt16 nLength = 0; + + // Padding + rEotOutput.push_back(0); + rEotOutput.push_back(0); + + if (pNameTableHanlder + && pNameTableHanlder->findEnglishUnicodeNameOffset(eNameID, nOffset, nLength)) + { + // Length + rEotOutput.push_back(sal_uInt8((nLength + 2) & 0xff)); + rEotOutput.push_back(sal_uInt8((nLength + 2) >> 8)); + + OUString aString = rFont.getNameTableString(nOffset, nLength); + for (sal_Int32 i = 0; i < aString.getLength(); i++) + { + sal_Unicode nUniChar = aString[i]; + rEotOutput.push_back(sal_uInt8(nUniChar & 0xff)); + rEotOutput.push_back(sal_uInt8(nUniChar >> 8)); + } + // null terminated + rEotOutput.push_back(sal_uInt8(0)); + rEotOutput.push_back(sal_uInt8(0)); + } + else + { + // Length 0 + rEotOutput.push_back(sal_uInt8(0)); + rEotOutput.push_back(sal_uInt8(0)); + } +} +} + +bool EOTConverter::convert(std::vector<sal_uInt8>& rEotOutput) +{ + font::TTFFont aFont(mrFontDataContainer); + + rEotOutput.clear(); + rEotOutput.resize(sizeof(EOTHeader)); + + EOTHeader* pEot = reinterpret_cast<EOTHeader*>(rEotOutput.data()); + pEot->nFontDataSize = mrFontDataContainer.size(); + pEot->nVersion = 0x00020002; + pEot->nFlags = 0; + pEot->nCharset = 0; + pEot->nMagicNumber = 0x504c; + pEot->nReserved1 = 0; + pEot->nReserved2 = 0; + pEot->nReserved3 = 0; + pEot->nReserved4 = 0; + + auto pHanlder = aFont.getTableEntriesHandler(); + + const font::OS2Table* pOS2 = pHanlder->getOS2Table(); + if (pOS2) + { + for (sal_uInt32 n = 0; n < 10; n++) + pEot->nFontPANOSE[n] = pOS2->nPanose[n]; + + pEot->nItalic = pOS2->nFsSelection & 0x01; + pEot->nWeight = pOS2->nWeightClass; + // FIXME: Should use OS2->fsType, but some TrueType fonts set it to an over-restrictive value. + // Since ATS does not enforce this on Mac OS X, we do not enforce it either. + pEot->nFsType = 0x0000; + pEot->nUnicodeRange1 = pOS2->nUnicodeRange1; + pEot->nUnicodeRange2 = pOS2->nUnicodeRange2; + pEot->nUnicodeRange3 = pOS2->nUnicodeRange3; + pEot->nUnicodeRange4 = pOS2->nUnicodeRange4; + pEot->nCodePageRange1 = pOS2->nCodePageRange1; + pEot->nCodePageRange2 = pOS2->nCodePageRange2; + } + + const font::HeadTable* pHeadTable = pHanlder->getHeadTable(); + if (pHeadTable) + { + pEot->nCheckSumAdjustment = pHeadTable->nCheckSumAdjustment; + } + + auto pNameTableHanlder = pHanlder->getNameTableHandler(); + + writeNameTableString(aFont, pNameTableHanlder, font::NameID::FamilyName, rEotOutput); + writeNameTableString(aFont, pNameTableHanlder, font::NameID::SubfamilyName, rEotOutput); + writeNameTableString(aFont, pNameTableHanlder, font::NameID::Version, rEotOutput); + writeNameTableString(aFont, pNameTableHanlder, font::NameID::FullFontName, rEotOutput); + + // Padding5 + rEotOutput.push_back(0); + rEotOutput.push_back(0); + + // Root String Size + rEotOutput.push_back(0); + rEotOutput.push_back(0); + + // Root String CheckSum (for size 0) + rEotOutput.push_back(0x42); + rEotOutput.push_back(0x53); + rEotOutput.push_back(0x47); + rEotOutput.push_back(0x50); + + // EUDC CodePage + rEotOutput.push_back(0xE4); + rEotOutput.push_back(0x04); + rEotOutput.push_back(0x00); + rEotOutput.push_back(0x00); + + // Padding6 + rEotOutput.push_back(0); + rEotOutput.push_back(0); + + // Signature Size = should be 0x0000 + rEotOutput.push_back(0); + rEotOutput.push_back(0); + + // EUDC Flags + rEotOutput.push_back(0); + rEotOutput.push_back(0); + rEotOutput.push_back(0); + rEotOutput.push_back(0); + + // EUDC Font Size = 0 + rEotOutput.push_back(0); + rEotOutput.push_back(0); + rEotOutput.push_back(0); + rEotOutput.push_back(0); + + // rEOTOutput could've been reallocated - need to reinterpret that. + pEot = reinterpret_cast<EOTHeader*>(rEotOutput.data()); + pEot->nEotSize = rEotOutput.size() + mrFontDataContainer.size(); + + rEotOutput.insert(rEotOutput.end(), mrFontDataContainer.begin(), mrFontDataContainer.end()); + + return true; +} + +} // end font namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */