use "Rubik" from more_fonts instead of "System" for consistent layout
[LibreOffice.git] / starmath / source / mathml / mathmlexport.cxx
blob172e0c1fe28497838d49e41f2104cce4ca53bfc7
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 .
21 Warning: The SvXMLElementExport helper class creates the beginning and
22 closing tags of xml elements in its constructor and destructor, so there's
23 hidden stuff going on, on occasion the ordering of these classes declarations
24 may be significant
27 #include <com/sun/star/xml/sax/Writer.hpp>
28 #include <com/sun/star/beans/PropertyAttribute.hpp>
29 #include <com/sun/star/embed/ElementModes.hpp>
30 #include <com/sun/star/util/MeasureUnit.hpp>
31 #include <com/sun/star/task/XStatusIndicator.hpp>
32 #include <com/sun/star/uno/Any.h>
34 #include <officecfg/Office/Common.hxx>
35 #include <rtl/math.hxx>
36 #include <sfx2/frame.hxx>
37 #include <sfx2/docfile.hxx>
38 #include <sfx2/sfxsids.hrc>
39 #include <osl/diagnose.h>
40 #include <sot/storage.hxx>
41 #include <svl/itemset.hxx>
42 #include <svl/stritem.hxx>
43 #include <comphelper/fileformat.h>
44 #include <comphelper/processfactory.hxx>
45 #include <unotools/streamwrap.hxx>
46 #include <sax/tools/converter.hxx>
47 #include <xmloff/xmlnamespace.hxx>
48 #include <xmloff/xmltoken.hxx>
49 #include <xmloff/namespacemap.hxx>
50 #include <xmloff/attrlist.hxx>
51 #include <comphelper/genericpropertyset.hxx>
52 #include <comphelper/servicehelper.hxx>
53 #include <comphelper/propertysetinfo.hxx>
54 #include <comphelper/diagnose_ex.hxx>
55 #include <sal/log.hxx>
57 #include <stack>
59 #include <mathmlexport.hxx>
60 #include <xparsmlbase.hxx>
61 #include <strings.hrc>
62 #include <smmod.hxx>
63 #include <unomodel.hxx>
64 #include <document.hxx>
65 #include <utility.hxx>
66 #include <cfgitem.hxx>
67 #include <starmathdatabase.hxx>
69 using namespace ::com::sun::star::beans;
70 using namespace ::com::sun::star::document;
71 using namespace ::com::sun::star::lang;
72 using namespace ::com::sun::star::uno;
73 using namespace ::com::sun::star;
74 using namespace ::xmloff::token;
76 namespace
78 bool IsInPrivateUseArea(sal_Unicode cChar) { return 0xE000 <= cChar && cChar <= 0xF8FF; }
80 sal_Unicode ConvertMathToMathML(sal_Unicode cChar)
82 sal_Unicode cRes = cChar;
83 if (IsInPrivateUseArea(cChar))
85 SAL_WARN("starmath", "Error: private use area characters should no longer be in use!");
86 cRes = u'@'; // just some character that should easily be notice as odd in the context
88 return cRes;
92 bool SmXMLExportWrapper::Export(SfxMedium& rMedium)
94 bool bRet = true;
95 uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
97 //Get model
98 uno::Reference<lang::XComponent> xModelComp = xModel;
100 bool bEmbedded = false;
101 SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel);
103 SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr;
104 if (pDocShell && SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode())
105 bEmbedded = true;
107 uno::Reference<task::XStatusIndicator> xStatusIndicator;
108 if (!bEmbedded)
110 if (pDocShell /*&& pDocShell->GetMedium()*/)
112 OSL_ENSURE(pDocShell->GetMedium() == &rMedium, "different SfxMedium found");
114 SfxItemSet* pSet = rMedium.GetItemSet();
115 if (pSet)
117 const SfxUnoAnyItem* pItem = pSet->GetItem(SID_PROGRESS_STATUSBAR_CONTROL);
118 if (pItem)
119 pItem->GetValue() >>= xStatusIndicator;
123 // set progress range and start status indicator
124 if (xStatusIndicator.is())
126 sal_Int32 nProgressRange = bFlat ? 1 : 3;
127 xStatusIndicator->start(SmResId(STR_STATSTR_WRITING), nProgressRange);
131 static constexpr OUStringLiteral sUsePrettyPrinting(u"UsePrettyPrinting");
132 static constexpr OUStringLiteral sBaseURI(u"BaseURI");
133 static constexpr OUStringLiteral sStreamRelPath(u"StreamRelPath");
134 static constexpr OUStringLiteral sStreamName(u"StreamName");
136 // create XPropertySet with three properties for status indicator
137 static const comphelper::PropertyMapEntry aInfoMap[] = {
138 { sUsePrettyPrinting, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::MAYBEVOID,
139 0 },
140 { sBaseURI, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0 },
141 { sStreamRelPath, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID,
142 0 },
143 { sStreamName, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0 }
145 uno::Reference<beans::XPropertySet> xInfoSet(
146 comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap)));
148 bool bUsePrettyPrinting
149 = bFlat || officecfg::Office::Common::Save::Document::PrettyPrinting::get();
150 xInfoSet->setPropertyValue(sUsePrettyPrinting, Any(bUsePrettyPrinting));
152 // Set base URI
153 xInfoSet->setPropertyValue(sBaseURI, Any(rMedium.GetBaseURL(true)));
155 sal_Int32 nSteps = 0;
156 if (xStatusIndicator.is())
157 xStatusIndicator->setValue(nSteps++);
158 if (!bFlat) //Storage (Package) of Stream
160 uno::Reference<embed::XStorage> xStg = rMedium.GetOutputStorage();
161 bool bOASIS = (SotStorage::GetVersion(xStg) > SOFFICE_FILEFORMAT_60);
163 // TODO/LATER: handle the case of embedded links gracefully
164 if (bEmbedded) //&& !pStg->IsRoot() )
166 OUString aName;
167 if (rMedium.GetItemSet())
169 const SfxStringItem* pDocHierarchItem
170 = rMedium.GetItemSet()->GetItem(SID_DOC_HIERARCHICALNAME);
171 if (pDocHierarchItem)
172 aName = pDocHierarchItem->GetValue();
175 if (!aName.isEmpty())
177 xInfoSet->setPropertyValue(sStreamRelPath, Any(aName));
181 if (!bEmbedded)
183 if (xStatusIndicator.is())
184 xStatusIndicator->setValue(nSteps++);
186 bRet = WriteThroughComponent(xStg, xModelComp, "meta.xml", xContext, xInfoSet,
187 (bOASIS ? "com.sun.star.comp.Math.XMLOasisMetaExporter"
188 : "com.sun.star.comp.Math.XMLMetaExporter"));
190 if (bRet)
192 if (xStatusIndicator.is())
193 xStatusIndicator->setValue(nSteps++);
195 bRet = WriteThroughComponent(xStg, xModelComp, "content.xml", xContext, xInfoSet,
196 "com.sun.star.comp.Math.XMLContentExporter");
199 if (bRet)
201 if (xStatusIndicator.is())
202 xStatusIndicator->setValue(nSteps++);
204 bRet = WriteThroughComponent(xStg, xModelComp, "settings.xml", xContext, xInfoSet,
205 (bOASIS ? "com.sun.star.comp.Math.XMLOasisSettingsExporter"
206 : "com.sun.star.comp.Math.XMLSettingsExporter"));
209 else
211 SvStream* pStream = rMedium.GetOutStream();
212 uno::Reference<io::XOutputStream> xOut(new utl::OOutputStreamWrapper(*pStream));
214 if (xStatusIndicator.is())
215 xStatusIndicator->setValue(nSteps++);
217 bRet = WriteThroughComponent(xOut, xModelComp, xContext, xInfoSet,
218 "com.sun.star.comp.Math.XMLContentExporter");
221 if (xStatusIndicator.is())
222 xStatusIndicator->end();
224 return bRet;
227 /// export through an XML exporter component (output stream version)
228 bool SmXMLExportWrapper::WriteThroughComponent(const Reference<io::XOutputStream>& xOutputStream,
229 const Reference<XComponent>& xComponent,
230 Reference<uno::XComponentContext> const& rxContext,
231 Reference<beans::XPropertySet> const& rPropSet,
232 const char* pComponentName)
234 OSL_ENSURE(xOutputStream.is(), "I really need an output stream!");
235 OSL_ENSURE(xComponent.is(), "Need component!");
236 OSL_ENSURE(nullptr != pComponentName, "Need component name!");
238 // get component
239 Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(rxContext);
241 // connect XML writer to output stream
242 xSaxWriter->setOutputStream(xOutputStream);
243 if (m_bUseHTMLMLEntities)
244 xSaxWriter->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntitiesExport);
246 // prepare arguments (prepend doc handler to given arguments)
247 Sequence<Any> aArgs{ Any(xSaxWriter), Any(rPropSet) };
249 // get filter component
250 Reference<document::XExporter> xExporter(
251 rxContext->getServiceManager()->createInstanceWithArgumentsAndContext(
252 OUString::createFromAscii(pComponentName), aArgs, rxContext),
253 UNO_QUERY);
254 OSL_ENSURE(xExporter.is(), "can't instantiate export filter component");
255 if (!xExporter.is())
256 return false;
258 // connect model and filter
259 xExporter->setSourceDocument(xComponent);
261 // filter!
262 Reference<XFilter> xFilter(xExporter, UNO_QUERY);
263 uno::Sequence<PropertyValue> aProps(0);
264 xFilter->filter(aProps);
266 auto pFilter = comphelper::getFromUnoTunnel<SmXMLExport>(xFilter);
267 return pFilter == nullptr || pFilter->GetSuccess();
270 /// export through an XML exporter component (storage version)
271 bool SmXMLExportWrapper::WriteThroughComponent(const Reference<embed::XStorage>& xStorage,
272 const Reference<XComponent>& xComponent,
273 const char* pStreamName,
274 Reference<uno::XComponentContext> const& rxContext,
275 Reference<beans::XPropertySet> const& rPropSet,
276 const char* pComponentName)
278 OSL_ENSURE(xStorage.is(), "Need storage!");
279 OSL_ENSURE(nullptr != pStreamName, "Need stream name!");
281 // open stream
282 Reference<io::XStream> xStream;
283 OUString sStreamName = OUString::createFromAscii(pStreamName);
286 xStream = xStorage->openStreamElement(sStreamName, embed::ElementModes::READWRITE
287 | embed::ElementModes::TRUNCATE);
289 catch (const uno::Exception&)
291 DBG_UNHANDLED_EXCEPTION("starmath", "Can't create output stream in package");
292 return false;
295 uno::Reference<beans::XPropertySet> xSet(xStream, uno::UNO_QUERY);
296 static const OUStringLiteral sMediaType = u"MediaType";
297 static const OUStringLiteral sTextXml = u"text/xml";
298 xSet->setPropertyValue(sMediaType, Any(OUString(sTextXml)));
300 // all streams must be encrypted in encrypted document
301 static const OUStringLiteral sUseCommonStoragePasswordEncryption
302 = u"UseCommonStoragePasswordEncryption";
303 xSet->setPropertyValue(sUseCommonStoragePasswordEncryption, Any(true));
305 // set Base URL
306 if (rPropSet.is())
308 rPropSet->setPropertyValue("StreamName", Any(sStreamName));
311 // write the stuff
312 bool bRet = WriteThroughComponent(xStream->getOutputStream(), xComponent, rxContext, rPropSet,
313 pComponentName);
315 return bRet;
318 SmXMLExport::SmXMLExport(const css::uno::Reference<css::uno::XComponentContext>& rContext,
319 OUString const& implementationName, SvXMLExportFlags nExportFlags)
320 : SvXMLExport(rContext, implementationName, util::MeasureUnit::INCH, XML_MATH, nExportFlags)
321 , pTree(nullptr)
322 , bSuccess(false)
326 sal_Int64 SAL_CALL SmXMLExport::getSomething(const uno::Sequence<sal_Int8>& rId)
328 return comphelper::getSomethingImpl(rId, this,
329 comphelper::FallbackToGetSomethingOf<SvXMLExport>{});
332 const uno::Sequence<sal_Int8>& SmXMLExport::getUnoTunnelId() noexcept
334 static const comphelper::UnoIdInit theSmXMLExportUnoTunnelId;
335 return theSmXMLExportUnoTunnelId.getSeq();
338 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
339 Math_XMLExporter_get_implementation(css::uno::XComponentContext* context,
340 css::uno::Sequence<css::uno::Any> const&)
342 return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLExporter",
343 SvXMLExportFlags::OASIS | SvXMLExportFlags::ALL));
346 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
347 Math_XMLMetaExporter_get_implementation(css::uno::XComponentContext* context,
348 css::uno::Sequence<css::uno::Any> const&)
350 return cppu::acquire(
351 new SmXMLExport(context, "com.sun.star.comp.Math.XMLMetaExporter", SvXMLExportFlags::META));
354 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
355 Math_XMLOasisMetaExporter_get_implementation(css::uno::XComponentContext* context,
356 css::uno::Sequence<css::uno::Any> const&)
358 return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisMetaExporter",
359 SvXMLExportFlags::OASIS | SvXMLExportFlags::META));
362 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
363 Math_XMLSettingsExporter_get_implementation(css::uno::XComponentContext* context,
364 css::uno::Sequence<css::uno::Any> const&)
366 return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLSettingsExporter",
367 SvXMLExportFlags::SETTINGS));
370 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
371 Math_XMLOasisSettingsExporter_get_implementation(css::uno::XComponentContext* context,
372 css::uno::Sequence<css::uno::Any> const&)
374 return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisSettingsExporter",
375 SvXMLExportFlags::OASIS | SvXMLExportFlags::SETTINGS));
378 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
379 Math_XMLContentExporter_get_implementation(css::uno::XComponentContext* context,
380 css::uno::Sequence<css::uno::Any> const&)
382 return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLContentExporter",
383 SvXMLExportFlags::OASIS | SvXMLExportFlags::CONTENT));
386 ErrCode SmXMLExport::exportDoc(enum XMLTokenEnum eClass)
388 if (!(getExportFlags() & SvXMLExportFlags::CONTENT))
390 SvXMLExport::exportDoc(eClass);
392 else
394 uno::Reference<frame::XModel> xModel = GetModel();
395 SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel);
397 if (pModel)
399 SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell());
400 pTree = pDocShell->GetFormulaTree();
401 aText = pDocShell->GetText();
404 GetDocHandler()->startDocument();
406 addChaffWhenEncryptedStorage();
408 /*Add xmlns line*/
409 SvXMLAttributeList& rList = GetAttrList();
411 // make use of a default namespace
412 ResetNamespaceMap(); // Math doesn't need namespaces from xmloff, since it now uses default namespaces (because that is common with current MathML usage in the web)
413 GetNamespaceMap_().Add(OUString(), GetXMLToken(XML_N_MATH), XML_NAMESPACE_MATH);
415 rList.AddAttribute(GetNamespaceMap().GetAttrNameByKey(XML_NAMESPACE_MATH),
416 GetNamespaceMap().GetNameByKey(XML_NAMESPACE_MATH));
418 //I think we need something like ImplExportEntities();
419 ExportContent_();
420 GetDocHandler()->endDocument();
423 bSuccess = true;
424 return ERRCODE_NONE;
427 void SmXMLExport::ExportContent_()
429 uno::Reference<frame::XModel> xModel = GetModel();
430 SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel);
431 SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr;
432 OSL_ENSURE(pDocShell, "doc shell missing");
434 if (pDocShell && !pDocShell->GetFormat().IsTextmode())
436 // If the Math equation is not in text mode, we attach a display="block"
437 // attribute on the <math> root. We don't do anything if it is in
438 // text mode, the default display="inline" value will be used.
439 AddAttribute(XML_NAMESPACE_MATH, XML_DISPLAY, XML_BLOCK);
441 SvXMLElementExport aEquation(*this, XML_NAMESPACE_MATH, XML_MATH, true, true);
442 std::unique_ptr<SvXMLElementExport> pSemantics;
444 if (!aText.isEmpty())
446 pSemantics.reset(
447 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_SEMANTICS, true, true));
450 ExportNodes(pTree, 0);
452 if (aText.isEmpty())
453 return;
455 SmModule* pMod = SM_MOD();
456 sal_uInt16 nSmSyntaxVersion = pMod->GetConfig()->GetDefaultSmSyntaxVersion();
458 // Convert symbol names
459 if (pDocShell)
461 nSmSyntaxVersion = pDocShell->GetSmSyntaxVersion();
462 AbstractSmParser* rParser = pDocShell->GetParser();
463 bool bVal = rParser->IsExportSymbolNames();
464 rParser->SetExportSymbolNames(true);
465 auto pTmpTree = rParser->Parse(aText);
466 aText = rParser->GetText();
467 pTmpTree.reset();
468 rParser->SetExportSymbolNames(bVal);
471 OUStringBuffer sStrBuf(12);
472 sStrBuf.append(u"StarMath ");
473 if (nSmSyntaxVersion == 5)
474 sStrBuf.append(u"5.0");
475 else
476 sStrBuf.append(static_cast<sal_Int32>(nSmSyntaxVersion));
478 AddAttribute(XML_NAMESPACE_MATH, XML_ENCODING, sStrBuf.makeStringAndClear());
479 SvXMLElementExport aAnnotation(*this, XML_NAMESPACE_MATH, XML_ANNOTATION, true, false);
480 GetDocHandler()->characters(aText);
483 void SmXMLExport::GetViewSettings(Sequence<PropertyValue>& aProps)
485 uno::Reference<frame::XModel> xModel = GetModel();
486 if (!xModel.is())
487 return;
489 SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel);
491 if (!pModel)
492 return;
494 SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell());
495 if (!pDocShell)
496 return;
498 aProps.realloc(4);
499 PropertyValue* pValue = aProps.getArray();
500 sal_Int32 nIndex = 0;
502 tools::Rectangle aRect(pDocShell->GetVisArea());
504 pValue[nIndex].Name = "ViewAreaTop";
505 pValue[nIndex++].Value <<= aRect.Top();
507 pValue[nIndex].Name = "ViewAreaLeft";
508 pValue[nIndex++].Value <<= aRect.Left();
510 pValue[nIndex].Name = "ViewAreaWidth";
511 pValue[nIndex++].Value <<= aRect.GetWidth();
513 pValue[nIndex].Name = "ViewAreaHeight";
514 pValue[nIndex++].Value <<= aRect.GetHeight();
517 void SmXMLExport::GetConfigurationSettings(Sequence<PropertyValue>& rProps)
519 Reference<XPropertySet> xProps(GetModel(), UNO_QUERY);
520 if (!xProps.is())
521 return;
523 Reference<XPropertySetInfo> xPropertySetInfo = xProps->getPropertySetInfo();
524 if (!xPropertySetInfo.is())
525 return;
527 const Sequence<Property> aProps = xPropertySetInfo->getProperties();
528 const sal_Int32 nCount = aProps.getLength();
529 if (!nCount)
530 return;
532 rProps.realloc(nCount);
533 SmMathConfig* pConfig = SM_MOD()->GetConfig();
534 const bool bUsedSymbolsOnly = pConfig && pConfig->IsSaveOnlyUsedSymbols();
536 std::transform(aProps.begin(), aProps.end(), rProps.getArray(),
537 [bUsedSymbolsOnly, &xProps](const Property& prop) {
538 PropertyValue aRet;
539 if (prop.Name != "Formula" && prop.Name != "BasicLibraries"
540 && prop.Name != "DialogLibraries" && prop.Name != "RuntimeUID")
542 aRet.Name = prop.Name;
543 OUString aActualName(prop.Name);
544 // handle 'save used symbols only'
545 static constexpr OUStringLiteral sUserDefinedSymbolsInUse
546 = u"UserDefinedSymbolsInUse";
547 if (bUsedSymbolsOnly && prop.Name == "Symbols")
548 aActualName = sUserDefinedSymbolsInUse;
549 aRet.Value = xProps->getPropertyValue(aActualName);
551 return aRet;
555 void SmXMLExport::ExportLine(const SmNode* pNode, int nLevel) { ExportExpression(pNode, nLevel); }
557 void SmXMLExport::ExportBinaryHorizontal(const SmNode* pNode, int nLevel)
559 TG nGroup = pNode->GetToken().nGroup;
561 SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
563 // Unfold the binary tree structure as long as the nodes are SmBinHorNode
564 // with the same nGroup. This will reduce the number of nested <mrow>
565 // elements e.g. we only need three <mrow> levels to export
567 // "a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l =
568 // a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l"
570 // See https://www.libreoffice.org/bugzilla/show_bug.cgi?id=66081
571 ::std::stack<const SmNode*> s;
572 s.push(pNode);
573 while (!s.empty())
575 const SmNode* node = s.top();
576 s.pop();
577 if (node->GetType() != SmNodeType::BinHor || node->GetToken().nGroup != nGroup)
579 ExportNodes(node, nLevel + 1);
580 continue;
582 const SmBinHorNode* binNode = static_cast<const SmBinHorNode*>(node);
583 s.push(binNode->RightOperand());
584 s.push(binNode->Symbol());
585 s.push(binNode->LeftOperand());
589 void SmXMLExport::ExportUnaryHorizontal(const SmNode* pNode, int nLevel)
591 ExportExpression(pNode, nLevel);
594 void SmXMLExport::ExportExpression(const SmNode* pNode, int nLevel,
595 bool bNoMrowContainer /*=false*/)
597 std::unique_ptr<SvXMLElementExport> pRow;
598 size_t nSize = pNode->GetNumSubNodes();
600 // #i115443: nodes of type expression always need to be grouped with mrow statement
601 if (!bNoMrowContainer && (nSize > 1 || pNode->GetType() == SmNodeType::Expression))
602 pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true));
604 for (size_t i = 0; i < nSize; ++i)
606 if (const SmNode* pTemp = pNode->GetSubNode(i))
607 ExportNodes(pTemp, nLevel + 1);
611 void SmXMLExport::ExportBinaryVertical(const SmNode* pNode, int nLevel)
613 assert(pNode->GetNumSubNodes() == 3);
614 const SmNode* pNum = pNode->GetSubNode(0);
615 const SmNode* pDenom = pNode->GetSubNode(2);
616 if (pNum->GetType() == SmNodeType::Align && pNum->GetToken().eType != TALIGNC)
618 // A left or right alignment is specified on the numerator:
619 // attach the corresponding numalign attribute.
620 AddAttribute(XML_NAMESPACE_MATH, XML_NUMALIGN,
621 pNum->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
623 if (pDenom->GetType() == SmNodeType::Align && pDenom->GetToken().eType != TALIGNC)
625 // A left or right alignment is specified on the denominator:
626 // attach the corresponding denomalign attribute.
627 AddAttribute(XML_NAMESPACE_MATH, XML_DENOMALIGN,
628 pDenom->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
630 SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true);
631 ExportNodes(pNum, nLevel);
632 ExportNodes(pDenom, nLevel);
635 void SmXMLExport::ExportBinaryDiagonal(const SmNode* pNode, int nLevel)
637 assert(pNode->GetNumSubNodes() == 3);
639 if (pNode->GetToken().eType == TWIDESLASH)
641 // wideslash
642 // export the node as <mfrac bevelled="true">
643 AddAttribute(XML_NAMESPACE_MATH, XML_BEVELLED, XML_TRUE);
644 SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true);
645 ExportNodes(pNode->GetSubNode(0), nLevel);
646 ExportNodes(pNode->GetSubNode(1), nLevel);
648 else
650 // widebslash
651 // We can not use <mfrac> to a backslash, so just use <mo>\</mo>
652 SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
654 ExportNodes(pNode->GetSubNode(0), nLevel);
656 { // Scoping for <mo> creation
657 SvXMLElementExport aMo(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
658 GetDocHandler()->characters(OUStringChar(MS_BACKSLASH));
661 ExportNodes(pNode->GetSubNode(1), nLevel);
665 void SmXMLExport::ExportTable(const SmNode* pNode, int nLevel)
667 std::unique_ptr<SvXMLElementExport> pTable;
669 size_t nSize = pNode->GetNumSubNodes();
671 //If the list ends in newline then the last entry has
672 //no subnodes, the newline is superfluous so we just drop
673 //the last node, inclusion would create a bad MathML
674 //table
675 if (nSize >= 1)
677 const SmNode* pLine = pNode->GetSubNode(nSize - 1);
678 if (pLine->GetType() == SmNodeType::Line && pLine->GetNumSubNodes() == 1
679 && pLine->GetSubNode(0) != nullptr
680 && pLine->GetSubNode(0)->GetToken().eType == TNEWLINE)
681 --nSize;
684 // try to avoid creating a mtable element when the formula consists only
685 // of a single output line
686 if (nLevel || (nSize > 1))
687 pTable.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true));
689 for (size_t i = 0; i < nSize; ++i)
691 if (const SmNode* pTemp = pNode->GetSubNode(i))
693 std::unique_ptr<SvXMLElementExport> pRow;
694 std::unique_ptr<SvXMLElementExport> pCell;
695 if (pTable)
697 pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTR, true, true));
698 SmTokenType eAlign = TALIGNC;
699 if (pTemp->GetType() == SmNodeType::Align)
701 // For Binom() and Stack() constructions, the SmNodeType::Align nodes
702 // are direct children.
703 // binom{alignl ...}{alignr ...} and
704 // stack{alignl ... ## alignr ... ## ...}
705 eAlign = pTemp->GetToken().eType;
707 else if (pTemp->GetType() == SmNodeType::Line && pTemp->GetNumSubNodes() == 1
708 && pTemp->GetSubNode(0)
709 && pTemp->GetSubNode(0)->GetType() == SmNodeType::Align)
711 // For the Table() construction, the SmNodeType::Align node is a child
712 // of an SmNodeType::Line node.
713 // alignl ... newline alignr ... newline ...
714 eAlign = pTemp->GetSubNode(0)->GetToken().eType;
716 if (eAlign != TALIGNC)
718 // If a left or right alignment is specified on this line,
719 // attach the corresponding columnalign attribute.
720 AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN,
721 eAlign == TALIGNL ? XML_LEFT : XML_RIGHT);
723 pCell.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTD, true, true));
725 ExportNodes(pTemp, nLevel + 1);
730 void SmXMLExport::ExportMath(const SmNode* pNode)
732 const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
733 std::unique_ptr<SvXMLElementExport> pMath;
735 if (pNode->GetType() == SmNodeType::Math || pNode->GetType() == SmNodeType::GlyphSpecial)
737 // Export SmNodeType::Math and SmNodeType::GlyphSpecial symbols as <mo> elements
738 pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MO, true, false));
740 else if (pNode->GetType() == SmNodeType::Special)
742 bool bIsItalic = IsItalic(pNode->GetFont());
743 if (!bIsItalic)
744 AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
745 pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
747 else
749 // Export SmNodeType::MathIdent and SmNodeType::Place symbols as <mi> elements:
750 // - These math symbols should not be drawn slanted. Hence we should
751 // attach a mathvariant="normal" attribute to single-char <mi> elements
752 // that are not mathematical alphanumeric symbol. For simplicity and to
753 // work around browser limitations, we always attach such an attribute.
754 // - The MathML specification suggests to use empty <mi> elements as
755 // placeholders but they won't be visible in most MathML rendering
756 // engines so let's use an empty square for SmNodeType::Place instead.
757 AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
758 pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
760 sal_Unicode nArse = pTemp->GetText()[0];
761 sal_Unicode cTmp = ConvertMathToMathML(nArse);
762 if (cTmp != 0)
763 nArse = cTmp;
764 OSL_ENSURE(nArse != 0xffff, "Non existent symbol");
765 GetDocHandler()->characters(OUString(nArse));
768 void SmXMLExport::ExportText(const SmNode* pNode)
770 std::unique_ptr<SvXMLElementExport> pText;
771 const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
772 switch (pNode->GetToken().eType)
774 default:
775 case TIDENT:
777 //Note that we change the fontstyle to italic for strings that
778 //are italic and longer than a single character.
779 bool bIsItalic = IsItalic(pTemp->GetFont());
780 if ((pTemp->GetText().getLength() > 1) && bIsItalic)
781 AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_ITALIC);
782 else if ((pTemp->GetText().getLength() == 1) && !bIsItalic)
783 AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL);
784 pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false));
785 break;
787 case TNUMBER:
788 pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MN, true, false));
789 break;
790 case TTEXT:
791 pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTEXT, true, false));
792 break;
794 GetDocHandler()->characters(pTemp->GetText());
797 void SmXMLExport::ExportBlank(const SmNode* pNode)
799 const SmBlankNode* pTemp = static_cast<const SmBlankNode*>(pNode);
800 //!! exports an <mspace> element. Note that for example "~_~" is allowed in
801 //!! Math (so it has no sense at all) but must not result in an empty
802 //!! <msub> tag in MathML !!
804 if (pTemp->GetBlankNum() != 0)
806 // Attach a width attribute. We choose the (somewhat arbitrary) values
807 // ".5em" for a small gap '`' and "2em" for a large gap '~'.
808 // (see SmBlankNode::IncreaseBy for how pTemp->mnNum is set).
809 OUStringBuffer sStrBuf;
810 ::sax::Converter::convertDouble(sStrBuf, pTemp->GetBlankNum() * .5);
811 sStrBuf.append("em");
812 AddAttribute(XML_NAMESPACE_MATH, XML_WIDTH, sStrBuf.makeStringAndClear());
815 SvXMLElementExport aTextExport(*this, XML_NAMESPACE_MATH, XML_MSPACE, true, false);
817 GetDocHandler()->characters(OUString());
820 void SmXMLExport::ExportSubSupScript(const SmNode* pNode, int nLevel)
822 const SmNode* pSub = nullptr;
823 const SmNode* pSup = nullptr;
824 const SmNode* pCSub = nullptr;
825 const SmNode* pCSup = nullptr;
826 const SmNode* pLSub = nullptr;
827 const SmNode* pLSup = nullptr;
828 std::unique_ptr<SvXMLElementExport> pThing2;
830 //if we have prescripts at all then we must use the tensor notation
832 //This is one of those excellent locations where scope is vital to
833 //arrange the construction and destruction of the element helper
834 //classes correctly
835 pLSub = pNode->GetSubNode(LSUB + 1);
836 pLSup = pNode->GetSubNode(LSUP + 1);
837 if (pLSub || pLSup)
839 SvXMLElementExport aMultiScripts(*this, XML_NAMESPACE_MATH, XML_MMULTISCRIPTS, true, true);
841 if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1))
842 && nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
844 pThing2.reset(
845 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDEROVER, true, true));
847 else if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1)))
849 pThing2.reset(
850 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
852 else if (nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
854 pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
857 ExportNodes(pNode->GetSubNode(0), nLevel + 1); //Main Term
859 if (pCSub)
860 ExportNodes(pCSub, nLevel + 1);
861 if (pCSup)
862 ExportNodes(pCSup, nLevel + 1);
863 pThing2.reset();
865 pSub = pNode->GetSubNode(RSUB + 1);
866 pSup = pNode->GetSubNode(RSUP + 1);
867 if (pSub || pSup)
869 if (pSub)
870 ExportNodes(pSub, nLevel + 1);
871 else
873 SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
875 if (pSup)
876 ExportNodes(pSup, nLevel + 1);
877 else
879 SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
883 //Separator element between suffix and prefix sub/sup pairs
885 SvXMLElementExport aPrescripts(*this, XML_NAMESPACE_MATH, XML_MPRESCRIPTS, true, true);
888 if (pLSub)
889 ExportNodes(pLSub, nLevel + 1);
890 else
892 SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
894 if (pLSup)
895 ExportNodes(pLSup, nLevel + 1);
896 else
898 SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true);
901 else
903 std::unique_ptr<SvXMLElementExport> pThing;
904 if (nullptr != (pSub = pNode->GetSubNode(RSUB + 1))
905 && nullptr != (pSup = pNode->GetSubNode(RSUP + 1)))
907 pThing.reset(
908 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUBSUP, true, true));
910 else if (nullptr != (pSub = pNode->GetSubNode(RSUB + 1)))
912 pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUB, true, true));
914 else if (nullptr != (pSup = pNode->GetSubNode(RSUP + 1)))
916 pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUP, true, true));
919 if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1))
920 && nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
922 pThing2.reset(
923 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDEROVER, true, true));
925 else if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1)))
927 pThing2.reset(
928 new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
930 else if (nullptr != (pCSup = pNode->GetSubNode(CSUP + 1)))
932 pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
934 ExportNodes(pNode->GetSubNode(0), nLevel + 1); //Main Term
936 if (pCSub)
937 ExportNodes(pCSub, nLevel + 1);
938 if (pCSup)
939 ExportNodes(pCSup, nLevel + 1);
940 pThing2.reset();
942 if (pSub)
943 ExportNodes(pSub, nLevel + 1);
944 if (pSup)
945 ExportNodes(pSup, nLevel + 1);
946 pThing.reset();
950 void SmXMLExport::ExportBrace(const SmNode* pNode, int nLevel)
952 const SmNode* pTemp;
953 const SmNode* pLeft = pNode->GetSubNode(0);
954 const SmNode* pRight = pNode->GetSubNode(2);
956 // This used to generate <mfenced> or <mrow>+<mo> elements according to
957 // the stretchiness of fences. The MathML recommendation defines an
958 // <mrow>+<mo> construction that is equivalent to the <mfenced> element:
959 // http://www.w3.org/TR/MathML3/chapter3.html#presm.mfenced
960 // To simplify our code and avoid issues with mfenced implementations in
961 // MathML rendering engines, we now always generate <mrow>+<mo> elements.
962 // See #fdo 66282.
964 // <mrow>
965 SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
967 // <mo fence="true"> opening-fence </mo>
968 if (pLeft && (pLeft->GetToken().eType != TNONE))
970 AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE);
971 AddAttribute(XML_NAMESPACE_MATH, XML_FORM, XML_PREFIX);
972 if (pNode->GetScaleMode() == SmScaleMode::Height)
973 AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
974 else
975 AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
976 ExportNodes(pLeft, nLevel + 1);
979 if (nullptr != (pTemp = pNode->GetSubNode(1)))
981 // <mrow>
982 SvXMLElementExport aRowExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
983 ExportNodes(pTemp, nLevel + 1);
984 // </mrow>
987 // <mo fence="true"> closing-fence </mo>
988 if (pRight && (pRight->GetToken().eType != TNONE))
990 AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE);
991 AddAttribute(XML_NAMESPACE_MATH, XML_FORM, XML_POSTFIX);
992 if (pNode->GetScaleMode() == SmScaleMode::Height)
993 AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
994 else
995 AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
996 ExportNodes(pRight, nLevel + 1);
999 // </mrow>
1002 void SmXMLExport::ExportRoot(const SmNode* pNode, int nLevel)
1004 if (pNode->GetSubNode(0))
1006 SvXMLElementExport aRoot(*this, XML_NAMESPACE_MATH, XML_MROOT, true, true);
1007 ExportNodes(pNode->GetSubNode(2), nLevel + 1);
1008 ExportNodes(pNode->GetSubNode(0), nLevel + 1);
1010 else
1012 SvXMLElementExport aSqrt(*this, XML_NAMESPACE_MATH, XML_MSQRT, true, true);
1013 ExportNodes(pNode->GetSubNode(2), nLevel + 1);
1017 void SmXMLExport::ExportOperator(const SmNode* pNode, int nLevel)
1019 /*we need to either use content or font and size attributes
1020 *here*/
1021 SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true);
1022 ExportNodes(pNode->GetSubNode(0), nLevel + 1);
1023 ExportNodes(pNode->GetSubNode(1), nLevel + 1);
1026 void SmXMLExport::ExportAttributes(const SmNode* pNode, int nLevel)
1028 std::unique_ptr<SvXMLElementExport> pElement;
1030 if (pNode->GetToken().eType == TUNDERLINE)
1032 AddAttribute(XML_NAMESPACE_MATH, XML_ACCENTUNDER, XML_TRUE);
1033 pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true));
1035 else if (pNode->GetToken().eType == TOVERSTRIKE)
1037 // export as <menclose notation="horizontalstrike">
1038 AddAttribute(XML_NAMESPACE_MATH, XML_NOTATION, XML_HORIZONTALSTRIKE);
1039 pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MENCLOSE, true, true));
1041 else
1043 AddAttribute(XML_NAMESPACE_MATH, XML_ACCENT, XML_TRUE);
1044 pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true));
1047 ExportNodes(pNode->GetSubNode(1), nLevel + 1);
1048 switch (pNode->GetToken().eType)
1050 case TOVERLINE:
1052 //proper entity support required
1053 SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
1054 static constexpr OUStringLiteral nArse = u"\u00AF";
1055 GetDocHandler()->characters(nArse);
1057 break;
1058 case TUNDERLINE:
1060 //proper entity support required
1061 SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, true, true);
1062 static constexpr OUStringLiteral nArse = u"\u0332";
1063 GetDocHandler()->characters(nArse);
1065 break;
1066 case TOVERSTRIKE:
1067 break;
1068 case TWIDETILDE:
1069 case TWIDEHAT:
1070 case TWIDEVEC:
1071 case TWIDEHARPOON:
1073 // make these wide accents stretchy
1074 AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
1075 ExportNodes(pNode->GetSubNode(0), nLevel + 1);
1077 break;
1078 default:
1079 ExportNodes(pNode->GetSubNode(0), nLevel + 1);
1080 break;
1084 static bool lcl_HasEffectOnMathvariant(const SmTokenType eType)
1086 return eType == TBOLD || eType == TNBOLD || eType == TITALIC || eType == TNITALIC
1087 || eType == TSANS || eType == TSERIF || eType == TFIXED;
1090 void SmXMLExport::ExportFont(const SmNode* pNode, int nLevel)
1092 // gather the mathvariant attribute relevant data from all
1093 // successively following SmFontNodes...
1095 int nBold = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true;
1096 int nItalic = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true;
1097 int nSansSerifFixed = -1;
1098 SmTokenType eNodeType = TUNKNOWN;
1100 for (;;)
1102 eNodeType = pNode->GetToken().eType;
1103 if (!lcl_HasEffectOnMathvariant(eNodeType))
1104 break;
1105 switch (eNodeType)
1107 case TBOLD:
1108 nBold = 1;
1109 break;
1110 case TNBOLD:
1111 nBold = 0;
1112 break;
1113 case TITALIC:
1114 nItalic = 1;
1115 break;
1116 case TNITALIC:
1117 nItalic = 0;
1118 break;
1119 case TSANS:
1120 nSansSerifFixed = 0;
1121 break;
1122 case TSERIF:
1123 nSansSerifFixed = 1;
1124 break;
1125 case TFIXED:
1126 nSansSerifFixed = 2;
1127 break;
1128 default:
1129 SAL_WARN("starmath", "unexpected case");
1131 // According to the parser every node that is to be evaluated here
1132 // has a single non-zero subnode at index 1!! Thus we only need to check
1133 // that single node for follow-up nodes that have an effect on the attribute.
1134 if (pNode->GetNumSubNodes() > 1 && pNode->GetSubNode(1)
1135 && lcl_HasEffectOnMathvariant(pNode->GetSubNode(1)->GetToken().eType))
1137 pNode = pNode->GetSubNode(1);
1139 else
1140 break;
1143 sal_uInt32 nc;
1144 switch (pNode->GetToken().eType)
1146 case TPHANTOM:
1147 // No attribute needed. An <mphantom> element will be used below.
1148 break;
1149 case TMATHMLCOL:
1151 nc = pNode->GetToken().cMathChar.toUInt32(16);
1152 const OUString& sssStr = starmathdatabase::Identify_Color_MATHML(nc).aIdent;
1153 AddAttribute(XML_NAMESPACE_MATH, XML_MATHCOLOR, sssStr);
1155 break;
1156 case TRGB:
1157 case TRGBA:
1158 case THEX:
1159 case THTMLCOL:
1160 case TDVIPSNAMESCOL:
1161 case TICONICCOL:
1163 nc = pNode->GetToken().cMathChar.toUInt32(16);
1164 OUString ssStr("#" + Color(ColorTransparency, nc).AsRGBHEXString());
1165 AddAttribute(XML_NAMESPACE_MATH, XML_MATHCOLOR, ssStr);
1167 break;
1168 case TSIZE:
1170 const SmFontNode* pFontNode = static_cast<const SmFontNode*>(pNode);
1171 const Fraction& aFrac = pFontNode->GetSizeParameter();
1173 OUStringBuffer sStrBuf;
1174 switch (pFontNode->GetSizeType())
1176 case FontSizeType::MULTIPLY:
1177 ::sax::Converter::convertDouble(sStrBuf,
1178 static_cast<double>(aFrac * Fraction(100.00)));
1179 sStrBuf.append('%');
1180 break;
1181 case FontSizeType::DIVIDE:
1182 ::sax::Converter::convertDouble(sStrBuf,
1183 static_cast<double>(Fraction(100.00) / aFrac));
1184 sStrBuf.append('%');
1185 break;
1186 case FontSizeType::ABSOLUT:
1187 ::sax::Converter::convertDouble(sStrBuf, static_cast<double>(aFrac));
1188 sStrBuf.append(GetXMLToken(XML_UNIT_PT));
1189 break;
1190 default:
1192 //The problem here is that the wheels fall off because
1193 //font size is stored in 100th's of a mm not pts, and
1194 //rounding errors take their toll on the original
1195 //value specified in points.
1197 //Must fix StarMath to retain the original pt values
1198 Fraction aTemp = Sm100th_mmToPts(pFontNode->GetFont().GetFontSize().Height());
1200 if (pFontNode->GetSizeType() == FontSizeType::MINUS)
1201 aTemp -= aFrac;
1202 else
1203 aTemp += aFrac;
1205 double mytest = static_cast<double>(aTemp);
1207 mytest = ::rtl::math::round(mytest, 1);
1208 ::sax::Converter::convertDouble(sStrBuf, mytest);
1209 sStrBuf.append(GetXMLToken(XML_UNIT_PT));
1211 break;
1214 OUString sStr(sStrBuf.makeStringAndClear());
1215 AddAttribute(XML_NAMESPACE_MATH, XML_MATHSIZE, sStr);
1217 break;
1218 case TBOLD:
1219 case TITALIC:
1220 case TNBOLD:
1221 case TNITALIC:
1222 case TFIXED:
1223 case TSANS:
1224 case TSERIF:
1226 // nBold: -1 = yet undefined; 0 = false; 1 = true;
1227 // nItalic: -1 = yet undefined; 0 = false; 1 = true;
1228 // nSansSerifFixed: -1 = undefined; 0 = sans; 1 = serif; 2 = fixed;
1229 const char* pText = "normal";
1230 if (nSansSerifFixed == -1 || nSansSerifFixed == 1)
1232 pText = "normal";
1233 if (nBold == 1 && nItalic != 1)
1234 pText = "bold";
1235 else if (nBold != 1 && nItalic == 1)
1236 pText = "italic";
1237 else if (nBold == 1 && nItalic == 1)
1238 pText = "bold-italic";
1240 else if (nSansSerifFixed == 0)
1242 pText = "sans-serif";
1243 if (nBold == 1 && nItalic != 1)
1244 pText = "bold-sans-serif";
1245 else if (nBold != 1 && nItalic == 1)
1246 pText = "sans-serif-italic";
1247 else if (nBold == 1 && nItalic == 1)
1248 pText = "sans-serif-bold-italic";
1250 else if (nSansSerifFixed == 2)
1251 pText = "monospace"; // no modifiers allowed for monospace ...
1252 else
1254 SAL_WARN("starmath", "unexpected case");
1256 AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, OUString::createFromAscii(pText));
1258 break;
1259 default:
1260 break;
1263 // Wrap everything in an <mphantom> or <mstyle> element. These elements
1264 // are mrow-like, so ExportExpression doesn't need to add an explicit
1265 // <mrow> element. See #fdo 66283.
1266 SvXMLElementExport aElement(*this, XML_NAMESPACE_MATH,
1267 pNode->GetToken().eType == TPHANTOM ? XML_MPHANTOM : XML_MSTYLE,
1268 true, true);
1269 ExportExpression(pNode, nLevel, true);
1273 void SmXMLExport::ExportVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel)
1275 // "[body] overbrace [script]"
1277 // Position body, overbrace and script vertically. First place the overbrace
1278 // OVER the body and then the script OVER this expression.
1280 // [script]
1281 // --[overbrace]--
1282 // XXXXXX[body]XXXXXXX
1284 // Similarly for the underbrace construction.
1286 XMLTokenEnum which;
1288 switch (pNode->GetToken().eType)
1290 case TOVERBRACE:
1291 default:
1292 which = XML_MOVER;
1293 break;
1294 case TUNDERBRACE:
1295 which = XML_MUNDER;
1296 break;
1299 SvXMLElementExport aOver1(*this, XML_NAMESPACE_MATH, which, true, true);
1300 { //Scoping
1301 // using accents will draw the over-/underbraces too close to the base
1302 // see http://www.w3.org/TR/MathML2/chapter3.html#id.3.4.5.2
1303 // also XML_ACCENT is illegal with XML_MUNDER. Thus no XML_ACCENT attribute here!
1304 SvXMLElementExport aOver2(*this, XML_NAMESPACE_MATH, which, true, true);
1305 ExportNodes(pNode->Body(), nLevel);
1306 AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
1307 ExportNodes(pNode->Brace(), nLevel);
1309 ExportNodes(pNode->Script(), nLevel);
1312 void SmXMLExport::ExportMatrix(const SmNode* pNode, int nLevel)
1314 SvXMLElementExport aTable(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true);
1315 const SmMatrixNode* pMatrix = static_cast<const SmMatrixNode*>(pNode);
1316 size_t i = 0;
1317 for (sal_uInt16 y = 0; y < pMatrix->GetNumRows(); y++)
1319 SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MTR, true, true);
1320 for (sal_uInt16 x = 0; x < pMatrix->GetNumCols(); x++)
1322 if (const SmNode* pTemp = pNode->GetSubNode(i++))
1324 if (pTemp->GetType() == SmNodeType::Align && pTemp->GetToken().eType != TALIGNC)
1326 // A left or right alignment is specified on this cell,
1327 // attach the corresponding columnalign attribute.
1328 AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN,
1329 pTemp->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT);
1331 SvXMLElementExport aCell(*this, XML_NAMESPACE_MATH, XML_MTD, true, true);
1332 ExportNodes(pTemp, nLevel + 1);
1338 void SmXMLExport::ExportNodes(const SmNode* pNode, int nLevel)
1340 if (!pNode)
1341 return;
1342 switch (pNode->GetType())
1344 case SmNodeType::Table:
1345 ExportTable(pNode, nLevel);
1346 break;
1347 case SmNodeType::Align:
1348 case SmNodeType::Bracebody:
1349 case SmNodeType::Expression:
1350 ExportExpression(pNode, nLevel);
1351 break;
1352 case SmNodeType::Line:
1353 ExportLine(pNode, nLevel);
1354 break;
1355 case SmNodeType::Text:
1356 ExportText(pNode);
1357 break;
1358 case SmNodeType::GlyphSpecial:
1359 case SmNodeType::Math:
1361 sal_Unicode cTmp = 0;
1362 const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode);
1363 if (!pTemp->GetText().isEmpty())
1364 cTmp = ConvertMathToMathML(pTemp->GetText()[0]);
1365 if (cTmp == 0)
1367 // no conversion to MathML implemented -> export it as text
1368 // thus at least it will not vanish into nothing
1369 ExportText(pNode);
1371 else
1373 switch (pNode->GetToken().eType)
1375 case TINTD:
1376 AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE);
1377 break;
1378 default:
1379 break;
1381 //To fully handle generic MathML we need to implement the full
1382 //operator dictionary, we will generate MathML with explicit
1383 //stretchiness for now.
1384 sal_Int16 nLength = GetAttrList().getLength();
1385 bool bAddStretch = true;
1386 for (sal_Int16 i = 0; i < nLength; i++)
1388 OUString sLocalName;
1389 sal_uInt16 nPrefix = GetNamespaceMap().GetKeyByAttrName(
1390 GetAttrList().getNameByIndex(i), &sLocalName);
1392 if ((XML_NAMESPACE_MATH == nPrefix) && IsXMLToken(sLocalName, XML_STRETCHY))
1394 bAddStretch = false;
1395 break;
1398 if (bAddStretch)
1400 AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE);
1402 ExportMath(pNode);
1405 break;
1406 case SmNodeType::
1407 Special: //SmNodeType::Special requires some sort of Entity preservation in the XML engine.
1408 case SmNodeType::MathIdent:
1409 case SmNodeType::Place:
1410 ExportMath(pNode);
1411 break;
1412 case SmNodeType::BinHor:
1413 ExportBinaryHorizontal(pNode, nLevel);
1414 break;
1415 case SmNodeType::UnHor:
1416 ExportUnaryHorizontal(pNode, nLevel);
1417 break;
1418 case SmNodeType::Brace:
1419 ExportBrace(pNode, nLevel);
1420 break;
1421 case SmNodeType::BinVer:
1422 ExportBinaryVertical(pNode, nLevel);
1423 break;
1424 case SmNodeType::BinDiagonal:
1425 ExportBinaryDiagonal(pNode, nLevel);
1426 break;
1427 case SmNodeType::SubSup:
1428 ExportSubSupScript(pNode, nLevel);
1429 break;
1430 case SmNodeType::Root:
1431 ExportRoot(pNode, nLevel);
1432 break;
1433 case SmNodeType::Oper:
1434 ExportOperator(pNode, nLevel);
1435 break;
1436 case SmNodeType::Attribute:
1437 ExportAttributes(pNode, nLevel);
1438 break;
1439 case SmNodeType::Font:
1440 ExportFont(pNode, nLevel);
1441 break;
1442 case SmNodeType::VerticalBrace:
1443 ExportVerticalBrace(static_cast<const SmVerticalBraceNode*>(pNode), nLevel);
1444 break;
1445 case SmNodeType::Matrix:
1446 ExportMatrix(pNode, nLevel);
1447 break;
1448 case SmNodeType::Blank:
1449 ExportBlank(pNode);
1450 break;
1451 default:
1452 SAL_WARN("starmath", "Warning: failed to export a node?");
1453 break;
1457 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */