diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /starmath/source | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'starmath/source')
53 files changed, 35493 insertions, 0 deletions
diff --git a/starmath/source/AccessibleSmElement.cxx b/starmath/source/AccessibleSmElement.cxx new file mode 100644 index 000000000..9903ce75d --- /dev/null +++ b/starmath/source/AccessibleSmElement.cxx @@ -0,0 +1,286 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <AccessibleSmElement.hxx> +#include <ElementsDockingWindow.hxx> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <toolkit/helper/convert.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star; +using OContextEntryGuard = ::comphelper::OContextEntryGuard; +using OExternalLockGuard = ::comphelper::OExternalLockGuard; + +AccessibleSmElement::AccessibleSmElement(SmElementsControl* pSmElementsControl, sal_uInt16 nItemId, + sal_Int32 nIndexInParent) + : m_pSmElementsControl(pSmElementsControl) + , m_nIndexInParent(nIndexInParent) + , m_nItemId(nItemId) + , m_bHasFocus(false) +{ + assert(m_pSmElementsControl); + m_nRole = m_pSmElementsControl->itemIsSeparator(m_nItemId) ? AccessibleRole::SEPARATOR + : AccessibleRole::PUSH_BUTTON; +} + +AccessibleSmElement::~AccessibleSmElement() {} + +void AccessibleSmElement::SetFocus(bool bFocus) +{ + if (m_bHasFocus == bFocus) + return; + + uno::Any aOldValue; + uno::Any aNewValue; + if (m_bHasFocus) + aOldValue <<= AccessibleStateType::FOCUSED; + else + aNewValue <<= AccessibleStateType::FOCUSED; + m_bHasFocus = bFocus; + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aOldValue, aNewValue); +} + +awt::Rectangle AccessibleSmElement::implGetBounds() +{ + awt::Rectangle aRect; + if (m_pSmElementsControl) + aRect = AWTRectangle(m_pSmElementsControl->itemPosRect(m_nItemId)); + return aRect; +} + +// XInterface + +IMPLEMENT_FORWARD_REFCOUNT(AccessibleSmElement, comphelper::OAccessibleComponentHelper) + +uno::Any AccessibleSmElement::queryInterface(const uno::Type& _rType) +{ + if (_rType == cppu::UnoType<XAccessibleAction>::get() + && (!m_pSmElementsControl || m_pSmElementsControl->itemIsSeparator(m_nItemId))) + return uno::Any(); + + uno::Any aReturn = comphelper::OAccessibleComponentHelper::queryInterface(_rType); + if (!aReturn.hasValue()) + aReturn = AccessibleSmElement_BASE::queryInterface(_rType); + return aReturn; +} + +// XTypeProvider + +IMPLEMENT_FORWARD_XTYPEPROVIDER2(AccessibleSmElement, comphelper::OAccessibleComponentHelper, + AccessibleSmElement_BASE) + +// XComponent + +void AccessibleSmElement::disposing() +{ + comphelper::OAccessibleComponentHelper::disposing(); + m_pSmElementsControl = nullptr; +} + +// XServiceInfo + +OUString AccessibleSmElement::getImplementationName() +{ + return "com.sun.star.comp.toolkit.AccessibleSmElement"; +} + +sal_Bool AccessibleSmElement::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence<OUString> AccessibleSmElement::getSupportedServiceNames() +{ + return { "com.sun.star.accessibility.AccessibleContext", + "com.sun.star.accessibility.AccessibleComponent", + "com.sun.star.accessibility.AccessibleSmElement" }; +} + +// XAccessible + +uno::Reference<XAccessibleContext> AccessibleSmElement::getAccessibleContext() { return this; } + +// XAccessibleContext + +sal_Int32 AccessibleSmElement::getAccessibleChildCount() { return 0; } + +uno::Reference<accessibility::XAccessible> AccessibleSmElement::getAccessibleChild(sal_Int32) +{ + return uno::Reference<XAccessible>(); +} + +uno::Reference<XAccessible> AccessibleSmElement::getAccessibleParent() +{ + OContextEntryGuard aGuard(this); + uno::Reference<XAccessible> xParent; + if (m_pSmElementsControl) + xParent.set(m_pSmElementsControl->GetAccessible().get()); + return xParent; +} + +sal_Int32 AccessibleSmElement::getAccessibleIndexInParent() +{ + OContextEntryGuard aGuard(this); + return m_nIndexInParent; +} + +sal_Int16 AccessibleSmElement::getAccessibleRole() +{ + OContextEntryGuard aGuard(this); + return m_nRole; +} + +OUString AccessibleSmElement::getAccessibleDescription() { return getAccessibleName(); } + +OUString AccessibleSmElement::getAccessibleName() +{ + OExternalLockGuard aGuard(this); + OUString aName; + if (m_pSmElementsControl) + aName = m_pSmElementsControl->itemName(m_nItemId); + return aName; +} + +uno::Reference<XAccessibleRelationSet> AccessibleSmElement::getAccessibleRelationSet() +{ + OContextEntryGuard aGuard(this); + + utl::AccessibleRelationSetHelper* pRelationSetHelper = new utl::AccessibleRelationSetHelper; + uno::Reference<XAccessibleRelationSet> xSet = pRelationSetHelper; + return xSet; +} + +uno::Reference<XAccessibleStateSet> AccessibleSmElement::getAccessibleStateSet() +{ + OExternalLockGuard aGuard(this); + + utl::AccessibleStateSetHelper* pStateSetHelper = new utl::AccessibleStateSetHelper; + uno::Reference<XAccessibleStateSet> xStateSet = pStateSetHelper; + + if (m_pSmElementsControl && !rBHelper.bDisposed && !rBHelper.bInDispose) + { + if (m_pSmElementsControl->itemIsVisible(m_nItemId)) + pStateSetHelper->AddState(AccessibleStateType::VISIBLE); + if (!m_pSmElementsControl->itemIsSeparator(m_nItemId)) + { + if (m_pSmElementsControl->IsEnabled()) + { + pStateSetHelper->AddState(AccessibleStateType::ENABLED); + pStateSetHelper->AddState(AccessibleStateType::SENSITIVE); + } + pStateSetHelper->AddState(AccessibleStateType::FOCUSABLE); + if (m_bHasFocus) + pStateSetHelper->AddState(AccessibleStateType::FOCUSED); + } + } + else + pStateSetHelper->AddState(AccessibleStateType::DEFUNC); + + return xStateSet; +} + +// XAccessibleComponent + +uno::Reference<XAccessible> AccessibleSmElement::getAccessibleAtPoint(const awt::Point&) +{ + return uno::Reference<XAccessible>(); +} + +void AccessibleSmElement::grabFocus() +{ + uno::Reference<XAccessible> xParent(getAccessibleParent()); + + if (xParent.is()) + { + uno::Reference<XAccessibleSelection> rxAccessibleSelection(xParent->getAccessibleContext(), + uno::UNO_QUERY); + if (rxAccessibleSelection.is()) + rxAccessibleSelection->selectAccessibleChild(getAccessibleIndexInParent()); + } +} + +sal_Int32 AccessibleSmElement::getForeground() +{ + OExternalLockGuard aGuard(this); + + Color nColor = SmElementsControl::GetTextColor(); + return sal_Int32(nColor); +} + +sal_Int32 AccessibleSmElement::getBackground() +{ + OExternalLockGuard aGuard(this); + + Color nColor = SmElementsControl::GetControlBackground(); + return sal_Int32(nColor); +} + +// XAccessibleAction + +sal_Int32 AccessibleSmElement::getAccessibleActionCount() +{ + // only one action -> "Press" + return m_pSmElementsControl->itemIsSeparator(m_nItemId) ? 0 : 1; +} + +void AccessibleSmElement::testAction(sal_Int32 nIndex) const +{ + if (!m_pSmElementsControl || m_pSmElementsControl->itemIsSeparator(m_nItemId) || (nIndex != 0)) + throw lang::IndexOutOfBoundsException(); +} + +sal_Bool AccessibleSmElement::doAccessibleAction(sal_Int32 nIndex) +{ + OExternalLockGuard aGuard(this); + + testAction(nIndex); + + return m_pSmElementsControl->itemTrigger(m_nItemId); +} + +OUString AccessibleSmElement::getAccessibleActionDescription(sal_Int32 nIndex) +{ + OExternalLockGuard aGuard(this); + + testAction(nIndex); + + return "press"; +} + +uno::Reference<XAccessibleKeyBinding> +AccessibleSmElement::getAccessibleActionKeyBinding(sal_Int32 nIndex) +{ + OContextEntryGuard aGuard(this); + + testAction(nIndex); + + return uno::Reference<XAccessibleKeyBinding>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/AccessibleSmElementsControl.cxx b/starmath/source/AccessibleSmElementsControl.cxx new file mode 100644 index 000000000..29701ce81 --- /dev/null +++ b/starmath/source/AccessibleSmElementsControl.cxx @@ -0,0 +1,375 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <AccessibleSmElementsControl.hxx> +#include <AccessibleSmElement.hxx> +#include <ElementsDockingWindow.hxx> +#include <smmod.hxx> + +#include <comphelper/accessiblewrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/types.hxx> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <o3tl/safeint.hxx> +#include <toolkit/helper/convert.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/vclevent.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using OContextEntryGuard = ::comphelper::OContextEntryGuard; +using OExternalLockGuard = ::comphelper::OExternalLockGuard; + +// AccessibleSmElementsControl + +AccessibleSmElementsControl::AccessibleSmElementsControl(SmElementsControl& rControl) + : m_pControl(&rControl) +{ +} + +AccessibleSmElementsControl::~AccessibleSmElementsControl() {} + +void AccessibleSmElementsControl::UpdateFocus(sal_uInt16 nPos) +{ + const bool bSetFocus = (nPos == SAL_MAX_UINT16); + + // submit events only if the widget has the focus to avoid sending events due to mouse move + if (!m_pControl || (bSetFocus && !m_pControl->HasFocus())) + return; + + if (bSetFocus) + nPos = m_pControl->itemHighlighted() - m_pControl->itemOffset(); + + if (nPos < m_aAccessibleChildren.size()) + { + const auto& rxChild = m_aAccessibleChildren[nPos]; + if (rxChild.is()) + rxChild->SetFocus(bSetFocus); + } +} + +void AccessibleSmElementsControl::ReleaseAllItems() +{ + if (m_aAccessibleChildren.empty()) + return; + + m_aAccessibleChildren.clear(); + + // The original toolbox accessibility code uses individual NAME_CHANGED + // events in a loop. We can't do this, because on each remove event the + // list of known children is rebuild. But since we rely on the child + // count of the SmElementsControl, we'll always have no or all items. + // In the latter case this would automatically recreate all items! + assert(m_pControl && m_pControl->itemCount() == 0); + NotifyAccessibleEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN, uno::Any(), uno::Any()); +} + +void AccessibleSmElementsControl::AddAllItems() +{ + assert(m_pControl); + if (!m_pControl) + return; + + uno::Any aNewName(getAccessibleName()); + NotifyAccessibleEvent(AccessibleEventId::NAME_CHANGED, uno::Any(), aNewName); + + // register the new items + sal_uInt16 nCount = getAccessibleChildCount(); + for (sal_uInt16 i = 0; i < nCount; ++i) + { + uno::Any aNewValue; + aNewValue <<= getAccessibleChild(static_cast<sal_Int32>(i)); + NotifyAccessibleEvent(AccessibleEventId::CHILD, uno::Any(), aNewValue); + } +} + +IMPLEMENT_FORWARD_XINTERFACE2(AccessibleSmElementsControl, comphelper::OAccessibleComponentHelper, + AccessibleSmElementsControl_BASE) + +IMPLEMENT_FORWARD_XTYPEPROVIDER2(AccessibleSmElementsControl, + comphelper::OAccessibleComponentHelper, + AccessibleSmElementsControl_BASE) + +// XAccessible +uno::Reference<XAccessibleContext> AccessibleSmElementsControl::getAccessibleContext() +{ + return this; +} + +// XComponent +void AccessibleSmElementsControl::disposing() +{ + comphelper::OAccessibleComponentHelper::disposing(); + m_aAccessibleChildren.clear(); +} + +void AccessibleSmElementsControl::grabFocus() +{ + SolarMutexGuard aGuard; + if (!m_pControl) + throw uno::RuntimeException(); + + m_pControl->GrabFocus(); +} + +sal_Int32 AccessibleSmElementsControl::getForeground() +{ + SolarMutexGuard aGuard; + + return static_cast<sal_Int32>(SmElementsControl::GetTextColor()); +} + +sal_Int32 AccessibleSmElementsControl::getBackground() +{ + SolarMutexGuard aGuard; + + Color nCol = SmElementsControl::GetControlBackground(); + return static_cast<sal_Int32>(nCol); +} + +// XServiceInfo +OUString AccessibleSmElementsControl::getImplementationName() +{ + return "com.sun.star.comp.toolkit.AccessibleSmElementsControl"; +} + +sal_Bool AccessibleSmElementsControl::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence<OUString> AccessibleSmElementsControl::getSupportedServiceNames() +{ + return { "com.sun.star.accessibility.AccessibleContext", + "com.sun.star.accessibility.AccessibleComponent", + "com.sun.star.accessibility.AccessibleSelection", + "com.sun.star.accessibility.AccessibleSmElementsControl" }; +} + +// XAccessibleContext +sal_Int32 AccessibleSmElementsControl::getAccessibleChildCount() +{ + comphelper::OExternalLockGuard aGuard(this); + sal_Int32 nCount = 0; + if (m_pControl) + { + nCount = m_pControl->itemCount(); + if (m_aAccessibleChildren.size() != sal_uInt16(nCount)) + m_aAccessibleChildren.resize(nCount); + } + return nCount; +} + +uno::Reference<XAccessible> AccessibleSmElementsControl::getAccessibleChild(sal_Int32 c) +{ + comphelper::OExternalLockGuard aGuard(this); + + if (c < 0 || c >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + + rtl::Reference<AccessibleSmElement> xChild = m_aAccessibleChildren[c]; + const sal_uInt16 nItemId = m_pControl->itemOffset() + c; + if (xChild.is() && xChild->itemId() != nItemId) + xChild.clear(); + if (!xChild.is()) + { + sal_uInt16 nHighlightItemId = m_pControl->itemHighlighted(); + AccessibleSmElement* pChild = new AccessibleSmElement(m_pControl, nItemId, c); + if (pChild->itemId() == nHighlightItemId) + pChild->SetFocus(true); + m_aAccessibleChildren[c] = pChild; + xChild = pChild; + } + return xChild.get(); +} + +uno::Reference<XAccessible> AccessibleSmElementsControl::getAccessibleParent() +{ + SolarMutexGuard aGuard; + if (!m_pControl) + throw uno::RuntimeException(); + + return m_pControl->GetDrawingArea()->get_accessible_parent(); +} + +uno::Reference<XAccessible> +AccessibleSmElementsControl::getAccessibleAtPoint(const awt::Point& rPoint) +{ + comphelper::OExternalLockGuard aGuard(this); + + uno::Reference<XAccessible> xAccessible; + if (m_pControl) + { + sal_uInt16 nPos = m_pControl->itemAtPos(VCLPoint(rPoint)); + nPos -= m_pControl->itemOffset(); + if (nPos <= m_aAccessibleChildren.size()) + xAccessible = getAccessibleChild(nPos); + } + return xAccessible; +} + +sal_Int16 AccessibleSmElementsControl::getAccessibleRole() { return AccessibleRole::SCROLL_PANE; } + +OUString AccessibleSmElementsControl::getAccessibleDescription() { return OUString(); } + +OUString AccessibleSmElementsControl::getAccessibleName() +{ + SolarMutexGuard aGuard; + OUString aName; + if (m_pControl) + aName = SmResId(m_pControl->elementSetId().getStr()); + return aName; +} + +// XAccessibleSelection +void AccessibleSmElementsControl::selectAccessibleChild(sal_Int32 nChildIndex) +{ + OExternalLockGuard aGuard(this); + + if ((!m_pControl) || nChildIndex < 0 + || o3tl::make_unsigned(nChildIndex) >= m_aAccessibleChildren.size()) + throw lang::IndexOutOfBoundsException(); + + m_pControl->setItemHighlighted(nChildIndex); +} + +sal_Bool AccessibleSmElementsControl::isAccessibleChildSelected(sal_Int32 nChildIndex) +{ + OExternalLockGuard aGuard(this); + if ((!m_pControl) || nChildIndex < 0 + || o3tl::make_unsigned(nChildIndex) >= m_aAccessibleChildren.size()) + throw lang::IndexOutOfBoundsException(); + + return (m_pControl->itemHighlighted() == nChildIndex); +} + +void AccessibleSmElementsControl::clearAccessibleSelection() +{ + OExternalLockGuard aGuard(this); + if (m_pControl) + m_pControl->setItemHighlighted(SAL_MAX_UINT16); +} + +void AccessibleSmElementsControl::selectAllAccessibleChildren() +{ + // intentionally empty +} + +sal_Int32 AccessibleSmElementsControl::getSelectedAccessibleChildCount() +{ + OExternalLockGuard aGuard(this); + + sal_Int32 nRet = 0; + if (m_pControl + && (m_pControl->itemHighlighted() - m_pControl->itemOffset()) < getAccessibleChildCount()) + nRet = 1; + return nRet; +} + +uno::Reference<XAccessible> +AccessibleSmElementsControl::getSelectedAccessibleChild(sal_Int32 nSelectedChildIndex) +{ + OExternalLockGuard aGuard(this); + if (nSelectedChildIndex != 0 || !m_pControl) + throw lang::IndexOutOfBoundsException(); + return getAccessibleChild(m_pControl->itemHighlighted() - m_pControl->itemOffset()); +} + +void AccessibleSmElementsControl::deselectAccessibleChild(sal_Int32 nChildIndex) +{ + OExternalLockGuard aGuard(this); + if (nChildIndex != 0 || nChildIndex >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + clearAccessibleSelection(); // there can be just one selected child +} + +void AccessibleSmElementsControl::TestControl() +{ + if (!m_pControl) + throw uno::RuntimeException(); +} + +awt::Rectangle AccessibleSmElementsControl::implGetBounds() +{ + SolarMutexGuard aGuard; + TestControl(); + + awt::Rectangle aRet; + + const Point aOutPos; + Size aOutSize(m_pControl->GetOutputSizePixel()); + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + + return aRet; +} + +sal_Bool AccessibleSmElementsControl::containsPoint(const awt::Point& aPoint) +{ + SolarMutexGuard aGuard; + TestControl(); + Size aSz(m_pControl->GetOutputSizePixel()); + return aPoint.X >= 0 && aPoint.Y >= 0 && aPoint.X < aSz.Width() && aPoint.Y < aSz.Height(); +} + +uno::Reference<XAccessibleRelationSet> AccessibleSmElementsControl::getAccessibleRelationSet() +{ + uno::Reference<XAccessibleRelationSet> xRelSet = new utl::AccessibleRelationSetHelper(); + return xRelSet; // empty relation set +} + +uno::Reference<XAccessibleStateSet> AccessibleSmElementsControl::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + ::utl::AccessibleStateSetHelper* pStateSet = new ::utl::AccessibleStateSetHelper; + + uno::Reference<XAccessibleStateSet> xStateSet(pStateSet); + + if (!m_pControl) + pStateSet->AddState(AccessibleStateType::DEFUNC); + else + { + pStateSet->AddState(AccessibleStateType::ENABLED); + pStateSet->AddState(AccessibleStateType::FOCUSABLE); + if (m_pControl->HasFocus()) + pStateSet->AddState(AccessibleStateType::FOCUSED); + if (m_pControl->IsActive()) + pStateSet->AddState(AccessibleStateType::ACTIVE); + if (m_pControl->IsVisible()) + pStateSet->AddState(AccessibleStateType::SHOWING); + if (m_pControl->IsReallyVisible()) + pStateSet->AddState(AccessibleStateType::VISIBLE); + if (COL_TRANSPARENT != SmElementsControl::GetControlBackground()) + pStateSet->AddState(AccessibleStateType::OPAQUE); + } + + return xStateSet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ElementsDockingWindow.cxx b/starmath/source/ElementsDockingWindow.cxx new file mode 100644 index 000000000..c21933fe1 --- /dev/null +++ b/starmath/source/ElementsDockingWindow.cxx @@ -0,0 +1,1230 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <ElementsDockingWindow.hxx> + +#include <starmath.hrc> +#include <strings.hrc> +#include <smmod.hxx> +#include <view.hxx> +#include <visitors.hxx> +#include <document.hxx> +#include <node.hxx> +#include "uiobject.hxx" +#include <strings.hxx> + +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxmodelfactory.hxx> +#include <svl/stritem.hxx> +#include <svtools/colorcfg.hxx> +#include <vcl/event.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <vcl/uitest/logger.hxx> + +SmElement::SmElement(std::unique_ptr<SmNode>&& pNode, const OUString& aText, const OUString& aHelpText) : + mpNode(std::move(pNode)), + maText(aText), + maHelpText(aHelpText) +{} + +SmElement::~SmElement() +{} + +const std::unique_ptr<SmNode>& SmElement::getNode() const { return mpNode; } + +SmElementSeparator::SmElementSeparator() : + SmElement(std::unique_ptr<SmNode>(), OUString(), OUString()) +{} + +const SmElementDescr SmElementsControl::m_aUnaryBinaryOperatorsList[] = +{ + {RID_PLUSX, RID_PLUSX_HELP}, {RID_MINUSX, RID_MINUSX_HELP}, + {RID_PLUSMINUSX, RID_PLUSMINUSX_HELP}, {RID_MINUSPLUSX, RID_MINUSPLUSX_HELP}, + {nullptr, nullptr}, + {RID_XPLUSY, RID_XPLUSY_HELP}, {RID_XMINUSY, RID_XMINUSY_HELP}, + {RID_XCDOTY, RID_XCDOTY_HELP}, {RID_XTIMESY, RID_XTIMESY_HELP}, + {RID_XSYMTIMESY, RID_XSYMTIMESY_HELP}, {RID_XOVERY, RID_XOVERY_HELP}, + {RID_XDIVY, RID_XDIVY_HELP}, {RID_XSYMDIVIDEY, RID_XSYMDIVIDEY_HELP}, + {RID_XOPLUSY, RID_XOPLUSY_HELP}, {RID_XOMINUSY, RID_XOMINUSY_HELP}, + {RID_XODOTY, RID_XODOTY_HELP}, {RID_XOTIMESY, RID_XOTIMESY_HELP}, + {RID_XODIVIDEY, RID_XODIVIDEY_HELP}, {RID_XCIRCY, RID_XCIRCY_HELP}, + {RID_XWIDESLASHY, RID_XWIDESLASHY_HELP}, {RID_XWIDEBSLASHY, RID_XWIDEBSLASHY_HELP}, + {nullptr, nullptr}, + {RID_NEGX, RID_NEGX_HELP}, {RID_XANDY, RID_XANDY_HELP}, {RID_XORY, RID_XORY_HELP}, +}; + +const SmElementDescr SmElementsControl::m_aRelationsList[] = +{ + {RID_XEQY, RID_XEQY_HELP}, {RID_XNEQY, RID_XNEQY_HELP}, {RID_XLTY, RID_XLTY_HELP}, + {RID_XLEY, RID_XLEY_HELP}, {RID_XLESLANTY, RID_XLESLANTY_HELP}, {RID_XGTY, RID_XGTY_HELP}, + {RID_XGEY, RID_XGEY_HELP}, {RID_XGESLANTY, RID_XGESLANTY_HELP}, + {RID_XLLY, RID_XLLY_HELP}, {RID_XGGY, RID_XGGY_HELP}, + {nullptr, nullptr}, + {RID_XAPPROXY, RID_XAPPROXY_HELP}, {RID_XSIMY, RID_XSIMY_HELP}, {RID_XSIMEQY, RID_XSIMEQY_HELP}, + {RID_XEQUIVY, RID_XEQUIVY_HELP}, {RID_XPROPY, RID_XPROPY_HELP}, {RID_XPARALLELY, RID_XPARALLELY_HELP}, + {RID_XORTHOY, RID_XORTHOY_HELP}, {RID_XDIVIDESY, RID_XDIVIDESY_HELP}, {RID_XNDIVIDESY, RID_XNDIVIDESY_HELP}, + {RID_XTOWARDY, RID_XTOWARDY_HELP}, {RID_XTRANSLY, RID_XTRANSLY_HELP}, {RID_XTRANSRY, RID_XTRANSRY_HELP}, + {RID_XDEFY, RID_XDEFY_HELP}, + {nullptr, nullptr}, + {RID_DLARROW, RID_DLARROW_HELP}, {RID_DLRARROW, RID_DLRARROW_HELP}, {RID_DRARROW, RID_DRARROW_HELP}, + {nullptr, nullptr}, + {RID_XPRECEDESY, RID_XPRECEDESY_HELP}, {RID_XSUCCEEDSY, RID_XSUCCEEDSY_HELP}, + {RID_XPRECEDESEQUALY, RID_XPRECEDESEQUALY_HELP}, {RID_XSUCCEEDSEQUALY, RID_XSUCCEEDSEQUALY_HELP}, + {RID_XPRECEDESEQUIVY, RID_XPRECEDESEQUIVY_HELP}, {RID_XSUCCEEDSEQUIVY, RID_XSUCCEEDSEQUIVY_HELP}, + {RID_XNOTPRECEDESY, RID_XNOTPRECEDESY_HELP}, {RID_XNOTSUCCEEDSY, RID_XNOTSUCCEEDSY_HELP}, +}; + +const SmElementDescr SmElementsControl::m_aSetOperationsList[] = +{ + {RID_XINY, RID_XINY_HELP}, {RID_XNOTINY, RID_XNOTINY_HELP}, {RID_XOWNSY, RID_XOWNSY_HELP}, + {nullptr, nullptr}, + {RID_XINTERSECTIONY, RID_XINTERSECTIONY_HELP}, {RID_XUNIONY, RID_XUNIONY_HELP}, + {RID_XSETMINUSY, RID_XSETMINUSY_HELP}, {RID_XSLASHY, RID_XSLASHY_HELP}, + {RID_XSUBSETY, RID_XSUBSETY_HELP}, {RID_XSUBSETEQY, RID_XSUBSETEQY_HELP}, + {RID_XSUPSETY, RID_XSUPSETY_HELP}, {RID_XSUPSETEQY, RID_XSUPSETEQY_HELP}, + {RID_XNSUBSETY, RID_XNSUBSETY_HELP}, {RID_XNSUBSETEQY, RID_XNSUBSETEQY_HELP}, + {RID_XNSUPSETY, RID_XNSUPSETY_HELP}, {RID_XNSUPSETEQY, RID_XNSUPSETEQY_HELP}, + {nullptr, nullptr}, + {RID_EMPTYSET, RID_EMPTYSET_HELP}, {RID_ALEPH, RID_ALEPH_HELP}, {RID_SETN, RID_SETN_HELP}, + {RID_SETZ, RID_SETZ_HELP}, {RID_SETQ, RID_SETQ_HELP}, {RID_SETR, RID_SETR_HELP}, {RID_SETC, RID_SETC_HELP} +}; + +const SmElementDescr SmElementsControl::m_aFunctionsList[] = +{ + {RID_ABSX, RID_ABSX_HELP}, {RID_FACTX, RID_FACTX_HELP}, {RID_SQRTX, RID_SQRTX_HELP}, + {RID_NROOTXY, RID_NROOTXY_HELP}, {RID_RSUPX, RID_RSUPX_HELP}, {RID_EX, RID_EX_HELP}, + {RID_LNX, RID_LNX_HELP}, {RID_EXPX, RID_EXPX_HELP}, {RID_LOGX, RID_LOGX_HELP}, + {nullptr, nullptr}, + {RID_SINX, RID_SINX_HELP}, {RID_COSX, RID_COSX_HELP}, {RID_TANX, RID_TANX_HELP}, {RID_COTX, RID_COTX_HELP}, + {RID_SINHX, RID_SINHX_HELP}, {RID_COSHX, RID_COSHX_HELP}, {RID_TANHX, RID_TANHX_HELP}, + {RID_COTHX, RID_COTHX_HELP}, + {nullptr, nullptr}, + {RID_ARCSINX, RID_ARCSINX_HELP}, {RID_ARCCOSX, RID_ARCCOSX_HELP}, {RID_ARCTANX, RID_ARCTANX_HELP}, + {RID_ARCCOTX, RID_ARCCOTX_HELP}, {RID_ARSINHX, RID_ARSINHX_HELP}, {RID_ARCOSHX, RID_ARCOSHX_HELP}, + {RID_ARTANHX, RID_ARTANHX_HELP}, {RID_ARCOTHX, RID_ARCOTHX_HELP} +}; + +const SmElementDescr SmElementsControl::m_aOperatorsList[] = +{ + {RID_LIMX, RID_LIMX_HELP}, {RID_LIM_FROMX, RID_LIM_FROMX_HELP}, + {RID_LIM_TOX, RID_LIM_TOX_HELP}, {RID_LIM_FROMTOX, RID_LIM_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_LIMINFX, RID_LIMINFX_HELP}, {RID_LIMINF_FROMX, RID_LIMINF_FROMX_HELP}, + {RID_LIMINF_TOX, RID_LIMINF_TOX_HELP}, {RID_LIMINF_FROMTOX, RID_LIMINF_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_LIMSUPX, RID_LIMSUPX_HELP}, {RID_LIMSUP_FROMX, RID_LIMSUP_FROMX_HELP}, + {RID_LIMSUP_TOX, RID_LIMSUP_TOX_HELP}, {RID_LIMSUP_FROMTOX, RID_LIMSUP_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_SUMX, RID_SUMX_HELP}, {RID_SUM_FROMX, RID_SUM_FROMX_HELP}, + {RID_SUM_TOX, RID_SUM_TOX_HELP}, {RID_SUM_FROMTOX, RID_SUM_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_PRODX, RID_PRODX_HELP}, {RID_PROD_FROMX, RID_PROD_FROMX_HELP}, + {RID_PROD_TOX, RID_PROD_TOX_HELP}, {RID_PROD_FROMTOX, RID_PROD_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_COPRODX, RID_COPRODX_HELP}, {RID_COPROD_FROMX, RID_COPROD_FROMX_HELP}, + {RID_COPROD_TOX, RID_COPROD_TOX_HELP}, {RID_COPROD_FROMTOX, RID_COPROD_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_INTX, RID_INTX_HELP}, {RID_INT_FROMX, RID_INT_FROMX_HELP}, + {RID_INT_TOX, RID_INT_TOX_HELP}, {RID_INT_FROMTOX, RID_INT_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_IINTX, RID_IINTX_HELP}, {RID_IINT_FROMX, RID_IINT_FROMX_HELP}, + {RID_IINT_TOX, RID_IINT_TOX_HELP}, {RID_IINT_FROMTOX, RID_IINT_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_IIINTX, RID_IIINTX_HELP}, {RID_IIINT_FROMX, RID_IIINT_FROMX_HELP}, + {RID_IIINT_TOX, RID_IIINT_TOX_HELP}, {RID_IIINT_FROMTOX, RID_IIINT_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_LINTX, RID_LINTX_HELP}, {RID_LINT_FROMX, RID_LINT_FROMX_HELP}, + {RID_LINT_TOX, RID_LINT_TOX_HELP}, {RID_LINT_FROMTOX, RID_LINT_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_LLINTX, RID_LLINTX_HELP}, {RID_LLINT_FROMX, RID_LLINT_FROMX_HELP}, + {RID_LLINT_TOX, RID_LLINT_TOX_HELP}, {RID_LLINT_FROMTOX, RID_LLINT_FROMTOX_HELP}, + {nullptr, nullptr}, + {RID_LLLINTX, RID_LLLINTX_HELP}, {RID_LLLINT_FROMX, RID_LLLINT_FROMX_HELP}, + {RID_LLLINT_TOX, RID_LLLINT_TOX_HELP}, {RID_LLLINT_FROMTOX, RID_LLLINT_FROMTOX_HELP}, +}; + +const SmElementDescr SmElementsControl::m_aAttributesList[] = +{ + {RID_ACUTEX, RID_ACUTEX_HELP}, {RID_GRAVEX, RID_GRAVEX_HELP}, {RID_BREVEX, RID_BREVEX_HELP}, + {RID_CIRCLEX, RID_CIRCLEX_HELP}, {RID_DOTX, RID_DOTX_HELP}, {RID_DDOTX, RID_DDOTX_HELP}, + {RID_DDDOTX, RID_DDDOTX_HELP}, {RID_BARX, RID_BARX_HELP}, {RID_VECX, RID_VECX_HELP}, + {RID_HARPOONX, RID_HARPOONX_HELP}, + {RID_TILDEX, RID_TILDEX_HELP}, {RID_HATX, RID_HATX_HELP}, {RID_CHECKX, RID_CHECKX_HELP}, + {nullptr, nullptr}, + {RID_WIDEVECX, RID_WIDEVECX_HELP}, {RID_WIDEHARPOONX, RID_WIDEHARPOONX_HELP}, + {RID_WIDETILDEX, RID_WIDETILDEX_HELP}, {RID_WIDEHATX, RID_WIDEHATX_HELP}, + {RID_OVERLINEX, RID_OVERLINEX_HELP}, {RID_UNDERLINEX, RID_UNDERLINEX_HELP}, {RID_OVERSTRIKEX, RID_OVERSTRIKEX_HELP}, + {nullptr, nullptr}, + {RID_PHANTOMX, RID_PHANTOMX_HELP}, {RID_BOLDX, RID_BOLDX_HELP}, {RID_ITALX, RID_ITALX_HELP}, + {RID_SIZEXY, RID_SIZEXY_HELP}, {RID_FONTXY, RID_FONTXY_HELP}, + {nullptr, nullptr}, + {RID_COLORX_BLACK, RID_COLORX_BLACK_HELP}, {RID_COLORX_BLUE, RID_COLORX_BLUE_HELP}, + {RID_COLORX_GREEN, RID_COLORX_GREEN_HELP}, {RID_COLORX_RED, RID_COLORX_RED_HELP}, + {RID_COLORX_CYAN, RID_COLORX_CYAN_HELP}, {RID_COLORX_MAGENTA, RID_COLORX_MAGENTA_HELP}, + {RID_COLORX_YELLOW, RID_COLORX_YELLOW_HELP}, {RID_COLORX_GRAY, RID_COLORX_GRAY_HELP}, + {RID_COLORX_LIME, RID_COLORX_LIME_HELP}, {RID_COLORX_MAROON, RID_COLORX_MAROON_HELP}, + {RID_COLORX_NAVY, RID_COLORX_NAVY_HELP}, {RID_COLORX_OLIVE, RID_COLORX_OLIVE_HELP}, + {RID_COLORX_PURPLE, RID_COLORX_PURPLE_HELP}, {RID_COLORX_SILVER, RID_COLORX_SILVER_HELP}, + {RID_COLORX_TEAL, RID_COLORX_TEAL_HELP},{RID_COLORX_RGB, RID_COLORX_RGB_HELP} +}; + +const SmElementDescr SmElementsControl::m_aBracketsList[] = +{ + {RID_LRGROUPX, RID_LRGROUPX_HELP}, + {nullptr, nullptr}, + {RID_LRPARENTX, RID_LRPARENTX_HELP}, {RID_LRBRACKETX, RID_LRBRACKETX_HELP}, + {RID_LRDBRACKETX, RID_LRDBRACKETX_HELP}, {RID_LRBRACEX, RID_LRBRACEX_HELP}, + {RID_LRANGLEX, RID_LRANGLEX_HELP}, {RID_LMRANGLEXY, RID_LMRANGLEXY_HELP}, + {RID_LRCEILX, RID_LRCEILX_HELP}, {RID_LRFLOORX, RID_LRFLOORX_HELP}, + {RID_LRLINEX, RID_LRLINEX_HELP}, {RID_LRDLINEX, RID_LRDLINEX_HELP}, + {nullptr, nullptr}, + {RID_SLRPARENTX, RID_SLRPARENTX_HELP}, {RID_SLRBRACKETX, RID_SLRBRACKETX_HELP}, + {RID_SLRDBRACKETX, RID_SLRDBRACKETX_HELP}, {RID_SLRBRACEX, RID_SLRBRACEX_HELP}, + {RID_SLRANGLEX, RID_SLRANGLEX_HELP}, {RID_SLMRANGLEXY, RID_SLMRANGLEXY_HELP}, + {RID_SLRCEILX, RID_SLRCEILX_HELP}, {RID_SLRFLOORX, RID_SLRFLOORX_HELP}, + {RID_SLRLINEX, RID_SLRLINEX_HELP}, {RID_SLRDLINEX, RID_SLRDLINEX_HELP}, + {RID_XEVALUATEDATY, RID_XEVALUATEDATY_HELP}, + {nullptr, nullptr}, + {RID_XOVERBRACEY, RID_XOVERBRACEY_HELP}, {RID_XUNDERBRACEY, RID_XUNDERBRACEY_HELP}, +}; + +const SmElementDescr SmElementsControl::m_aFormatsList[] = +{ + {RID_RSUPX, RID_RSUPX_HELP}, {RID_RSUBX, RID_RSUBX_HELP}, {RID_LSUPX, RID_LSUPX_HELP}, + {RID_LSUBX, RID_LSUBX_HELP}, {RID_CSUPX, RID_CSUPX_HELP}, {RID_CSUBX, RID_CSUBX_HELP}, + {nullptr, nullptr}, + {RID_NEWLINE, RID_NEWLINE_HELP}, {RID_SBLANK, RID_SBLANK_HELP}, {RID_BLANK, RID_BLANK_HELP}, + {RID_NOSPACE, RID_NOSPACE_HELP}, + {RID_ALIGNLX, RID_ALIGNLX_HELP}, {RID_ALIGNCX, RID_ALIGNCX_HELP}, {RID_ALIGNRX, RID_ALIGNRX_HELP}, + {nullptr, nullptr}, + {RID_BINOMXY, RID_BINOMXY_HELP}, {RID_STACK, RID_STACK_HELP}, + {RID_MATRIX, RID_MATRIX_HELP}, +}; + +const SmElementDescr SmElementsControl::m_aOthersList[] = +{ + {RID_INFINITY, RID_INFINITY_HELP}, {RID_PARTIAL, RID_PARTIAL_HELP}, {RID_NABLA, RID_NABLA_HELP}, + {RID_EXISTS, RID_EXISTS_HELP}, {RID_NOTEXISTS, RID_NOTEXISTS_HELP}, {RID_FORALL, RID_FORALL_HELP}, + {RID_HBAR, RID_HBAR_HELP}, {RID_LAMBDABAR, RID_LAMBDABAR_HELP}, {RID_RE, RID_RE_HELP}, + {RID_IM, RID_IM_HELP}, {RID_WP, RID_WP_HELP}, {RID_LAPLACE, RID_LAPLACE_HELP}, + {nullptr, nullptr}, + {RID_LEFTARROW, RID_LEFTARROW_HELP}, {RID_RIGHTARROW, RID_RIGHTARROW_HELP}, {RID_UPARROW, RID_UPARROW_HELP}, + {RID_DOWNARROW, RID_DOWNARROW_HELP}, + {nullptr, nullptr}, + {RID_DOTSLOW, RID_DOTSLOW_HELP}, {RID_DOTSAXIS, RID_DOTSAXIS_HELP}, {RID_DOTSVERT, RID_DOTSVERT_HELP}, + {RID_DOTSUP, RID_DOTSUP_HELP}, {RID_DOTSDOWN, RID_DOTSDOWN_HELP} +}; + +const SmElementDescr SmElementsControl::m_aExamplesList[] = +{ + {"C=%pi cdot d = 2 cdot %pi cdot r", RID_EXAMPLE_CIRCUMFERENCE_HELP}, + {"E=mc^2", RID_EXAMPLE_MASS_ENERGY_EQUIV_HELP}, + {"a^2 + b^2 = c^2", RID_EXAMPLE_PYTHAGOREAN_THEO_HELP}, + {"f ( x ) = sum from { { i = 0 } } to { infinity } { {f^{(i)}(0)} over {i!} x^i}", RID_EXAMPLE_A_SIMPLE_SERIES_HELP}, + {"f ( x ) = {1} over {%sigma sqrt{2%pi} }func e^-{{(x-%mu)^2} over {2%sigma^2}}", RID_EXAMPLE_GAUSS_DISTRIBUTION_HELP}, +}; + +#define AS_PAIR(a) a, SAL_N_ELEMENTS(a) +const std::tuple<const char*, const SmElementDescr*, size_t> SmElementsControl::m_aCategories[] = +{ + {RID_CATEGORY_UNARY_BINARY_OPERATORS, AS_PAIR(m_aUnaryBinaryOperatorsList)}, + {RID_CATEGORY_RELATIONS, AS_PAIR(m_aRelationsList)}, + {RID_CATEGORY_SET_OPERATIONS, AS_PAIR(m_aSetOperationsList)}, + {RID_CATEGORY_FUNCTIONS, AS_PAIR(m_aFunctionsList)}, + {RID_CATEGORY_OPERATORS, AS_PAIR(m_aOperatorsList)}, + {RID_CATEGORY_ATTRIBUTES, AS_PAIR(m_aAttributesList)}, + {RID_CATEGORY_BRACKETS, AS_PAIR(m_aBracketsList)}, + {RID_CATEGORY_FORMATS, AS_PAIR(m_aFormatsList)}, + {RID_CATEGORY_OTHERS, AS_PAIR(m_aOthersList)}, + {RID_CATEGORY_EXAMPLES, AS_PAIR(m_aExamplesList)}, +}; + +const size_t SmElementsControl::m_aCategoriesSize = SAL_N_ELEMENTS(m_aCategories); + +SmElementsControl::SmElementsControl(std::unique_ptr<weld::ScrolledWindow> xScrolledWindow) + : mpDocShell(new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT)) + , m_nCurrentElement(SAL_MAX_UINT16) + , m_nCurrentRolloverElement(SAL_MAX_UINT16) + , m_nCurrentOffset(1) // Default offset of 1 due to the ScrollBar child + , mbVerticalMode(true) + , mxScroll(std::move(xScrolledWindow)) + , m_bFirstPaintAfterLayout(false) +{ + mxScroll->set_user_managed_scrolling(); + mxScroll->connect_hadjustment_changed( LINK(this, SmElementsControl, ScrollHdl) ); + mxScroll->connect_vadjustment_changed( LINK(this, SmElementsControl, ScrollHdl) ); +} + +SmElementsControl::~SmElementsControl() +{ + mpDocShell->DoClose(); +} + +void SmElementsControl::setVerticalMode(bool bVerticalMode) +{ + if (mbVerticalMode == bVerticalMode) + return; + mbVerticalMode = bVerticalMode; + // turn off scrollbars, LayoutOrPaintContents will enable whichever one + // might be needed + mxScroll->set_vpolicy(VclPolicyType::NEVER); + mxScroll->set_hpolicy(VclPolicyType::NEVER); + LayoutOrPaintContents(GetDrawingArea()->get_ref_device(), false); + Invalidate(); +} + +SmElement* SmElementsControl::current() const +{ + sal_uInt16 nCur = (m_nCurrentRolloverElement != SAL_MAX_UINT16) + ? m_nCurrentRolloverElement + : (HasFocus() ? m_nCurrentElement : SAL_MAX_UINT16); + return (nCur < maElementList.size()) ? maElementList[nCur].get() : nullptr; +} + +void SmElementsControl::setCurrentElement(sal_uInt16 nPos) +{ + if (m_nCurrentElement == nPos) + return; + if (nPos != SAL_MAX_UINT16 && nPos >= maElementList.size()) + return; + if (m_xAccessible.is() && m_nCurrentElement != SAL_MAX_UINT16) + m_xAccessible->ReleaseFocus(m_nCurrentElement); + m_nCurrentElement = nPos; + if (m_xAccessible.is() && m_nCurrentElement != SAL_MAX_UINT16) + m_xAccessible->AcquireFocus(); +} + +Color SmElementsControl::GetTextColor() +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + return rStyleSettings.GetFieldTextColor(); +} + +Color SmElementsControl::GetControlBackground() +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + return rStyleSettings.GetFieldColor(); +} + +/** + * !bDraw => layout only + * + * Layouting is always done without a scrollbar and will show or hide it. + * The first paint (m_bFirstPaintAfterLayout) therefore needs to update a + * visible scrollbar, because the layouting was wrong. + **/ +void SmElementsControl::LayoutOrPaintContents(vcl::RenderContext& rContext, bool bDraw) +{ + rContext.Push(); + + rContext.SetMapMode( MapMode(MapUnit::Map100thMM) ); + rContext.SetDrawMode( DrawModeFlags::Default ); + rContext.SetLayoutMode( ComplexTextLayoutFlags::Default ); + rContext.SetDigitLanguage( LANGUAGE_ENGLISH ); + if (bDraw) + { + rContext.SetBackground(GetControlBackground()); + rContext.SetTextColor(GetTextColor()); + rContext.Erase(); + } + + const sal_Int32 nControlHeight = GetOutputSizePixel().Height(); + const sal_Int32 nControlWidth = GetOutputSizePixel().Width(); + + sal_Int32 boxX = maMaxElementDimensions.Width() + 10; + sal_Int32 boxY = maMaxElementDimensions.Height() + 10; + + sal_Int32 x = mbVerticalMode ? -mxScroll->hadjustment_get_value() : 0; + sal_Int32 y = !mbVerticalMode ? -mxScroll->vadjustment_get_value() : 0; + + sal_Int32 perLine = 0; + + if (mbVerticalMode) + perLine = nControlHeight / boxY; + else + perLine = nControlWidth / boxX; + if (perLine <= 0) + perLine = 1; + + if (mbVerticalMode) + boxY = nControlHeight / perLine; + else + boxX = nControlWidth / perLine; + + const SmElement* pCurrentElement = current(); + for (const std::unique_ptr<SmElement> & i : maElementList) + { + SmElement* element = i.get(); + if (element->isSeparator()) + { + if (mbVerticalMode) + { + x += boxX; + y = 0; + + element->mBoxLocation = Point(x, y); + element->mBoxSize = Size(10, nControlHeight); + + tools::Rectangle aSelectionRectangle(x + 5 - 1, y + 5, + x + 5 + 1, nControlHeight - 5); + + if (bDraw) + rContext.DrawRect(rContext.PixelToLogic(aSelectionRectangle)); + x += 10; + } + else + { + x = 0; + y += boxY; + + element->mBoxLocation = Point(x, y); + element->mBoxSize = Size(nControlWidth, 10); + + tools::Rectangle aSelectionRectangle(x + 5, y + 5 - 1, + nControlWidth - 5, y + 5 + 1); + + if (bDraw) + rContext.DrawRect(rContext.PixelToLogic(aSelectionRectangle)); + y += 10; + } + } + else + { + if (mbVerticalMode) + { + if (y + boxY > nControlHeight) + { + x += boxX; + y = 0; + } + } + else + { + if ( x + boxX > nControlWidth) + { + x = 0; + y += boxY; + } + } + + element->mBoxLocation = Point(x,y); + element->mBoxSize = Size(boxX, boxY); + + if (bDraw) + { + if (pCurrentElement == element) + { + rContext.Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR); + const StyleSettings& rStyleSettings = rContext.GetSettings().GetStyleSettings(); + rContext.SetLineColor(rStyleSettings.GetHighlightColor()); + rContext.SetFillColor(COL_TRANSPARENT); + rContext.DrawRect(rContext.PixelToLogic(tools::Rectangle(x + 1, y + 1, x + boxX - 1, y + boxY - 1))); + rContext.DrawRect(rContext.PixelToLogic(tools::Rectangle(x + 2, y + 2, x + boxX - 2, y + boxY - 2))); + rContext.Pop(); + } + + Size aSizePixel = rContext.LogicToPixel(Size(element->getNode()->GetWidth(), + element->getNode()->GetHeight())); + Point location(x + ((boxX - aSizePixel.Width()) / 2), + y + ((boxY - aSizePixel.Height()) / 2)); + SmDrawingVisitor(rContext, rContext.PixelToLogic(location), element->getNode().get()); + } + + if (mbVerticalMode) + y += boxY; + else + x += boxX; + } + } + + if (bDraw) + { + if (!m_bFirstPaintAfterLayout) + { + rContext.Pop(); + return; + } + m_bFirstPaintAfterLayout = false; + } + else + m_bFirstPaintAfterLayout = true; + + if (mbVerticalMode) + { + sal_Int32 nTotalControlWidth = x + boxX + mxScroll->hadjustment_get_value(); + if (nTotalControlWidth > GetOutputSizePixel().Width()) + { + mxScroll->hadjustment_set_upper(nTotalControlWidth); + mxScroll->hadjustment_set_page_size(nControlWidth); + mxScroll->hadjustment_set_page_increment(nControlWidth); + mxScroll->set_hpolicy(VclPolicyType::ALWAYS); + } + else + { + mxScroll->hadjustment_set_value(0); + mxScroll->set_hpolicy(VclPolicyType::NEVER); + } + } + else + { + sal_Int32 nTotalControlHeight = y + boxY + mxScroll->vadjustment_get_value(); + if (nTotalControlHeight > GetOutputSizePixel().Height()) + { + mxScroll->vadjustment_set_upper(nTotalControlHeight); + mxScroll->vadjustment_set_page_size(nControlHeight); + mxScroll->vadjustment_set_page_increment(nControlHeight); + mxScroll->set_vpolicy(VclPolicyType::ALWAYS); + } + else + { + mxScroll->vadjustment_set_value(0); + mxScroll->set_vpolicy(VclPolicyType::NEVER); + } + } + rContext.Pop(); +} + +void SmElementsControl::Resize() +{ + CustomWidgetController::Resize(); + LayoutOrPaintContents(GetDrawingArea()->get_ref_device(), false); +} + +void SmElementsControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + LayoutOrPaintContents(rRenderContext, true); +} + +OUString SmElementsControl::RequestHelp(tools::Rectangle& rRect) +{ + if (!hasRollover()) + return OUString(); + + const SmElement* pHelpElement = current(); + if (!pHelpElement) + return OUString(); + + rRect = tools::Rectangle(pHelpElement->mBoxLocation, pHelpElement->mBoxSize); + + // get text and display it + return pHelpElement->getHelpText(); +} + +bool SmElementsControl::MouseMove( const MouseEvent& rMouseEvent ) +{ + if (rMouseEvent.IsLeaveWindow()) + { + m_nCurrentRolloverElement = SAL_MAX_UINT16; + Invalidate(); + return false; + } + + if (tools::Rectangle(Point(0, 0), GetOutputSizePixel()).IsInside(rMouseEvent.GetPosPixel())) + { + const SmElement* pPrevElement = current(); + if (pPrevElement) + { + const tools::Rectangle rect(pPrevElement->mBoxLocation, pPrevElement->mBoxSize); + if (rect.IsInside(rMouseEvent.GetPosPixel())) + return true; + } + + const sal_uInt16 nElementCount = maElementList.size(); + for (sal_uInt16 n = 0; n < nElementCount; n++) + { + const SmElement* element = maElementList[n].get(); + if (pPrevElement == element) + continue; + + const tools::Rectangle rect(element->mBoxLocation, element->mBoxSize); + if (rect.IsInside(rMouseEvent.GetPosPixel())) + { + m_nCurrentRolloverElement = n; + Invalidate(); + return true; + } + } + if (pPrevElement && hasRollover()) + Invalidate(); + m_nCurrentRolloverElement = SAL_MAX_UINT16; + return true; + } + + return false; +} + +namespace { + +void collectUIInformation(const OUString& aID) +{ + EventDescription aDescription; + aDescription.aID = aID; + aDescription.aParent = "element_selector"; + aDescription.aAction = "SELECT"; + aDescription.aKeyWord = "ElementUIObject"; + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +bool SmElementsControl::MouseButtonDown(const MouseEvent& rMouseEvent) +{ + GrabFocus(); + + if (rMouseEvent.IsLeft() && tools::Rectangle(Point(0, 0), GetOutputSizePixel()).IsInside(rMouseEvent.GetPosPixel()) && maSelectHdlLink.IsSet()) + { + const SmElement* pPrevElement = hasRollover() ? current() : nullptr; + if (pPrevElement) + { + tools::Rectangle rect(pPrevElement->mBoxLocation, pPrevElement->mBoxSize); + if (rect.IsInside(rMouseEvent.GetPosPixel())) + { + setCurrentElement(m_nCurrentRolloverElement); + maSelectHdlLink.Call(*const_cast<SmElement*>(pPrevElement)); + collectUIInformation(OUString::number(m_nCurrentRolloverElement)); + return true; + } + } + + const sal_uInt16 nElementCount = maElementList.size(); + for (sal_uInt16 n = 0; n < nElementCount; n++) + { + SmElement* element = maElementList[n].get(); + tools::Rectangle rect(element->mBoxLocation, element->mBoxSize); + if (rect.IsInside(rMouseEvent.GetPosPixel())) + { + setCurrentElement(n); + maSelectHdlLink.Call(*element); + collectUIInformation(OUString::number(n)); + return true; + } + } + + return true; + } + return false; +} + +void SmElementsControl::GetFocus() +{ + CustomWidgetController::GetFocus(); + Invalidate(); +} + +void SmElementsControl::LoseFocus() +{ + CustomWidgetController::LoseFocus(); + Invalidate(); +} + +sal_uInt16 SmElementsControl::nextElement(const bool bBackward, const sal_uInt16 nStartPos, const sal_uInt16 nLastElement) +{ + sal_uInt16 nPos = nStartPos; + + while (true) + { + if (bBackward) + { + if (nPos == 0) + break; + nPos--; + } + else + { + if (nPos == nLastElement) + break; + nPos++; + } + + if (nStartPos == nPos) + break; + if (!maElementList[nPos]->isSeparator()) + break; + } + + return nPos; +} + +void SmElementsControl::scrollToElement(const bool bBackward, const SmElement *pCur) +{ + if (mbVerticalMode) + { + auto nScrollPos = mxScroll->hadjustment_get_value(); + nScrollPos += pCur->mBoxLocation.X(); + if (!bBackward) + nScrollPos += pCur->mBoxSize.Width() - GetOutputSizePixel().Width(); + mxScroll->hadjustment_set_value(nScrollPos); + } + else + { + auto nScrollPos = mxScroll->vadjustment_get_value(); + nScrollPos += pCur->mBoxLocation.Y(); + if (!bBackward) + nScrollPos += pCur->mBoxSize.Height() - GetOutputSizePixel().Height(); + mxScroll->vadjustment_set_value(nScrollPos); + } +} + +void SmElementsControl::stepFocus(const bool bBackward) +{ + const sal_uInt16 nStartPos = m_nCurrentElement; + const sal_uInt16 nLastElement = (maElementList.size() ? maElementList.size() - 1 : 0); + assert(nStartPos <= nLastElement); + + sal_uInt16 nPos = nextElement(bBackward, nStartPos, nLastElement); + if (nStartPos != nPos) + { + m_nCurrentRolloverElement = SAL_MAX_UINT16; + setCurrentElement(nPos); + + const tools::Rectangle outputRect(Point(0,0), GetOutputSizePixel()); + const SmElement *pCur = maElementList[nPos].get(); + tools::Rectangle elementRect(pCur->mBoxLocation, pCur->mBoxSize); + if (!outputRect.IsInside(elementRect)) + scrollToElement(bBackward, pCur); + Invalidate(); + } +} + +void SmElementsControl::pageFocus(const bool bBackward) +{ + const sal_uInt16 nStartPos = m_nCurrentElement; + const sal_uInt16 nLastElement = (maElementList.size() ? maElementList.size() - 1 : 0); + assert(nStartPos <= nLastElement); + tools::Rectangle outputRect(Point(0,0), GetOutputSizePixel()); + sal_uInt16 nPrevPos = nStartPos; + sal_uInt16 nPos = nPrevPos; + + bool bMoved = false; + while (true) + { + nPrevPos = nPos; + nPos = nextElement(bBackward, nPrevPos, nLastElement); + if (nPrevPos == nPos) + break; + + m_nCurrentRolloverElement = SAL_MAX_UINT16; + + SmElement *pCur = maElementList[nPos].get(); + tools::Rectangle elementRect(pCur->mBoxLocation, pCur->mBoxSize); + if (!outputRect.IsInside(elementRect)) + { + if (nPrevPos != nStartPos) + { + nPos = nPrevPos; + break; + } + if (bMoved) + break; + pCur = maElementList[nPrevPos].get(); + + elementRect = tools::Rectangle(pCur->mBoxLocation, pCur->mBoxSize); + if (mbVerticalMode) + outputRect.Move(bBackward ? -outputRect.GetWidth() + elementRect.Right() : elementRect.Left(), 0); + else + outputRect.Move(0, bBackward ? -outputRect.GetHeight() + elementRect.Bottom() : elementRect.Top()); + bMoved = true; + } + } + + if (nStartPos != nPos) + { + setCurrentElement(nPos); + if (bMoved) + scrollToElement(bBackward, maElementList[nPos].get()); + Invalidate(); + } +} + +bool SmElementsControl::KeyInput(const KeyEvent& rKEvt) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if (aKeyCode.GetModifier()) + { + return false; + } + + switch(aKeyCode.GetCode()) + { + case KEY_RETURN: + [[fallthrough]]; + case KEY_SPACE: + assert(m_nCurrentElement < maElementList.size()); + assert(maSelectHdlLink.IsSet()); + maSelectHdlLink.Call(*maElementList[m_nCurrentElement]); + collectUIInformation(OUString::number(m_nCurrentElement)); + break; + + case KEY_DOWN: + [[fallthrough]]; + case KEY_RIGHT: + stepFocus(false); + break; + + case KEY_LEFT: + [[fallthrough]]; + case KEY_UP: + stepFocus(true); + break; + + case KEY_HOME: + if (!maElementList.empty()) + { + setCurrentElement(0); + mxScroll->vadjustment_set_value(0); + } + break; + case KEY_END: + if (!maElementList.empty()) + { + setCurrentElement(maElementList.size() - 1); + mxScroll->vadjustment_set_value(mxScroll->vadjustment_get_upper()); + } + break; + + case KEY_PAGEUP: + pageFocus(true); + break; + case KEY_PAGEDOWN: + pageFocus(false); + break; + + default: + return false; + break; + } + return true; +} + +IMPL_LINK_NOARG( SmElementsControl, ScrollHdl, weld::ScrolledWindow&, void ) +{ + Invalidate(); +} + +void SmElementsControl::addElement(SmParser &rParser, const OUString& aElementVisual, const OUString& aElementSource, const OUString& aHelpText) +{ + // SAL_MAX_UINT16 is invalid, zero is the scrollbar + assert(maElementList.size() < SAL_MAX_UINT16 - 2); + auto pNode = rParser.ParseExpression(aElementVisual); + + OutputDevice& rDevice = GetDrawingArea()->get_ref_device(); + rDevice.Push(PushFlags::MAPMODE); + rDevice.SetMapMode( MapMode(MapUnit::Map100thMM) ); + + pNode->Prepare(maFormat, *mpDocShell, 0); + pNode->SetSize(Fraction(10,8)); + pNode->Arrange(rDevice, maFormat); + + Size aSizePixel = rDevice.LogicToPixel(Size(pNode->GetWidth(), pNode->GetHeight()), MapMode(MapUnit::Map100thMM)); + if (aSizePixel.Width() > maMaxElementDimensions.Width()) { + maMaxElementDimensions.setWidth( aSizePixel.Width() ); + } + + if (aSizePixel.Height() > maMaxElementDimensions.Height()) { + maMaxElementDimensions.setHeight( aSizePixel.Height() ); + } + + maElementList.push_back(std::make_unique<SmElement>(std::move(pNode), aElementSource, aHelpText)); + + rDevice.Pop(); +} + +void SmElementsControl::setElementSetId(const char* pSetId) +{ + if (msCurrentSetId == pSetId) + return; + msCurrentSetId = pSetId; + maMaxElementDimensions = Size(); + build(); +} + +void SmElementsControl::addElements(const SmElementDescr aElementsArray[], sal_uInt16 aElementsArraySize) +{ + SmParser aParser; + aParser.SetImportSymbolNames(true); + + for (sal_uInt16 i = 0; i < aElementsArraySize ; i++) + { + const char* pElement = aElementsArray[i].first; + const char* pElementHelp = aElementsArray[i].second; + if (!pElement) { + maElementList.push_back(std::make_unique<SmElementSeparator>()); + } else { + OUString aElement(OUString::createFromAscii(pElement)); + if (aElement == RID_NEWLINE) + addElement(aParser, OUString(u"\u21B5"), aElement, SmResId(pElementHelp)); + else if (aElement == RID_SBLANK) + addElement(aParser, "\"`\"", aElement, SmResId(pElementHelp)); + else if (aElement == RID_BLANK) + addElement(aParser, "\"~\"", aElement, SmResId(pElementHelp)); + else if (aElement == RID_PHANTOMX) + addElement(aParser, "\"" + SmResId(STR_HIDE) +"\"", aElement, SmResId(pElementHelp)); + else if (aElement == RID_BOLDX) + addElement(aParser, "bold B", aElement, SmResId(pElementHelp)); + else if (aElement == RID_ITALX) + addElement(aParser, "ital I", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SIZEXY) + addElement(aParser, "\"" + SmResId(STR_SIZE) + "\"", aElement, SmResId(pElementHelp)); + else if (aElement == RID_FONTXY) + addElement(aParser, "\"" + SmResId(STR_FONT) + "\"", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_BLACK) + addElement(aParser, "color black { \"" + SmResId(STR_BLACK) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_BLUE) + addElement(aParser, "color blue { \"" + SmResId(STR_BLUE) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_GREEN) + addElement(aParser, "color green { \"" + SmResId(STR_GREEN) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_RED) + addElement(aParser, "color red { \"" + SmResId(STR_RED) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_CYAN) + addElement(aParser, "color cyan { \"" + SmResId(STR_CYAN) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_MAGENTA) + addElement(aParser, "color magenta { \"" + SmResId(STR_MAGENTA) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_YELLOW) + addElement(aParser, "color yellow { \"" + SmResId(STR_YELLOW) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_GRAY) + addElement(aParser, "color gray { \"" + SmResId(STR_GRAY) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_LIME) + addElement(aParser, "color lime { \"" + SmResId(STR_LIME) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_MAROON) + addElement(aParser, "color maroon { \"" + SmResId(STR_MAROON) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_NAVY) + addElement(aParser, "color navy { \"" + SmResId(STR_NAVY) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_OLIVE) + addElement(aParser, "color olive { \"" + SmResId(STR_OLIVE) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_PURPLE) + addElement(aParser, "color purple { \"" + SmResId(STR_PURPLE) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_SILVER) + addElement(aParser, "color silver { \"" + SmResId(STR_SILVER) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_TEAL) + addElement(aParser, "color teal { \"" + SmResId(STR_TEAL) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_COLORX_RGB) + addElement(aParser, "color rgb 0 0 0 { \"" + SmResId(STR_RGB) + "\" }", aElement, SmResId(pElementHelp)); + else if (aElement == RID_ALIGNLX) + addElement(aParser, "\"" + SmResId(STR_ALIGN_LEFT) + "\"", aElement, SmResId(pElementHelp)); + else if (aElement == RID_ALIGNCX) + addElement(aParser, "\"" + SmResId(STR_ALIGN_CENTER) + "\"", aElement, SmResId(pElementHelp)); + else if (aElement == RID_ALIGNRX) + addElement(aParser, "\"" + SmResId(STR_ALIGN_RIGHT) + "\"", aElement, SmResId(pElementHelp)); + + else if (aElement == RID_SLRPARENTX) + addElement(aParser, "left ( binom{<?>}{<?>} right ) ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SLRBRACKETX) + addElement(aParser, "left [ binom{<?>}{<?>} right ] ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SLRDBRACKETX) + addElement(aParser, "left ldbracket binom{<?>}{<?>} right rdbracket ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SLRBRACEX) + addElement(aParser, "left lbrace binom{<?>}{<?>} right rbrace ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SLRANGLEX) + addElement(aParser, "left langle binom{<?>}{<?>} right rangle ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SLRCEILX) + addElement(aParser, "left lceil binom{<?>}{<?>} right rceil ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SLRFLOORX) + addElement(aParser, "left lfloor binom{<?>}{<?>} right rfloor ", aElement, SmResId(pElementHelp)); + + else if (aElement == RID_SLRLINEX) + addElement(aParser, "left lline binom{<?>}{<?>} right rline ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SLRDLINEX) + addElement(aParser, "left ldline binom{<?>}{<?>} right rdline ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_SLMRANGLEXY) + addElement(aParser, "left langle binom{<?>}{<?>} mline binom{<?>}{<?>} right rangle ", aElement, SmResId(pElementHelp)); + + else if (aElement == RID_XOVERBRACEY) + addElement(aParser, "{<?><?><?>} overbrace {<?>} ", aElement, SmResId(pElementHelp)); + else if (aElement == RID_XUNDERBRACEY) + addElement(aParser, "{<?><?><?>} underbrace {<?>} ", aElement, SmResId(pElementHelp)); + else + addElement(aParser, aElement, aElement, pElementHelp ? SmResId(pElementHelp) : ""); + } + } +} + +void SmElementsControl::build() +{ + // The order is important! + // 1. Ensure there are no items left, including the default scrollbar! + // 2. Release all the current accessible items. + // This will check for new items after releasing them! + // 3. Set the cursor element + maElementList.clear(); + mxScroll->hadjustment_set_value(0); + mxScroll->vadjustment_set_value(0); + mxScroll->set_hpolicy(VclPolicyType::NEVER); + mxScroll->set_vpolicy(VclPolicyType::NEVER); + + if (m_xAccessible.is()) + m_xAccessible->ReleaseAllItems(); + + setCurrentElement(SAL_MAX_UINT16); + + // The first element is the scrollbar. We can't change its indexInParent + // value, as this is set by being a child of the SmElementsControl. + m_nCurrentOffset = 1; + for (sal_uInt16 n = 0; n < SAL_N_ELEMENTS(m_aCategories); ++n) + { + if (msCurrentSetId == std::get<0>(m_aCategories[n])) + { + addElements(std::get<1>(m_aCategories[n]), std::get<2>(m_aCategories[n])); + break; + } + else + m_nCurrentOffset += std::get<2>(m_aCategories[n]); + } + + m_nCurrentRolloverElement = SAL_MAX_UINT16; + LayoutOrPaintContents(GetDrawingArea()->get_ref_device(), false); + + if (m_xAccessible.is()) + m_xAccessible->AddAllItems(); + + setCurrentElement(0); + Invalidate(); +} + +void SmElementsControl::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + maFormat.SetBaseSize(rDevice.PixelToLogic(Size(0, SmPtsTo100th_mm(12)))); + Size aSize(rDevice.LogicToPixel(Size(10, 100), MapMode(MapUnit::MapAppFont))); + // give it an arbitrary small width request so it can shrink in the sidebar + pDrawingArea->set_size_request(42, aSize.Height()); + SetOutputSizePixel(aSize); +} + +FactoryFunction SmElementsControl::GetUITestFactory() const +{ + return ElementSelectorUIObject::create; +} + +bool SmElementsControl::itemIsSeparator(sal_uInt16 nPos) const +{ + if (nPos < m_nCurrentOffset) + return true; + nPos -= m_nCurrentOffset; + if (nPos >= maElementList.size()) + return true; + return maElementList[nPos]->isSeparator(); +} + +css::uno::Reference<css::accessibility::XAccessible> SmElementsControl::CreateAccessible() +{ + if (!m_xAccessible.is()) + { + m_xAccessible = new AccessibleSmElementsControl(*this); + m_xAccessible->AddAllItems(); + } + return m_xAccessible.get(); +} + +bool SmElementsControl::itemTrigger(sal_uInt16 nPos) +{ + if (nPos < m_nCurrentOffset) + return false; + nPos -= m_nCurrentOffset; + if (nPos >= maElementList.size()) + return false; + + maSelectHdlLink.Call(*maElementList[nPos]); + collectUIInformation(OUString::number(nPos)); + return true; +} + +tools::Rectangle SmElementsControl::itemPosRect(sal_uInt16 nPos) const +{ + if (nPos < m_nCurrentOffset) + return tools::Rectangle(); + nPos -= m_nCurrentOffset; + if (nPos >= maElementList.size()) + return tools::Rectangle(); + + SmElement* pItem = maElementList[nPos].get(); + return tools::Rectangle(pItem->mBoxLocation, pItem->mBoxSize); +} + +bool SmElementsControl::itemIsVisible(sal_uInt16 nPos) const +{ + tools::Rectangle elementRect = itemPosRect(nPos); + if (elementRect.IsEmpty()) + return false; + + tools::Rectangle outputRect(Point(0, 0), GetOutputSizePixel()); + return outputRect.IsInside(elementRect); +} + +sal_uInt16 SmElementsControl::itemCount() const { return maElementList.size(); } + +sal_uInt16 SmElementsControl::itemHighlighted() const { return m_nCurrentElement; } + +void SmElementsControl::setItemHighlighted(sal_uInt16 nPos) +{ + if (m_nCurrentRolloverElement == nPos) + return; + if (nPos != SAL_MAX_UINT16 && nPos >= maElementList.size()) + return; + + if (maElementList[nPos]->isSeparator()) + m_nCurrentRolloverElement = SAL_MAX_UINT16; + else + m_nCurrentRolloverElement = nPos; + Invalidate(); +} + +OUString SmElementsControl::itemName(sal_uInt16 nPos) const +{ + if (nPos < m_nCurrentOffset) + return OUString(); + nPos -= m_nCurrentOffset; + if (nPos >= maElementList.size()) + return OUString(); + + return maElementList[nPos]->getHelpText(); +} + +sal_uInt16 SmElementsControl::itemAtPos(const Point& rPoint) const +{ + sal_uInt16 nElementCount = maElementList.size(); + for (sal_uInt16 n = 0; n < nElementCount; n++) + { + const SmElement* pItem = maElementList[n].get(); + tools::Rectangle elementRect(pItem->mBoxLocation, pItem->mBoxSize); + if (elementRect.IsInside(rPoint)) + return n; + } + return SAL_MAX_UINT16; +} + +SmElementsDockingWindow::SmElementsDockingWindow(SfxBindings* pInputBindings, SfxChildWindow* pChildWindow, vcl::Window* pParent) + : SfxDockingWindow(pInputBindings, pChildWindow, pParent, "DockingElements", + "modules/smath/ui/dockingelements.ui") + , mxElementsControl(new SmElementsControl(m_xBuilder->weld_scrolled_window("scrolledwindow"))) + , mxElementsControlWin(new weld::CustomWeld(*m_xBuilder, "element_selector", *mxElementsControl)) + , mxElementListBox(m_xBuilder->weld_combo_box("listbox")) +{ + // give it an arbitrary small width request so it can shrink in the sidebar + mxElementListBox->set_size_request(42, -1); + + for (size_t i = 0; i < SmElementsControl::categoriesSize(); ++i) + mxElementListBox->append_text(SmResId(std::get<0>(SmElementsControl::categories()[i]))); + + mxElementListBox->connect_changed(LINK(this, SmElementsDockingWindow, ElementSelectedHandle)); + mxElementListBox->set_active_text(SmResId(RID_CATEGORY_UNARY_BINARY_OPERATORS)); + + mxElementsControl->setElementSetId(RID_CATEGORY_UNARY_BINARY_OPERATORS); + mxElementsControl->SetSelectHdl(LINK(this, SmElementsDockingWindow, SelectClickHandler)); +} + +SmElementsDockingWindow::~SmElementsDockingWindow () +{ + disposeOnce(); +} + +void SmElementsDockingWindow::dispose() +{ + mxElementsControlWin.reset(); + mxElementsControl.reset(); + mxElementListBox.reset(); + SfxDockingWindow::dispose(); +} + +void SmElementsDockingWindow::ToggleFloatingMode() +{ + SfxDockingWindow::ToggleFloatingMode(); + + if (GetFloatingWindow()) + GetFloatingWindow()->SetMinOutputSizePixel( Size(100, 100) ); + + Invalidate(); +} + +void SmElementsDockingWindow::EndDocking( const tools::Rectangle& rReactangle, bool bFloatMode) +{ + SfxDockingWindow::EndDocking(rReactangle, bFloatMode); + bool bVertical = ( GetAlignment() == SfxChildAlignment::TOP || GetAlignment() == SfxChildAlignment::BOTTOM ); + mxElementsControl->setVerticalMode(bVertical); +} + +IMPL_LINK(SmElementsDockingWindow, SelectClickHandler, SmElement&, rElement, void) +{ + SmViewShell* pViewSh = GetView(); + + if (pViewSh) + { + std::unique_ptr<SfxStringItem> pInsertCommand = std::make_unique<SfxStringItem>(SID_INSERTCOMMANDTEXT, rElement.getText()); + pViewSh->GetViewFrame()->GetDispatcher()->ExecuteList( + SID_INSERTCOMMANDTEXT, SfxCallMode::RECORD, + { pInsertCommand.get() }); + } +} + +IMPL_LINK( SmElementsDockingWindow, ElementSelectedHandle, weld::ComboBox&, rList, void) +{ + for (size_t i = 0; i < SmElementsControl::categoriesSize(); ++i) + { + const char *pCurrentCategory = std::get<0>(SmElementsControl::categories()[i]); + OUString aCurrentCategoryString = SmResId(pCurrentCategory); + if (aCurrentCategoryString == rList.get_active_text()) + { + mxElementsControl->setElementSetId(pCurrentCategory); + return; + } + } +} + +SmViewShell* SmElementsDockingWindow::GetView() +{ + SfxViewShell* pView = GetBindings().GetDispatcher()->GetFrame()->GetViewShell(); + return dynamic_cast<SmViewShell*>( pView); +} + +void SmElementsDockingWindow::Resize() +{ + bool bVertical = ( GetAlignment() == SfxChildAlignment::TOP || GetAlignment() == SfxChildAlignment::BOTTOM ); + mxElementsControl->setVerticalMode(bVertical); + + SfxDockingWindow::Resize(); + Invalidate(); +} + +SFX_IMPL_DOCKINGWINDOW_WITHID(SmElementsDockingWindowWrapper, SID_ELEMENTSDOCKINGWINDOW); + +SmElementsDockingWindowWrapper::SmElementsDockingWindowWrapper( + vcl::Window *pParentWindow, sal_uInt16 nId, + SfxBindings *pBindings, SfxChildWinInfo *pInfo) : + SfxChildWindow(pParentWindow, nId) +{ + VclPtrInstance<SmElementsDockingWindow> pDialog(pBindings, this, pParentWindow); + SetWindow(pDialog); + pDialog->setDeferredProperties(); + pDialog->SetPosSizePixel(Point(0, 0), Size(300, 0)); + pDialog->Show(); + + SetAlignment(SfxChildAlignment::LEFT); + + pDialog->Initialize( pInfo ); +} + +SmElementsDockingWindowWrapper::~SmElementsDockingWindowWrapper() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/accessibility.cxx b/starmath/source/accessibility.cxx new file mode 100644 index 000000000..854f09443 --- /dev/null +++ b/starmath/source/accessibility.cxx @@ -0,0 +1,1791 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <memory> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/accessibility/AccessibleEventObject.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <unotools/accessiblerelationsethelper.hxx> + +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <unotools/accessiblestatesethelper.hxx> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/diagnose.h> +#include <svx/AccessibleTextHelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <vcl/unohelp2.hxx> +#include <vcl/settings.hxx> + +#include <tools/gen.hxx> +#include <svl/itemset.hxx> + +#include <editeng/editobj.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editview.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unoedhlp.hxx> + + +#include "accessibility.hxx" +#include <document.hxx> +#include <view.hxx> +#include <strings.hrc> +#include <smmod.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::accessibility; + + +static awt::Rectangle lcl_GetBounds( vcl::Window const *pWin ) +{ + // !! see VCLXAccessibleComponent::implGetBounds() + + //! the coordinates returned are relative to the parent window ! + //! Thus the top-left point may be different from (0, 0) ! + + awt::Rectangle aBounds; + if (pWin) + { + tools::Rectangle aRect = pWin->GetWindowExtentsRelative( nullptr ); + aBounds.X = aRect.Left(); + aBounds.Y = aRect.Top(); + aBounds.Width = aRect.GetWidth(); + aBounds.Height = aRect.GetHeight(); + vcl::Window* pParent = pWin->GetAccessibleParentWindow(); + if (pParent) + { + tools::Rectangle aParentRect = pParent->GetWindowExtentsRelative( nullptr ); + awt::Point aParentScreenLoc( aParentRect.Left(), aParentRect.Top() ); + aBounds.X -= aParentScreenLoc.X; + aBounds.Y -= aParentScreenLoc.Y; + } + } + return aBounds; +} + +static awt::Point lcl_GetLocationOnScreen( vcl::Window const *pWin ) +{ + // !! see VCLXAccessibleComponent::getLocationOnScreen() + + awt::Point aPos; + if (pWin) + { + tools::Rectangle aRect = pWin->GetWindowExtentsRelative( nullptr ); + aPos.X = aRect.Left(); + aPos.Y = aRect.Top(); + } + return aPos; +} + + +SmGraphicAccessible::SmGraphicAccessible( SmGraphicWindow *pGraphicWin ) : + aAccName (SmResId(RID_DOCUMENTSTR)), + nClientId (0), + pWin (pGraphicWin) +{ + OSL_ENSURE( pWin, "SmGraphicAccessible: window missing" ); +} + +SmGraphicAccessible::~SmGraphicAccessible() +{ +} + + +SmDocShell * SmGraphicAccessible::GetDoc_Impl() +{ + SmViewShell *pView = pWin ? pWin->GetView() : nullptr; + return pView ? pView->GetDoc() : nullptr; +} + +OUString SmGraphicAccessible::GetAccessibleText_Impl() +{ + OUString aTxt; + SmDocShell *pDoc = GetDoc_Impl(); + if (pDoc) + aTxt = pDoc->GetAccessibleText(); + return aTxt; +} + +void SmGraphicAccessible::ClearWin() +{ + pWin = nullptr; // implicitly results in AccessibleStateType::DEFUNC set + + if ( nClientId ) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( nClientId, *this ); + nClientId = 0; + } +} + +void SmGraphicAccessible::LaunchEvent( + const sal_Int16 nAccessibleEventId, + const uno::Any &rOldVal, + const uno::Any &rNewVal) +{ + AccessibleEventObject aEvt; + aEvt.Source = static_cast<XAccessible *>(this); + aEvt.EventId = nAccessibleEventId; + aEvt.OldValue = rOldVal; + aEvt.NewValue = rNewVal ; + + // pass event on to event-listener's + if (nClientId) + comphelper::AccessibleEventNotifier::addEvent( nClientId, aEvt ); +} + +uno::Reference< XAccessibleContext > SAL_CALL SmGraphicAccessible::getAccessibleContext() +{ + return this; +} + +sal_Bool SAL_CALL SmGraphicAccessible::containsPoint( const awt::Point& aPoint ) +{ + //! the arguments coordinates are relative to the current window ! + //! Thus the top-left point is (0, 0) + + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + Size aSz( pWin->GetSizePixel() ); + return aPoint.X >= 0 && aPoint.Y >= 0 && + aPoint.X < aSz.Width() && aPoint.Y < aSz.Height(); +} + +uno::Reference< XAccessible > SAL_CALL SmGraphicAccessible::getAccessibleAtPoint( + const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + XAccessible *pRes = nullptr; + if (containsPoint( aPoint )) + pRes = this; + return pRes; +} + +awt::Rectangle SAL_CALL SmGraphicAccessible::getBounds() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + OSL_ENSURE(pWin->GetParent()->GetAccessible() == getAccessibleParent(), + "mismatch of window parent and accessible parent" ); + return lcl_GetBounds( pWin ); +} + +awt::Point SAL_CALL SmGraphicAccessible::getLocation() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + OSL_ENSURE(pWin->GetParent()->GetAccessible() == getAccessibleParent(), + "mismatch of window parent and accessible parent" ); + awt::Rectangle aRect( lcl_GetBounds( pWin ) ); + return awt::Point( aRect.X, aRect.Y ); +} + +awt::Point SAL_CALL SmGraphicAccessible::getLocationOnScreen() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + OSL_ENSURE(pWin->GetParent()->GetAccessible() == getAccessibleParent(), + "mismatch of window parent and accessible parent" ); + return lcl_GetLocationOnScreen( pWin ); +} + +awt::Size SAL_CALL SmGraphicAccessible::getSize() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + OSL_ENSURE(pWin->GetParent()->GetAccessible() == getAccessibleParent(), + "mismatch of window parent and accessible parent" ); + + Size aSz( pWin->GetSizePixel() ); +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + awt::Rectangle aRect( lcl_GetBounds( pWin ) ); + Size aSz2( aRect.Width, aRect.Height ); + assert(aSz == aSz2 && "mismatch in width" ); +#endif + return awt::Size( aSz.Width(), aSz.Height() ); +} + +void SAL_CALL SmGraphicAccessible::grabFocus() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + pWin->GrabFocus(); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getForeground() +{ + SolarMutexGuard aGuard; + + if (!pWin) + throw RuntimeException(); + return static_cast<sal_Int32>(pWin->GetTextColor()); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getBackground() +{ + SolarMutexGuard aGuard; + + if (!pWin) + throw RuntimeException(); + Wallpaper aWall( pWin->GetDisplayBackground() ); + Color nCol; + if (aWall.IsBitmap() || aWall.IsGradient()) + nCol = pWin->GetSettings().GetStyleSettings().GetWindowColor(); + else + nCol = aWall.GetColor(); + return static_cast<sal_Int32>(nCol); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getAccessibleChildCount() +{ + return 0; +} + +Reference< XAccessible > SAL_CALL SmGraphicAccessible::getAccessibleChild( + sal_Int32 /*i*/ ) +{ + throw IndexOutOfBoundsException(); // there is no child... +} + +Reference< XAccessible > SAL_CALL SmGraphicAccessible::getAccessibleParent() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + vcl::Window *pAccParent = pWin->GetAccessibleParentWindow(); + OSL_ENSURE( pAccParent, "accessible parent missing" ); + return pAccParent ? pAccParent->GetAccessible() : Reference< XAccessible >(); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + sal_Int32 nIdx = -1; + vcl::Window *pAccParent = pWin ? pWin->GetAccessibleParentWindow() : nullptr; + if (pAccParent) + { + sal_uInt16 nCnt = pAccParent->GetAccessibleChildWindowCount(); + for (sal_uInt16 i = 0; i < nCnt && nIdx == -1; ++i) + if (pAccParent->GetAccessibleChildWindow( i ) == pWin) + nIdx = i; + } + return nIdx; +} + +sal_Int16 SAL_CALL SmGraphicAccessible::getAccessibleRole() +{ + return AccessibleRole::DOCUMENT; +} + +OUString SAL_CALL SmGraphicAccessible::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + SmDocShell *pDoc = GetDoc_Impl(); + return pDoc ? pDoc->GetText() : OUString(); +} + +OUString SAL_CALL SmGraphicAccessible::getAccessibleName() +{ + SolarMutexGuard aGuard; + return aAccName; +} + +Reference< XAccessibleRelationSet > SAL_CALL SmGraphicAccessible::getAccessibleRelationSet() +{ + SolarMutexGuard aGuard; + Reference< XAccessibleRelationSet > xRelSet = new utl::AccessibleRelationSetHelper(); + return xRelSet; // empty relation set +} + +Reference< XAccessibleStateSet > SAL_CALL SmGraphicAccessible::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + ::utl::AccessibleStateSetHelper *pStateSet = + new ::utl::AccessibleStateSetHelper; + + Reference<XAccessibleStateSet> xStateSet( pStateSet ); + + if (!pWin) + pStateSet->AddState( AccessibleStateType::DEFUNC ); + else + { + pStateSet->AddState( AccessibleStateType::ENABLED ); + pStateSet->AddState( AccessibleStateType::FOCUSABLE ); + if (pWin->HasFocus()) + pStateSet->AddState( AccessibleStateType::FOCUSED ); + if (pWin->IsActive()) + pStateSet->AddState( AccessibleStateType::ACTIVE ); + if (pWin->IsVisible()) + pStateSet->AddState( AccessibleStateType::SHOWING ); + if (pWin->IsReallyVisible()) + pStateSet->AddState( AccessibleStateType::VISIBLE ); + if (COL_TRANSPARENT != pWin->GetBackground().GetColor()) + pStateSet->AddState( AccessibleStateType::OPAQUE ); + } + + return xStateSet; +} + +Locale SAL_CALL SmGraphicAccessible::getLocale() +{ + SolarMutexGuard aGuard; + // should be the document language... + // We use the language of the localized symbol names here. + return Application::GetSettings().GetUILanguageTag().getLocale(); +} + + +void SAL_CALL SmGraphicAccessible::addAccessibleEventListener( + const Reference< XAccessibleEventListener >& xListener ) +{ + if (xListener.is()) + { + SolarMutexGuard aGuard; + if (pWin) + { + if (!nClientId) + nClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( nClientId, xListener ); + } + } +} + +void SAL_CALL SmGraphicAccessible::removeAccessibleEventListener( + const Reference< XAccessibleEventListener >& xListener ) +{ + if (!(xListener.is() && nClientId)) + return; + + SolarMutexGuard aGuard; + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( nClientId, xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + comphelper::AccessibleEventNotifier::revokeClient( nClientId ); + nClientId = 0; + } +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getCaretPosition() +{ + return 0; +} + +sal_Bool SAL_CALL SmGraphicAccessible::setCaretPosition( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + if (nIndex >= aTxt.getLength()) + throw IndexOutOfBoundsException(); + return false; +} + +sal_Unicode SAL_CALL SmGraphicAccessible::getCharacter( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + if (nIndex >= aTxt.getLength()) + throw IndexOutOfBoundsException(); + return aTxt[nIndex]; +} + +Sequence< beans::PropertyValue > SAL_CALL SmGraphicAccessible::getCharacterAttributes( + sal_Int32 nIndex, + const uno::Sequence< OUString > & /*rRequestedAttributes*/ ) +{ + SolarMutexGuard aGuard; + sal_Int32 nLen = GetAccessibleText_Impl().getLength(); + if (!(0 <= nIndex && nIndex < nLen)) + throw IndexOutOfBoundsException(); + return Sequence< beans::PropertyValue >(); +} + +awt::Rectangle SAL_CALL SmGraphicAccessible::getCharacterBounds( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + awt::Rectangle aRes; + + if (!pWin) + throw RuntimeException(); + + // get accessible text + SmViewShell *pView = pWin->GetView(); + SmDocShell *pDoc = pView ? pView->GetDoc() : nullptr; + if (!pDoc) + throw RuntimeException(); + OUString aTxt( GetAccessibleText_Impl() ); + if (!(0 <= nIndex && nIndex <= aTxt.getLength())) // aTxt.getLength() is valid + throw IndexOutOfBoundsException(); + + // find a reasonable rectangle for position aTxt.getLength(). + bool bWasBehindText = (nIndex == aTxt.getLength()); + if (bWasBehindText && nIndex) + --nIndex; + + const SmNode *pTree = pDoc->GetFormulaTree(); + const SmNode *pNode = pTree->FindNodeWithAccessibleIndex( nIndex ); + //! pNode may be 0 if the index belongs to a char that was inserted + //! only for the accessible text! + if (pNode) + { + sal_Int32 nAccIndex = pNode->GetAccessibleIndex(); + OSL_ENSURE( nAccIndex >= 0, "invalid accessible index" ); + OSL_ENSURE( nIndex >= nAccIndex, "index out of range" ); + + OUStringBuffer aBuf; + pNode->GetAccessibleText(aBuf); + OUString aNodeText = aBuf.makeStringAndClear(); + sal_Int32 nNodeIndex = nIndex - nAccIndex; + if (0 <= nNodeIndex && nNodeIndex < aNodeText.getLength()) + { + // get appropriate rectangle + Point aOffset(pNode->GetTopLeft() - pTree->GetTopLeft()); + Point aTLPos (pWin->GetFormulaDrawPos() + aOffset); + Size aSize (pNode->GetSize()); + + std::unique_ptr<long[]> pXAry(new long[ aNodeText.getLength() ]); + pWin->SetFont( pNode->GetFont() ); + pWin->GetTextArray( aNodeText, pXAry.get(), 0, aNodeText.getLength() ); + aTLPos.AdjustX(nNodeIndex > 0 ? pXAry[nNodeIndex - 1] : 0 ); + aSize.setWidth( nNodeIndex > 0 ? pXAry[nNodeIndex] - pXAry[nNodeIndex - 1] : pXAry[nNodeIndex] ); + pXAry.reset(); + + aTLPos = pWin->LogicToPixel( aTLPos ); + aSize = pWin->LogicToPixel( aSize ); + aRes.X = aTLPos.X(); + aRes.Y = aTLPos.Y(); + aRes.Width = aSize.Width(); + aRes.Height = aSize.Height(); + } + } + + // take rectangle from last character and move it to the right + if (bWasBehindText) + aRes.X += aRes.Width; + + return aRes; +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getCharacterCount() +{ + SolarMutexGuard aGuard; + return GetAccessibleText_Impl().getLength(); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getIndexAtPoint( const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + + sal_Int32 nRes = -1; + if (pWin) + { + const SmNode *pTree = pWin->GetView()->GetDoc()->GetFormulaTree(); + // can be NULL! e.g. if one clicks within the window already during loading of the + // document (before the parser even started) + if (!pTree) + return nRes; + + // get position relative to formula draw position + Point aPos( aPoint.X, aPoint.Y ); + aPos = pWin->PixelToLogic( aPos ); + aPos -= pWin->GetFormulaDrawPos(); + + // if it was inside the formula then get the appropriate node + const SmNode *pNode = nullptr; + if (pTree->OrientedDist(aPos) <= 0) + pNode = pTree->FindRectClosestTo(aPos); + + if (pNode) + { + // get appropriate rectangle + Point aOffset( pNode->GetTopLeft() - pTree->GetTopLeft() ); + Point aTLPos ( aOffset ); + Size aSize( pNode->GetSize() ); + + tools::Rectangle aRect( aTLPos, aSize ); + if (aRect.IsInside( aPos )) + { + OSL_ENSURE( pNode->IsVisible(), "node is not a leaf" ); + OUStringBuffer aBuf; + pNode->GetAccessibleText(aBuf); + OUString aTxt = aBuf.makeStringAndClear(); + OSL_ENSURE( !aTxt.isEmpty(), "no accessible text available" ); + + long nNodeX = pNode->GetLeft(); + + std::unique_ptr<long[]> pXAry(new long[ aTxt.getLength() ]); + pWin->SetFont( pNode->GetFont() ); + pWin->GetTextArray( aTxt, pXAry.get(), 0, aTxt.getLength() ); + for (sal_Int32 i = 0; i < aTxt.getLength() && nRes == -1; ++i) + { + if (pXAry[i] + nNodeX > aPos.X()) + nRes = i; + } + pXAry.reset(); + OSL_ENSURE( nRes >= 0 && nRes < aTxt.getLength(), "index out of range" ); + OSL_ENSURE( pNode->GetAccessibleIndex() >= 0, + "invalid accessible index" ); + + nRes = pNode->GetAccessibleIndex() + nRes; + } + } + } + return nRes; +} + +OUString SAL_CALL SmGraphicAccessible::getSelectedText() +{ + return OUString(); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getSelectionStart() +{ + return -1; +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getSelectionEnd() +{ + return -1; +} + +sal_Bool SAL_CALL SmGraphicAccessible::setSelection( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + sal_Int32 nLen = GetAccessibleText_Impl().getLength(); + if (!(0 <= nStartIndex && nStartIndex < nLen) || + !(0 <= nEndIndex && nEndIndex < nLen)) + throw IndexOutOfBoundsException(); + return false; +} + +OUString SAL_CALL SmGraphicAccessible::getText() +{ + SolarMutexGuard aGuard; + return GetAccessibleText_Impl(); +} + +OUString SAL_CALL SmGraphicAccessible::getTextRange( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) +{ + //!! nEndIndex may be the string length per definition of the interface !! + //!! text should be copied exclusive that end index though. And arguments + //!! may be switched. + + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + sal_Int32 nStart = std::min(nStartIndex, nEndIndex); + sal_Int32 nEnd = std::max(nStartIndex, nEndIndex); + if ((nStart > aTxt.getLength()) || + (nEnd > aTxt.getLength())) + throw IndexOutOfBoundsException(); + return aTxt.copy( nStart, nEnd - nStart ); +} + +css::accessibility::TextSegment SAL_CALL SmGraphicAccessible::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + //!! nIndex is allowed to be the string length + if (nIndex > aTxt.getLength()) + throw IndexOutOfBoundsException(); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + if ( (AccessibleTextType::CHARACTER == aTextType) && (nIndex < aTxt.getLength()) ) + { + aResult.SegmentText = aTxt.copy(nIndex, 1); + aResult.SegmentStart = nIndex; + aResult.SegmentEnd = nIndex+1; + } + return aResult; +} + +css::accessibility::TextSegment SAL_CALL SmGraphicAccessible::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + //!! nIndex is allowed to be the string length + if (nIndex > aTxt.getLength()) + throw IndexOutOfBoundsException(); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + if ( (AccessibleTextType::CHARACTER == aTextType) && nIndex ) + { + aResult.SegmentText = aTxt.copy(nIndex-1, 1); + aResult.SegmentStart = nIndex-1; + aResult.SegmentEnd = nIndex; + } + return aResult; +} + +css::accessibility::TextSegment SAL_CALL SmGraphicAccessible::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + //!! nIndex is allowed to be the string length + if (nIndex > aTxt.getLength()) + throw IndexOutOfBoundsException(); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + nIndex++; // text *behind* + if ( (AccessibleTextType::CHARACTER == aTextType) && (nIndex < aTxt.getLength()) ) + { + aResult.SegmentText = aTxt.copy(nIndex, 1); + aResult.SegmentStart = nIndex; + aResult.SegmentEnd = nIndex+1; + } + return aResult; +} + +sal_Bool SAL_CALL SmGraphicAccessible::copyText( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + bool bReturn = false; + + if (!pWin) + throw RuntimeException(); + + Reference< datatransfer::clipboard::XClipboard > xClipboard = pWin->GetClipboard(); + if ( xClipboard.is() ) + { + OUString sText( getTextRange(nStartIndex, nEndIndex) ); + + vcl::unohelper::TextDataObject* pDataObj = new vcl::unohelper::TextDataObject( sText ); + SolarMutexReleaser aReleaser; + xClipboard->setContents( pDataObj, nullptr ); + + Reference< datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( xClipboard, uno::UNO_QUERY ); + if( xFlushableClipboard.is() ) + xFlushableClipboard->flushClipboard(); + + bReturn = true; + } + + + return bReturn; +} + +sal_Bool SAL_CALL SmGraphicAccessible::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ) +{ + return false; +} + +OUString SAL_CALL SmGraphicAccessible::getImplementationName() +{ + return "SmGraphicAccessible"; +} + +sal_Bool SAL_CALL SmGraphicAccessible::supportsService( + const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SAL_CALL SmGraphicAccessible::getSupportedServiceNames() +{ + return { + "css::accessibility::Accessible", + "css::accessibility::AccessibleComponent", + "css::accessibility::AccessibleContext", + "css::accessibility::AccessibleText" + }; +} + + +SmEditSource::SmEditSource( SmEditAccessible &rAcc ) : + aViewFwd (rAcc), + aTextFwd (rAcc, *this), + aEditViewFwd(rAcc), + rEditAcc (rAcc) +{ +} + +SmEditSource::SmEditSource( const SmEditSource &rSrc ) : + SvxEditSource(), + aViewFwd (rSrc.rEditAcc), + aTextFwd (rSrc.rEditAcc, *this), + aEditViewFwd(rSrc.rEditAcc), + rEditAcc (rSrc.rEditAcc) +{ +} + +SmEditSource::~SmEditSource() +{ +} + +std::unique_ptr<SvxEditSource> SmEditSource::Clone() const +{ + return std::unique_ptr<SvxEditSource>(new SmEditSource( *this )); +} + +SvxTextForwarder* SmEditSource::GetTextForwarder() +{ + return &aTextFwd; +} + +SvxViewForwarder* SmEditSource::GetViewForwarder() +{ + return &aViewFwd; +} + +SvxEditViewForwarder* SmEditSource::GetEditViewForwarder( bool /*bCreate*/ ) +{ + return &aEditViewFwd; +} + +void SmEditSource::UpdateData() +{ + // would possibly only by needed if the XText interface is implemented + // and its text needs to be updated. +} + +SfxBroadcaster & SmEditSource::GetBroadcaster() const +{ + return const_cast<SmEditSource*>(this)->aBroadCaster; +} + +SmViewForwarder::SmViewForwarder( SmEditAccessible &rAcc ) : + rEditAcc(rAcc) +{ +} + +SmViewForwarder::~SmViewForwarder() +{ +} + +bool SmViewForwarder::IsValid() const +{ + return rEditAcc.GetEditView() != nullptr; +} + +Point SmViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + EditView *pEditView = rEditAcc.GetEditView(); + OutputDevice* pOutDev = pEditView ? pEditView->GetWindow() : nullptr; + + if( pOutDev ) + { + MapMode aMapMode(pOutDev->GetMapMode()); + Point aPoint( OutputDevice::LogicToLogic( rPoint, rMapMode, + MapMode(aMapMode.GetMapUnit())) ); + aMapMode.SetOrigin(Point()); + return pOutDev->LogicToPixel( aPoint, aMapMode ); + } + + return Point(); +} + +Point SmViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + EditView *pEditView = rEditAcc.GetEditView(); + OutputDevice* pOutDev = pEditView ? pEditView->GetWindow() : nullptr; + + if( pOutDev ) + { + MapMode aMapMode(pOutDev->GetMapMode()); + aMapMode.SetOrigin(Point()); + Point aPoint( pOutDev->PixelToLogic( rPoint, aMapMode ) ); + return OutputDevice::LogicToLogic( aPoint, + MapMode(aMapMode.GetMapUnit()), + rMapMode ); + } + + return Point(); +} + + +SmTextForwarder::SmTextForwarder( SmEditAccessible& rAcc, SmEditSource & rSource) : + rEditAcc ( rAcc ), + rEditSource (rSource) +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->SetNotifyHdl( LINK(this, SmTextForwarder, NotifyHdl) ); +} + +SmTextForwarder::~SmTextForwarder() +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->SetNotifyHdl( Link<EENotify&,void>() ); +} + +IMPL_LINK(SmTextForwarder, NotifyHdl, EENotify&, rNotify, void) +{ + ::std::unique_ptr< SfxHint > aHint = SvxEditSourceHelper::EENotification2Hint( &rNotify ); + if (aHint) + rEditSource.GetBroadcaster().Broadcast(*aHint); +} + +sal_Int32 SmTextForwarder::GetParagraphCount() const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetParagraphCount() : 0; +} + +sal_Int32 SmTextForwarder::GetTextLen( sal_Int32 nParagraph ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetTextLen( nParagraph ) : 0; +} + +OUString SmTextForwarder::GetText( const ESelection& rSel ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + OUString aRet; + if (pEditEngine) + aRet = pEditEngine->GetText( rSel ); + return convertLineEnd(aRet, GetSystemLineEnd()); +} + +SfxItemSet SmTextForwarder::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + assert(pEditEngine && "EditEngine missing"); + if( rSel.nStartPara == rSel.nEndPara ) + { + GetAttribsFlags nFlags = GetAttribsFlags::NONE; + switch( nOnlyHardAttrib ) + { + case EditEngineAttribs::All: + nFlags = GetAttribsFlags::ALL; + break; + case EditEngineAttribs::OnlyHard: + nFlags = GetAttribsFlags::CHARATTRIBS; + break; + default: + SAL_WARN("starmath", "unknown flags for SmTextForwarder::GetAttribs"); + } + + return pEditEngine->GetAttribs( rSel.nStartPara, rSel.nStartPos, rSel.nEndPos, nFlags ); + } + else + { + return pEditEngine->GetAttribs( rSel, nOnlyHardAttrib ); + } +} + +SfxItemSet SmTextForwarder::GetParaAttribs( sal_Int32 nPara ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + assert(pEditEngine && "EditEngine missing"); + + SfxItemSet aSet( pEditEngine->GetParaAttribs( nPara ) ); + + sal_uInt16 nWhich = EE_PARA_START; + while( nWhich <= EE_PARA_END ) + { + if( aSet.GetItemState( nWhich ) != SfxItemState::SET ) + { + if( pEditEngine->HasParaAttrib( nPara, nWhich ) ) + aSet.Put( pEditEngine->GetParaAttrib( nPara, nWhich ) ); + } + nWhich++; + } + + return aSet; +} + +void SmTextForwarder::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->SetParaAttribs( nPara, rSet ); +} + +SfxItemPool* SmTextForwarder::GetPool() const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetEmptyItemSet().GetPool() : nullptr; +} + +void SmTextForwarder::RemoveAttribs( const ESelection& rSelection ) +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->RemoveAttribs( rSelection, false/*bRemoveParaAttribs*/, 0 ); +} + +void SmTextForwarder::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->GetPortions( nPara, rList ); +} + +void SmTextForwarder::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->QuickInsertText( rText, rSel ); +} + +void SmTextForwarder::QuickInsertLineBreak( const ESelection& rSel ) +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->QuickInsertLineBreak( rSel ); +} + +void SmTextForwarder::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->QuickInsertField( rFld, rSel ); +} + +void SmTextForwarder::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->QuickSetAttribs( rSet, rSel ); +} + +bool SmTextForwarder::IsValid() const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + // cannot reliably query EditEngine state + // while in the middle of an update + return pEditEngine && pEditEngine->GetUpdateMode(); +} + +OUString SmTextForwarder::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor ) +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->CalcFieldValue(rField, nPara, nPos, rpTxtColor, rpFldColor) : OUString(); +} + +void SmTextForwarder::FieldClicked(const SvxFieldItem&) +{ +} + +static SfxItemState GetSvxEditEngineItemState( EditEngine const & rEditEngine, const ESelection& rSel, sal_uInt16 nWhich ) +{ + std::vector<EECharAttrib> aAttribs; + + const SfxPoolItem* pLastItem = nullptr; + + SfxItemState eState = SfxItemState::DEFAULT; + + // check all paragraphs inside the selection + for( sal_Int32 nPara = rSel.nStartPara; nPara <= rSel.nEndPara; nPara++ ) + { + SfxItemState eParaState = SfxItemState::DEFAULT; + + // calculate start and endpos for this paragraph + sal_Int32 nPos = 0; + if( rSel.nStartPara == nPara ) + nPos = rSel.nStartPos; + + sal_Int32 nEndPos = rSel.nEndPos; + if( rSel.nEndPara != nPara ) + nEndPos = rEditEngine.GetTextLen( nPara ); + + + // get list of char attribs + rEditEngine.GetCharAttribs( nPara, aAttribs ); + + bool bEmpty = true; // we found no item inside the selection of this paragraph + bool bGaps = false; // we found items but there are gaps between them + sal_Int32 nLastEnd = nPos; + + const SfxPoolItem* pParaItem = nullptr; + + for(const auto& rAttrib : aAttribs) + { + OSL_ENSURE( rAttrib.pAttr, "GetCharAttribs gives corrupt data" ); + + const bool bEmptyPortion = (rAttrib.nStart == rAttrib.nEnd); + if( (!bEmptyPortion && (rAttrib.nStart >= nEndPos)) || (bEmptyPortion && (rAttrib.nStart > nEndPos)) ) + break; // break if we are already behind our selection + + if( (!bEmptyPortion && (rAttrib.nEnd <= nPos)) || (bEmptyPortion && (rAttrib.nEnd < nPos)) ) + continue; // or if the attribute ends before our selection + + if( rAttrib.pAttr->Which() != nWhich ) + continue; // skip if is not the searched item + + // if we already found an item + if( pParaItem ) + { + // ... and its different to this one than the state is don't care + if( *pParaItem != *(rAttrib.pAttr) ) + return SfxItemState::DONTCARE; + } + else + { + pParaItem = rAttrib.pAttr; + } + + if( bEmpty ) + bEmpty = false; + + if( !bGaps && rAttrib.nStart > nLastEnd ) + bGaps = true; + + nLastEnd = rAttrib.nEnd; + } + + if( !bEmpty && !bGaps && nLastEnd < ( nEndPos - 1 ) ) + bGaps = true; + if( bEmpty ) + eParaState = SfxItemState::DEFAULT; + else if( bGaps ) + eParaState = SfxItemState::DONTCARE; + else + eParaState = SfxItemState::SET; + + // if we already found an item check if we found the same + if( pLastItem ) + { + if( (pParaItem == nullptr) || (*pLastItem != *pParaItem) ) + return SfxItemState::DONTCARE; + } + else + { + pLastItem = pParaItem; + eState = eParaState; + } + } + + return eState; +} + +SfxItemState SmTextForwarder::GetItemState( const ESelection& rSel, sal_uInt16 nWhich ) const +{ + SfxItemState nState = SfxItemState::DISABLED; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + nState = GetSvxEditEngineItemState( *pEditEngine, rSel, nWhich ); + return nState; +} + +SfxItemState SmTextForwarder::GetItemState( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + SfxItemState nState = SfxItemState::DISABLED; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + { + const SfxItemSet& rSet = pEditEngine->GetParaAttribs( nPara ); + nState = rSet.GetItemState( nWhich ); + } + return nState; +} + +LanguageType SmTextForwarder::GetLanguage( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetLanguage(nPara, nIndex) : LANGUAGE_NONE; +} + +sal_Int32 SmTextForwarder::GetFieldCount( sal_Int32 nPara ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetFieldCount(nPara) : 0; +} + +EFieldInfo SmTextForwarder::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetFieldInfo( nPara, nField ) : EFieldInfo(); +} + +EBulletInfo SmTextForwarder::GetBulletInfo( sal_Int32 /*nPara*/ ) const +{ + return EBulletInfo(); +} + +tools::Rectangle SmTextForwarder::GetCharBounds( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + tools::Rectangle aRect(0,0,0,0); + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + + if (pEditEngine) + { + // Handle virtual position one-past-the end of the string + if( nIndex >= pEditEngine->GetTextLen(nPara) ) + { + if( nIndex ) + aRect = pEditEngine->GetCharacterBounds( EPosition(nPara, nIndex-1) ); + + aRect.Move( aRect.Right() - aRect.Left(), 0 ); + aRect.SetSize( Size(1, pEditEngine->GetTextHeight()) ); + } + else + { + aRect = pEditEngine->GetCharacterBounds( EPosition(nPara, nIndex) ); + } + } + return aRect; +} + +tools::Rectangle SmTextForwarder::GetParaBounds( sal_Int32 nPara ) const +{ + tools::Rectangle aRect(0,0,0,0); + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + + if (pEditEngine) + { + const Point aPnt = pEditEngine->GetDocPosTopLeft( nPara ); + const sal_uLong nWidth = pEditEngine->CalcTextWidth(); + const sal_uLong nHeight = pEditEngine->GetTextHeight( nPara ); + aRect = tools::Rectangle( aPnt.X(), aPnt.Y(), aPnt.X() + nWidth, aPnt.Y() + nHeight ); + } + + return aRect; +} + +MapMode SmTextForwarder::GetMapMode() const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetRefMapMode() : MapMode( MapUnit::Map100thMM ); +} + +OutputDevice* SmTextForwarder::GetRefDevice() const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetRefDevice() : nullptr; +} + +bool SmTextForwarder::GetIndexAtPoint( const Point& rPos, sal_Int32& nPara, sal_Int32& nIndex ) const +{ + bool bRes = false; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + { + EPosition aDocPos = pEditEngine->FindDocPosition( rPos ); + nPara = aDocPos.nPara; + nIndex = aDocPos.nIndex; + bRes = true; + } + return bRes; +} + +bool SmTextForwarder::GetWordIndices( sal_Int32 nPara, sal_Int32 nIndex, sal_Int32& nStart, sal_Int32& nEnd ) const +{ + bool bRes = false; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + { + ESelection aRes = pEditEngine->GetWord( ESelection(nPara, nIndex, nPara, nIndex), css::i18n::WordType::DICTIONARY_WORD ); + + if( aRes.nStartPara == nPara && + aRes.nStartPara == aRes.nEndPara ) + { + nStart = aRes.nStartPos; + nEnd = aRes.nEndPos; + + bRes = true; + } + } + + return bRes; +} + +bool SmTextForwarder::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nPara, sal_Int32 nIndex, bool bInCell ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (!pEditEngine) + return false; + SvxEditSourceHelper::GetAttributeRun( nStartIndex, nEndIndex, *pEditEngine, nPara, nIndex, bInCell ); + return true; +} + +sal_Int32 SmTextForwarder::GetLineCount( sal_Int32 nPara ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetLineCount(nPara) : 0; +} + +sal_Int32 SmTextForwarder::GetLineLen( sal_Int32 nPara, sal_Int32 nLine ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetLineLen(nPara, nLine) : 0; +} + +void SmTextForwarder::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nPara, sal_Int32 nLine ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + pEditEngine->GetLineBoundaries(rStart, rEnd, nPara, nLine); + else + rStart = rEnd = 0; +} + +sal_Int32 SmTextForwarder::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + return pEditEngine ? pEditEngine->GetLineNumberAtIndex(nPara, nIndex) : 0; +} + +bool SmTextForwarder::QuickFormatDoc( bool /*bFull*/ ) +{ + bool bRes = false; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + { + pEditEngine->QuickFormatDoc(); + bRes = true; + } + return bRes; +} + +sal_Int16 SmTextForwarder::GetDepth( sal_Int32 /*nPara*/ ) const +{ + // math has no outliner... + return -1; +} + +bool SmTextForwarder::SetDepth( sal_Int32 /*nPara*/, sal_Int16 nNewDepth ) +{ + // math has no outliner... + return -1 == nNewDepth; // is it the value from 'GetDepth' ? +} + +bool SmTextForwarder::Delete( const ESelection& rSelection ) +{ + bool bRes = false; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + { + pEditEngine->QuickDelete( rSelection ); + pEditEngine->QuickFormatDoc(); + bRes = true; + } + return bRes; +} + +bool SmTextForwarder::InsertText( const OUString& rStr, const ESelection& rSelection ) +{ + bool bRes = false; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + { + pEditEngine->QuickInsertText( rStr, rSelection ); + pEditEngine->QuickFormatDoc(); + bRes = true; + } + return bRes; +} + +const SfxItemSet* SmTextForwarder::GetEmptyItemSetPtr() +{ + const SfxItemSet *pItemSet = nullptr; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + { + pItemSet = &pEditEngine->GetEmptyItemSet(); + } + return pItemSet; +} + +void SmTextForwarder::AppendParagraph() +{ + // append an empty paragraph + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine) + { + sal_Int32 nParaCount = pEditEngine->GetParagraphCount(); + pEditEngine->InsertParagraph( nParaCount, OUString() ); + } +} + +sal_Int32 SmTextForwarder::AppendTextPortion( sal_Int32 nPara, const OUString &rText, const SfxItemSet &rSet ) +{ + sal_uInt16 nRes = 0; + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine && nPara < pEditEngine->GetParagraphCount()) + { + // append text + ESelection aSel( nPara, pEditEngine->GetTextLen( nPara ) ); + pEditEngine->QuickInsertText( rText, aSel ); + + // set attributes for new appended text + nRes = aSel.nEndPos = pEditEngine->GetTextLen( nPara ); + pEditEngine->QuickSetAttribs( rSet, aSel ); + } + return nRes; +} + +void SmTextForwarder::CopyText(const SvxTextForwarder& rSource) +{ + + const SmTextForwarder* pSourceForwarder = dynamic_cast< const SmTextForwarder* >( &rSource ); + if( !pSourceForwarder ) + return; + EditEngine* pSourceEditEngine = pSourceForwarder->rEditAcc.GetEditEngine(); + EditEngine *pEditEngine = rEditAcc.GetEditEngine(); + if (pEditEngine && pSourceEditEngine ) + { + std::unique_ptr<EditTextObject> pNewTextObject = pSourceEditEngine->CreateTextObject(); + pEditEngine->SetText( *pNewTextObject ); + } +} + + +SmEditViewForwarder::SmEditViewForwarder( SmEditAccessible& rAcc ) : + rEditAcc( rAcc ) +{ +} + +SmEditViewForwarder::~SmEditViewForwarder() +{ +} + +bool SmEditViewForwarder::IsValid() const +{ + return rEditAcc.GetEditView() != nullptr; +} + + +Point SmEditViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + EditView *pEditView = rEditAcc.GetEditView(); + OutputDevice* pOutDev = pEditView ? pEditView->GetWindow() : nullptr; + + if( pOutDev ) + { + MapMode aMapMode(pOutDev->GetMapMode()); + Point aPoint( OutputDevice::LogicToLogic( rPoint, rMapMode, + MapMode(aMapMode.GetMapUnit()))); + aMapMode.SetOrigin(Point()); + return pOutDev->LogicToPixel( aPoint, aMapMode ); + } + + return Point(); +} + +Point SmEditViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + EditView *pEditView = rEditAcc.GetEditView(); + OutputDevice* pOutDev = pEditView ? pEditView->GetWindow() : nullptr; + + if( pOutDev ) + { + MapMode aMapMode(pOutDev->GetMapMode()); + aMapMode.SetOrigin(Point()); + Point aPoint( pOutDev->PixelToLogic( rPoint, aMapMode ) ); + return OutputDevice::LogicToLogic( aPoint, + MapMode(aMapMode.GetMapUnit()), + rMapMode ); + } + + return Point(); +} + +bool SmEditViewForwarder::GetSelection( ESelection& rSelection ) const +{ + bool bRes = false; + EditView *pEditView = rEditAcc.GetEditView(); + if (pEditView) + { + rSelection = pEditView->GetSelection(); + bRes = true; + } + return bRes; +} + +bool SmEditViewForwarder::SetSelection( const ESelection& rSelection ) +{ + bool bRes = false; + EditView *pEditView = rEditAcc.GetEditView(); + if (pEditView) + { + pEditView->SetSelection( rSelection ); + bRes = true; + } + return bRes; +} + +bool SmEditViewForwarder::Copy() +{ + bool bRes = false; + EditView *pEditView = rEditAcc.GetEditView(); + if (pEditView) + { + pEditView->Copy(); + bRes = true; + } + return bRes; +} + +bool SmEditViewForwarder::Cut() +{ + bool bRes = false; + EditView *pEditView = rEditAcc.GetEditView(); + if (pEditView) + { + pEditView->Cut(); + bRes = true; + } + return bRes; +} + +bool SmEditViewForwarder::Paste() +{ + bool bRes = false; + EditView *pEditView = rEditAcc.GetEditView(); + if (pEditView) + { + pEditView->Paste(); + bRes = true; + } + return bRes; +} + + +SmEditAccessible::SmEditAccessible( SmEditWindow *pEditWin ) : + aAccName (SmResId(STR_CMDBOXWINDOW)), + pTextHelper (), + pWin (pEditWin) +{ + OSL_ENSURE( pWin, "SmEditAccessible: window missing" ); +} + +SmEditAccessible::~SmEditAccessible() +{ +} + +::accessibility::AccessibleTextHelper *SmEditAccessible::GetTextHelper() +{ + return pTextHelper.get(); +} + +void SmEditAccessible::Init() +{ + OSL_ENSURE( pWin, "SmEditAccessible: window missing" ); + if (pWin) + { + EditEngine *pEditEngine = pWin->GetEditEngine(); + EditView *pEditView = pWin->GetEditView(); + if (pEditEngine && pEditView) + { + assert(!pTextHelper); + pTextHelper.reset(new ::accessibility::AccessibleTextHelper( std::make_unique<SmEditSource>( *this ) )); + pTextHelper->SetEventSource( this ); + } + } +} + +void SmEditAccessible::ClearWin() +{ + // remove handler before current object gets destroyed + // (avoid handler being called for already dead object) + EditEngine *pEditEngine = GetEditEngine(); + if (pEditEngine) + pEditEngine->SetNotifyHdl( Link<EENotify&,void>() ); + + pWin = nullptr; // implicitly results in AccessibleStateType::DEFUNC set + + //! make TextHelper implicitly release C++ references to some core objects + pTextHelper->SetEditSource( ::std::unique_ptr<SvxEditSource>() ); + //! make TextHelper release references + //! (e.g. the one set by the 'SetEventSource' call) + pTextHelper->Dispose(); + pTextHelper.reset(); +} + +// XAccessible +uno::Reference< XAccessibleContext > SAL_CALL SmEditAccessible::getAccessibleContext( ) +{ + return this; +} + +// XAccessibleComponent +sal_Bool SAL_CALL SmEditAccessible::containsPoint( const awt::Point& aPoint ) +{ + //! the arguments coordinates are relative to the current window ! + //! Thus the top left-point is (0, 0) + + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + Size aSz( pWin->GetSizePixel() ); + return aPoint.X >= 0 && aPoint.Y >= 0 && + aPoint.X < aSz.Width() && aPoint.Y < aSz.Height(); +} + +uno::Reference< XAccessible > SAL_CALL SmEditAccessible::getAccessibleAtPoint( const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + if (!pTextHelper) + throw RuntimeException(); + return pTextHelper->GetAt( aPoint ); +} + +awt::Rectangle SAL_CALL SmEditAccessible::getBounds( ) +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + OSL_ENSURE(pWin->GetParent()->GetAccessible() == getAccessibleParent(), + "mismatch of window parent and accessible parent" ); + return lcl_GetBounds( pWin ); +} + +awt::Point SAL_CALL SmEditAccessible::getLocation( ) +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + OSL_ENSURE(pWin->GetParent()->GetAccessible() == getAccessibleParent(), + "mismatch of window parent and accessible parent" ); + awt::Rectangle aRect( lcl_GetBounds( pWin ) ); + return awt::Point( aRect.X, aRect.Y ); +} + +awt::Point SAL_CALL SmEditAccessible::getLocationOnScreen( ) +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + OSL_ENSURE(pWin->GetParent()->GetAccessible() == getAccessibleParent(), + "mismatch of window parent and accessible parent" ); + return lcl_GetLocationOnScreen( pWin ); +} + +awt::Size SAL_CALL SmEditAccessible::getSize( ) +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + OSL_ENSURE(pWin->GetParent()->GetAccessible() == getAccessibleParent(), + "mismatch of window parent and accessible parent" ); + + Size aSz( pWin->GetSizePixel() ); +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + awt::Rectangle aRect( lcl_GetBounds( pWin ) ); + Size aSz2( aRect.Width, aRect.Height ); + assert(aSz == aSz2 && "mismatch in width"); +#endif + return awt::Size( aSz.Width(), aSz.Height() ); +} + +void SAL_CALL SmEditAccessible::grabFocus( ) +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + pWin->GrabFocus(); +} + +sal_Int32 SAL_CALL SmEditAccessible::getForeground() +{ + SolarMutexGuard aGuard; + + if (!pWin) + throw RuntimeException(); + return static_cast<sal_Int32>(pWin->GetTextColor()); +} + +sal_Int32 SAL_CALL SmEditAccessible::getBackground() +{ + SolarMutexGuard aGuard; + + if (!pWin) + throw RuntimeException(); + Wallpaper aWall( pWin->GetDisplayBackground() ); + Color nCol; + if (aWall.IsBitmap() || aWall.IsGradient()) + nCol = pWin->GetSettings().GetStyleSettings().GetWindowColor(); + else + nCol = aWall.GetColor(); + return static_cast<sal_Int32>(nCol); +} + +// XAccessibleContext +sal_Int32 SAL_CALL SmEditAccessible::getAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + if (!pTextHelper) + return 0; + return pTextHelper->GetChildCount(); +} + +uno::Reference< XAccessible > SAL_CALL SmEditAccessible::getAccessibleChild( sal_Int32 i ) +{ + SolarMutexGuard aGuard; + if (!pTextHelper) + throw RuntimeException(); + return pTextHelper->GetChild( i ); +} + +uno::Reference< XAccessible > SAL_CALL SmEditAccessible::getAccessibleParent( ) +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + vcl::Window *pAccParent = pWin->GetAccessibleParentWindow(); + OSL_ENSURE( pAccParent, "accessible parent missing" ); + return pAccParent ? pAccParent->GetAccessible() : Reference< XAccessible >(); +} + +sal_Int32 SAL_CALL SmEditAccessible::getAccessibleIndexInParent( ) +{ + SolarMutexGuard aGuard; + sal_Int32 nIdx = -1; + vcl::Window *pAccParent = pWin ? pWin->GetAccessibleParentWindow() : nullptr; + if (pAccParent) + { + sal_uInt16 nCnt = pAccParent->GetAccessibleChildWindowCount(); + for (sal_uInt16 i = 0; i < nCnt && nIdx == -1; ++i) + if (pAccParent->GetAccessibleChildWindow( i ) == pWin) + nIdx = i; + } + return nIdx; +} + +sal_Int16 SAL_CALL SmEditAccessible::getAccessibleRole( ) +{ + return AccessibleRole::TEXT_FRAME; +} + +OUString SAL_CALL SmEditAccessible::getAccessibleDescription( ) +{ + return OUString(); // empty as agreed with product-management +} + +OUString SAL_CALL SmEditAccessible::getAccessibleName( ) +{ + SolarMutexGuard aGuard; + // same name as displayed by the window when not docked + return aAccName; +} + +uno::Reference< XAccessibleRelationSet > SAL_CALL SmEditAccessible::getAccessibleRelationSet( ) +{ + Reference< XAccessibleRelationSet > xRelSet = new utl::AccessibleRelationSetHelper(); + return xRelSet; // empty relation set +} + +uno::Reference< XAccessibleStateSet > SAL_CALL SmEditAccessible::getAccessibleStateSet( ) +{ + SolarMutexGuard aGuard; + ::utl::AccessibleStateSetHelper *pStateSet = + new ::utl::AccessibleStateSetHelper; + + Reference<XAccessibleStateSet> xStateSet( pStateSet ); + + if (!pWin || !pTextHelper) + pStateSet->AddState( AccessibleStateType::DEFUNC ); + else + { + pStateSet->AddState( AccessibleStateType::MULTI_LINE ); + pStateSet->AddState( AccessibleStateType::ENABLED ); + pStateSet->AddState( AccessibleStateType::EDITABLE ); + pStateSet->AddState( AccessibleStateType::FOCUSABLE ); + if (pWin->HasFocus()) + pStateSet->AddState( AccessibleStateType::FOCUSED ); + if (pWin->IsActive()) + pStateSet->AddState( AccessibleStateType::ACTIVE ); + if (pWin->IsVisible()) + pStateSet->AddState( AccessibleStateType::SHOWING ); + if (pWin->IsReallyVisible()) + pStateSet->AddState( AccessibleStateType::VISIBLE ); + if (COL_TRANSPARENT != pWin->GetBackground().GetColor()) + pStateSet->AddState( AccessibleStateType::OPAQUE ); + } + + return xStateSet; +} + +Locale SAL_CALL SmEditAccessible::getLocale( ) +{ + SolarMutexGuard aGuard; + // should be the document language... + // We use the language of the localized symbol names here. + return Application::GetSettings().GetUILanguageTag().getLocale(); +} + + +// XAccessibleEventBroadcaster +void SAL_CALL SmEditAccessible::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (pTextHelper) // not disposing (about to destroy view shell) + pTextHelper->AddEventListener( xListener ); +} + +void SAL_CALL SmEditAccessible::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (pTextHelper) // not disposing (about to destroy view shell) + pTextHelper->RemoveEventListener( xListener ); +} + +OUString SAL_CALL SmEditAccessible::getImplementationName() +{ + return "SmEditAccessible"; +} + +sal_Bool SAL_CALL SmEditAccessible::supportsService( + const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SAL_CALL SmEditAccessible::getSupportedServiceNames() +{ + return { + "css::accessibility::Accessible", + "css::accessibility::AccessibleComponent", + "css::accessibility::AccessibleContext" + }; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/accessibility.hxx b/starmath/source/accessibility.hxx new file mode 100644 index 000000000..8cccdc916 --- /dev/null +++ b/starmath/source/accessibility.hxx @@ -0,0 +1,362 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_ACCESSIBILITY_HXX +#define INCLUDED_STARMATH_SOURCE_ACCESSIBILITY_HXX + +#include <com/sun/star/accessibility/AccessibleScrollType.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/Reference.h> +#include <cppuhelper/implbase.hxx> +#include <svl/SfxBroadcaster.hxx> + +#include <editeng/editeng.hxx> +#include <editeng/unoedsrc.hxx> +#include <edit.hxx> +#include <view.hxx> +#include <memory> + +class SmDocShell; + +namespace accessibility { class AccessibleTextHelper; } + +// classes and helper-classes used for accessibility in the graphic-window + + +typedef +cppu::WeakImplHelper + < + css::lang::XServiceInfo, + css::accessibility::XAccessible, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleText, + css::accessibility::XAccessibleEventBroadcaster + > +SmGraphicAccessibleBaseClass; + +class SmGraphicAccessible final : + public SmGraphicAccessibleBaseClass +{ + OUString aAccName; + /// client id in the AccessibleEventNotifier queue + sal_uInt32 nClientId; + + VclPtr<SmGraphicWindow> pWin; + + SmGraphicAccessible( const SmGraphicAccessible & ) = delete; + SmGraphicAccessible & operator = ( const SmGraphicAccessible & ) = delete; + + SmDocShell * GetDoc_Impl(); + OUString GetAccessibleText_Impl(); + +public: + explicit SmGraphicAccessible( SmGraphicWindow *pGraphicWin ); + virtual ~SmGraphicAccessible() override; + + void ClearWin(); // to be called when view is destroyed + void LaunchEvent( + const sal_Int16 nAccessibleEventId, + const css::uno::Any &rOldVal, + const css::uno::Any &rNewVal); + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleText + virtual sal_Int32 SAL_CALL getCaretPosition( ) override; + virtual sal_Bool SAL_CALL setCaretPosition ( sal_Int32 nIndex ) override; + virtual sal_Unicode SAL_CALL getCharacter( sal_Int32 nIndex ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) override; + virtual css::awt::Rectangle SAL_CALL getCharacterBounds( sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getCharacterCount( ) override; + virtual sal_Int32 SAL_CALL getIndexAtPoint( const css::awt::Point& aPoint ) override; + virtual OUString SAL_CALL getSelectedText( ) override; + virtual sal_Int32 SAL_CALL getSelectionStart( ) override; + virtual sal_Int32 SAL_CALL getSelectionEnd( ) override; + virtual sal_Bool SAL_CALL setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual OUString SAL_CALL getText( ) override; + virtual OUString SAL_CALL getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual sal_Bool SAL_CALL copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL scrollSubstringTo( sal_Int32 nStartIndex, sal_Int32 nEndIndex, css::accessibility::AccessibleScrollType aScrollType) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; + + +// classes and helper-classes used for accessibility in the command-window + + +class SmEditAccessible; +class SmEditSource; +class EditView; +class SvxFieldItem; + + +class SmViewForwarder : + public SvxViewForwarder +{ + SmEditAccessible & rEditAcc; + + SmViewForwarder( const SmViewForwarder & ) = delete; + SmViewForwarder & operator = ( const SmViewForwarder & ) = delete; + +public: + explicit SmViewForwarder( SmEditAccessible &rAcc ); + virtual ~SmViewForwarder() override; + + virtual bool IsValid() const override; + virtual Point LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const override; + virtual Point PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const override; +}; + + +class SmTextForwarder : /* analog to SvxEditEngineForwarder */ + public SvxTextForwarder +{ + SmEditAccessible & rEditAcc; + SmEditSource & rEditSource; + + DECL_LINK( NotifyHdl, EENotify&, void ); + + SmTextForwarder( const SmTextForwarder & ) = delete; + SmTextForwarder & operator = ( const SmTextForwarder & ) = delete; + +public: + SmTextForwarder( SmEditAccessible& rAcc, SmEditSource & rSource ); + virtual ~SmTextForwarder() override; + + virtual sal_Int32 GetParagraphCount() const override; + virtual sal_Int32 GetTextLen( sal_Int32 nParagraph ) const override; + virtual OUString GetText( const ESelection& rSel ) const override; + virtual SfxItemSet GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib = EditEngineAttribs::All ) const override; + virtual SfxItemSet GetParaAttribs( sal_Int32 nPara ) const override; + virtual void SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) override; + virtual void RemoveAttribs( const ESelection& rSelection ) override; + virtual void GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) const override; + + virtual SfxItemState GetItemState( const ESelection& rSel, sal_uInt16 nWhich ) const override; + virtual SfxItemState GetItemState( sal_Int32 nPara, sal_uInt16 nWhich ) const override; + + virtual void QuickInsertText( const OUString& rText, const ESelection& rSel ) override; + virtual void QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) override; + virtual void QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) override; + virtual void QuickInsertLineBreak( const ESelection& rSel ) override; + + virtual SfxItemPool* GetPool() const override; + + virtual OUString CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor ) override; + virtual void FieldClicked(const SvxFieldItem&) override; + virtual bool IsValid() const override; + + virtual LanguageType GetLanguage( sal_Int32, sal_Int32 ) const override; + virtual sal_Int32 GetFieldCount( sal_Int32 nPara ) const override; + virtual EFieldInfo GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const override; + virtual EBulletInfo GetBulletInfo( sal_Int32 nPara ) const override; + virtual tools::Rectangle GetCharBounds( sal_Int32 nPara, sal_Int32 nIndex ) const override; + virtual tools::Rectangle GetParaBounds( sal_Int32 nPara ) const override; + virtual MapMode GetMapMode() const override; + virtual OutputDevice* GetRefDevice() const override; + virtual bool GetIndexAtPoint( const Point&, sal_Int32& nPara, sal_Int32& nIndex ) const override; + virtual bool GetWordIndices( sal_Int32 nPara, sal_Int32 nIndex, sal_Int32& nStart, sal_Int32& nEnd ) const override; + virtual bool GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nPara, sal_Int32 nIndex, bool bInCell = false ) const override; + virtual sal_Int32 GetLineCount( sal_Int32 nPara ) const override; + virtual sal_Int32 GetLineLen( sal_Int32 nPara, sal_Int32 nLine ) const override; + virtual void GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const override; + virtual sal_Int32 GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nLine ) const override; + virtual bool Delete( const ESelection& ) override; + virtual bool InsertText( const OUString&, const ESelection& ) override; + virtual bool QuickFormatDoc( bool bFull = false ) override; + + virtual sal_Int16 GetDepth( sal_Int32 nPara ) const override; + virtual bool SetDepth( sal_Int32 nPara, sal_Int16 nNewDepth ) override; + + virtual const SfxItemSet* GetEmptyItemSetPtr() override; + // implementation functions for XParagraphAppend and XTextPortionAppend + virtual void AppendParagraph() override; + virtual sal_Int32 AppendTextPortion( sal_Int32 nPara, const OUString &rText, const SfxItemSet &rSet ) override; + + virtual void CopyText(const SvxTextForwarder& rSource) override; +}; + + +class SmEditViewForwarder : /* analog to SvxEditEngineViewForwarder */ + public SvxEditViewForwarder +{ + SmEditAccessible& rEditAcc; + + SmEditViewForwarder( const SmEditViewForwarder & ) = delete; + SmEditViewForwarder & operator = ( const SmEditViewForwarder & ) = delete; + +public: + explicit SmEditViewForwarder( SmEditAccessible& rAcc ); + virtual ~SmEditViewForwarder() override; + + virtual bool IsValid() const override; + + virtual Point LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const override; + virtual Point PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const override; + + virtual bool GetSelection( ESelection& rSelection ) const override; + virtual bool SetSelection( const ESelection& rSelection ) override; + virtual bool Copy() override; + virtual bool Cut() override; + virtual bool Paste() override; +}; + + +class SmEditSource : + public SvxEditSource +{ + SfxBroadcaster aBroadCaster; + SmViewForwarder aViewFwd; + SmTextForwarder aTextFwd; + SmEditViewForwarder aEditViewFwd; + + SmEditAccessible& rEditAcc; + + SmEditSource( const SmEditSource &rSrc ); + SmEditSource & operator = ( const SmEditSource & ) = delete; + +public: + SmEditSource( SmEditAccessible &rAcc ); + virtual ~SmEditSource() override; + + virtual std::unique_ptr<SvxEditSource> Clone() const override; + virtual SvxTextForwarder* GetTextForwarder() override; + virtual SvxViewForwarder* GetViewForwarder() override; + virtual SvxEditViewForwarder* GetEditViewForwarder( bool bCreate = false ) override; + virtual void UpdateData() override; + virtual SfxBroadcaster& GetBroadcaster() const override; +}; + + +typedef +cppu::WeakImplHelper + < + css::lang::XServiceInfo, + css::accessibility::XAccessible, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleEventBroadcaster + > +SmEditAccessibleBaseClass; + +class SmEditAccessible : + public SmEditAccessibleBaseClass +{ + OUString aAccName; + std::unique_ptr<::accessibility::AccessibleTextHelper> pTextHelper; + VclPtr<SmEditWindow> pWin; + + SmEditAccessible( const SmEditAccessible & ) = delete; + SmEditAccessible & operator = ( const SmEditAccessible & ) = delete; + +public: + explicit SmEditAccessible( SmEditWindow *pEditWin ); + virtual ~SmEditAccessible() override; + + ::accessibility::AccessibleTextHelper * GetTextHelper(); + + void Init(); + void ClearWin(); // to be called when view is destroyed + + //! access EditEngine and EditView via the functions in the respective window + //! pointers may be 0 (e.g. during reload) + EditEngine * GetEditEngine() { return pWin ? pWin->GetEditEngine() : nullptr; } + EditView * GetEditView() { return pWin ? pWin->GetEditView() : nullptr; } + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/action.cxx b/starmath/source/action.cxx new file mode 100644 index 000000000..37e643c85 --- /dev/null +++ b/starmath/source/action.cxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <action.hxx> +#include <document.hxx> +#include <strings.hxx> + +SmFormatAction::SmFormatAction(SmDocShell *pDocSh, + const SmFormat& rOldFormat, + const SmFormat& rNewFormat) : + pDoc( pDocSh ), + aOldFormat( rOldFormat ), + aNewFormat( rNewFormat ) +{ +} + +void SmFormatAction::Undo() +{ + pDoc->SetFormat(aOldFormat); +} + +void SmFormatAction::Redo() +{ + pDoc->SetFormat(aNewFormat); +} + +void SmFormatAction::Repeat(SfxRepeatTarget& rDocSh) +{ + dynamic_cast< SmDocShell & >(rDocSh).SetFormat(aNewFormat); +} + +OUString SmFormatAction::GetComment() const +{ + return RID_UNDOFORMATNAME; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/caret.cxx b/starmath/source/caret.cxx new file mode 100644 index 000000000..4e2effaa2 --- /dev/null +++ b/starmath/source/caret.cxx @@ -0,0 +1,28 @@ +/* -*- 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 <caret.hxx> + +SmCaretPosGraph::SmCaretPosGraph() = default; + +SmCaretPosGraph::~SmCaretPosGraph() = default; + +SmCaretPosGraphEntry* SmCaretPosGraph::Add(SmCaretPos pos, + SmCaretPosGraphEntry* left) +{ + assert(pos.nIndex >= 0); + auto entry = std::make_unique<SmCaretPosGraphEntry>(pos, left, nullptr); + SmCaretPosGraphEntry* e = entry.get(); + //Set Left and Right to point to the entry itself if they are NULL + entry->Left = entry->Left ? entry->Left : e; + entry->Right = entry->Right ? entry->Right : e; + mvEntries.push_back(std::move(entry)); + return e; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/cfgitem.cxx b/starmath/source/cfgitem.cxx new file mode 100644 index 000000000..f3c0ec3e9 --- /dev/null +++ b/starmath/source/cfgitem.cxx @@ -0,0 +1,1269 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> +#include <memory> +#include <vector> + +#include <svl/itemset.hxx> +#include <svl/intitem.hxx> +#include <svl/itempool.hxx> +#include <svl/eitem.hxx> +#include <svl/languageoptions.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <i18nlangtag/languagetag.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <officecfg/Office/Math.hxx> +#include "cfgitem.hxx" + +#include <starmath.hrc> +#include <smmod.hxx> +#include <symbol.hxx> +#include <format.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; + +#define SYMBOL_LIST "SymbolList" +#define FONT_FORMAT_LIST "FontFormatList" + +static Sequence< OUString > lcl_GetFontPropertyNames() +{ + return Sequence< OUString > { + "Name", + "CharSet", + "Family", + "Pitch", + "Weight", + "Italic" + }; +} + +static Sequence< OUString > lcl_GetSymbolPropertyNames() +{ + return Sequence< OUString > { + "Char", + "Set", + "Predefined", + "FontFormatId" + }; +} + +static Sequence< OUString > lcl_GetFormatPropertyNames() +{ + //! Beware of order according to *_BEGIN *_END defines in format.hxx ! + //! see respective load/save routines here + return Sequence< OUString > { + "StandardFormat/Textmode", + "StandardFormat/GreekCharStyle", + "StandardFormat/ScaleNormalBracket", + "StandardFormat/HorizontalAlignment", + "StandardFormat/BaseSize", + "StandardFormat/TextSize", + "StandardFormat/IndexSize", + "StandardFormat/FunctionSize", + "StandardFormat/OperatorSize", + "StandardFormat/LimitsSize", + "StandardFormat/Distance/Horizontal", + "StandardFormat/Distance/Vertical", + "StandardFormat/Distance/Root", + "StandardFormat/Distance/SuperScript", + "StandardFormat/Distance/SubScript", + "StandardFormat/Distance/Numerator", + "StandardFormat/Distance/Denominator", + "StandardFormat/Distance/Fraction", + "StandardFormat/Distance/StrokeWidth", + "StandardFormat/Distance/UpperLimit", + "StandardFormat/Distance/LowerLimit", + "StandardFormat/Distance/BracketSize", + "StandardFormat/Distance/BracketSpace", + "StandardFormat/Distance/MatrixRow", + "StandardFormat/Distance/MatrixColumn", + "StandardFormat/Distance/OrnamentSize", + "StandardFormat/Distance/OrnamentSpace", + "StandardFormat/Distance/OperatorSize", + "StandardFormat/Distance/OperatorSpace", + "StandardFormat/Distance/LeftSpace", + "StandardFormat/Distance/RightSpace", + "StandardFormat/Distance/TopSpace", + "StandardFormat/Distance/BottomSpace", + "StandardFormat/Distance/NormalBracketSize", + "StandardFormat/VariableFont", + "StandardFormat/FunctionFont", + "StandardFormat/NumberFont", + "StandardFormat/TextFont", + "StandardFormat/SerifFont", + "StandardFormat/SansFont", + "StandardFormat/FixedFont" + }; +} + +struct SmCfgOther +{ + SmPrintSize ePrintSize; + sal_uInt16 nPrintZoomFactor; + bool bPrintTitle; + bool bPrintFormulaText; + bool bPrintFrame; + bool bIsSaveOnlyUsedSymbols; + bool bIsAutoCloseBrackets; + bool bIgnoreSpacesRight; + bool bToolboxVisible; + bool bAutoRedraw; + bool bFormulaCursor; + + SmCfgOther(); +}; + + +SmCfgOther::SmCfgOther() + : ePrintSize(PRINT_SIZE_NORMAL) + , nPrintZoomFactor(100) + , bPrintTitle(true) + , bPrintFormulaText(true) + , bPrintFrame(true) + , bIsSaveOnlyUsedSymbols(true) + , bIsAutoCloseBrackets(true) + , bIgnoreSpacesRight(true) + , bToolboxVisible(true) + , bAutoRedraw(true) + , bFormulaCursor(true) +{ +} + + +SmFontFormat::SmFontFormat() + : aName(FONTNAME_MATH) + , nCharSet(RTL_TEXTENCODING_UNICODE) + , nFamily(FAMILY_DONTKNOW) + , nPitch(PITCH_DONTKNOW) + , nWeight(WEIGHT_DONTKNOW) + , nItalic(ITALIC_NONE) +{ +} + + +SmFontFormat::SmFontFormat( const vcl::Font &rFont ) + : aName(rFont.GetFamilyName()) + , nCharSet(static_cast<sal_Int16>(rFont.GetCharSet())) + , nFamily(static_cast<sal_Int16>(rFont.GetFamilyType())) + , nPitch(static_cast<sal_Int16>(rFont.GetPitch())) + , nWeight(static_cast<sal_Int16>(rFont.GetWeight())) + , nItalic(static_cast<sal_Int16>(rFont.GetItalic())) +{ +} + + +vcl::Font SmFontFormat::GetFont() const +{ + vcl::Font aRes; + aRes.SetFamilyName( aName ); + aRes.SetCharSet( static_cast<rtl_TextEncoding>(nCharSet) ); + aRes.SetFamily( static_cast<FontFamily>(nFamily) ); + aRes.SetPitch( static_cast<FontPitch>(nPitch) ); + aRes.SetWeight( static_cast<FontWeight>(nWeight) ); + aRes.SetItalic( static_cast<FontItalic>(nItalic) ); + return aRes; +} + + +bool SmFontFormat::operator == ( const SmFontFormat &rFntFmt ) const +{ + return aName == rFntFmt.aName && + nCharSet == rFntFmt.nCharSet && + nFamily == rFntFmt.nFamily && + nPitch == rFntFmt.nPitch && + nWeight == rFntFmt.nWeight && + nItalic == rFntFmt.nItalic; +} + + +SmFntFmtListEntry::SmFntFmtListEntry( const OUString &rId, const SmFontFormat &rFntFmt ) : + aId (rId), + aFntFmt (rFntFmt) +{ +} + + +SmFontFormatList::SmFontFormatList() + : bModified(false) +{ +} + + +void SmFontFormatList::Clear() +{ + if (!aEntries.empty()) + { + aEntries.clear(); + SetModified( true ); + } +} + + +void SmFontFormatList::AddFontFormat( const OUString &rFntFmtId, + const SmFontFormat &rFntFmt ) +{ + const SmFontFormat *pFntFmt = GetFontFormat( rFntFmtId ); + OSL_ENSURE( !pFntFmt, "FontFormatId already exists" ); + if (!pFntFmt) + { + SmFntFmtListEntry aEntry( rFntFmtId, rFntFmt ); + aEntries.push_back( aEntry ); + SetModified( true ); + } +} + + +void SmFontFormatList::RemoveFontFormat( const OUString &rFntFmtId ) +{ + + // search for entry + for (size_t i = 0; i < aEntries.size(); ++i) + { + if (aEntries[i].aId == rFntFmtId) + { + // remove entry if found + aEntries.erase( aEntries.begin() + i ); + SetModified( true ); + break; + } + } +} + + +const SmFontFormat * SmFontFormatList::GetFontFormat( const OUString &rFntFmtId ) const +{ + const SmFontFormat *pRes = nullptr; + + for (const auto & rEntry : aEntries) + { + if (rEntry.aId == rFntFmtId) + { + pRes = &rEntry.aFntFmt; + break; + } + } + + return pRes; +} + + +const SmFontFormat * SmFontFormatList::GetFontFormat( size_t nPos ) const +{ + const SmFontFormat *pRes = nullptr; + if (nPos < aEntries.size()) + pRes = &aEntries[nPos].aFntFmt; + return pRes; +} + + +OUString SmFontFormatList::GetFontFormatId( const SmFontFormat &rFntFmt ) const +{ + OUString aRes; + + for (const auto & rEntry : aEntries) + { + if (rEntry.aFntFmt == rFntFmt) + { + aRes = rEntry.aId; + break; + } + } + + return aRes; +} + + +OUString SmFontFormatList::GetFontFormatId( const SmFontFormat &rFntFmt, bool bAdd ) +{ + OUString aRes( GetFontFormatId( rFntFmt) ); + if (aRes.isEmpty() && bAdd) + { + aRes = GetNewFontFormatId(); + AddFontFormat( aRes, rFntFmt ); + } + return aRes; +} + + +OUString SmFontFormatList::GetFontFormatId( size_t nPos ) const +{ + OUString aRes; + if (nPos < aEntries.size()) + aRes = aEntries[nPos].aId; + return aRes; +} + + +OUString SmFontFormatList::GetNewFontFormatId() const +{ + // returns first unused FormatId + + sal_Int32 nCnt = GetCount(); + for (sal_Int32 i = 1; i <= nCnt + 1; ++i) + { + OUString aTmpId = "Id" + OUString::number(i); + if (!GetFontFormat(aTmpId)) + return aTmpId; + } + OSL_ENSURE( false, "failed to create new FontFormatId" ); + + return OUString(); +} + + +SmMathConfig::SmMathConfig() : + ConfigItem("Office.Math") + , pFormat() + , pOther() + , pFontFormatList() + , pSymbolMgr() + , bIsOtherModified(false) + , bIsFormatModified(false) +{ +} + + +SmMathConfig::~SmMathConfig() +{ + Save(); +} + + +void SmMathConfig::SetOtherModified( bool bVal ) +{ + bIsOtherModified = bVal; +} + + +void SmMathConfig::SetFormatModified( bool bVal ) +{ + bIsFormatModified = bVal; +} + + +void SmMathConfig::ReadSymbol( SmSym &rSymbol, + const OUString &rSymbolName, + const OUString &rBaseNode ) const +{ + Sequence< OUString > aNames = lcl_GetSymbolPropertyNames(); + sal_Int32 nProps = aNames.getLength(); + + OUString aDelim( "/" ); + for (auto& rName : aNames) + rName = rBaseNode + aDelim + rSymbolName + aDelim + rName; + + const Sequence< Any > aValues = const_cast<SmMathConfig*>(this)->GetProperties(aNames); + + if (!(nProps && aValues.getLength() == nProps)) + return; + + const Any * pValue = aValues.getConstArray(); + vcl::Font aFont; + sal_UCS4 cChar = '\0'; + OUString aSet; + bool bPredefined = false; + + OUString aTmpStr; + sal_Int32 nTmp32 = 0; + bool bTmp = false; + + bool bOK = true; + if (pValue->hasValue() && (*pValue >>= nTmp32)) + cChar = static_cast< sal_UCS4 >( nTmp32 ); + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= aTmpStr)) + aSet = aTmpStr; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= bTmp)) + bPredefined = bTmp; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= aTmpStr)) + { + const SmFontFormat *pFntFmt = GetFontFormatList().GetFontFormat( aTmpStr ); + OSL_ENSURE( pFntFmt, "unknown FontFormat" ); + if (pFntFmt) + aFont = pFntFmt->GetFont(); + } + else + bOK = false; + ++pValue; + + if (bOK) + { + OUString aUiName( rSymbolName ); + OUString aUiSetName( aSet ); + if (bPredefined) + { + OUString aTmp; + aTmp = SmLocalizedSymbolData::GetUiSymbolName( rSymbolName ); + OSL_ENSURE( !aTmp.isEmpty(), "localized symbol-name not found" ); + if (!aTmp.isEmpty()) + aUiName = aTmp; + aTmp = SmLocalizedSymbolData::GetUiSymbolSetName( aSet ); + OSL_ENSURE( !aTmp.isEmpty(), "localized symbolset-name not found" ); + if (!aTmp.isEmpty()) + aUiSetName = aTmp; + } + + rSymbol = SmSym( aUiName, aFont, cChar, aUiSetName, bPredefined ); + if (aUiName != rSymbolName) + rSymbol.SetExportName( rSymbolName ); + } + else + { + SAL_WARN("starmath", "symbol read error"); + } +} + + +SmSymbolManager & SmMathConfig::GetSymbolManager() +{ + if (!pSymbolMgr) + { + pSymbolMgr.reset(new SmSymbolManager); + pSymbolMgr->Load(); + } + return *pSymbolMgr; +} + + +void SmMathConfig::ImplCommit() +{ + Save(); +} + + +void SmMathConfig::Save() +{ + SaveOther(); + SaveFormat(); + SaveFontFormatList(); +} + + +void SmMathConfig::GetSymbols( std::vector< SmSym > &rSymbols ) const +{ + Sequence< OUString > aNodes(const_cast<SmMathConfig*>(this)->GetNodeNames(SYMBOL_LIST)); + const OUString *pNode = aNodes.getConstArray(); + sal_Int32 nNodes = aNodes.getLength(); + + rSymbols.resize( nNodes ); + for (auto& rSymbol : rSymbols) + { + ReadSymbol( rSymbol, *pNode++, SYMBOL_LIST ); + } +} + + +void SmMathConfig::SetSymbols( const std::vector< SmSym > &rNewSymbols ) +{ + auto nCount = sal::static_int_cast<sal_Int32>(rNewSymbols.size()); + + Sequence< OUString > aNames = lcl_GetSymbolPropertyNames(); + const OUString *pNames = aNames.getConstArray(); + sal_Int32 nSymbolProps = aNames.getLength(); + + Sequence< PropertyValue > aValues( nCount * nSymbolProps ); + PropertyValue *pValues = aValues.getArray(); + + PropertyValue *pVal = pValues; + OUString aDelim( "/" ); + for (const SmSym& rSymbol : rNewSymbols) + { + OUString aNodeNameDelim = SYMBOL_LIST + + aDelim + + rSymbol.GetExportName() + + aDelim; + + const OUString *pName = pNames; + + // Char + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= rSymbol.GetCharacter(); + pVal++; + // Set + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + OUString aTmp( rSymbol.GetSymbolSetName() ); + if (rSymbol.IsPredefined()) + aTmp = SmLocalizedSymbolData::GetExportSymbolSetName( aTmp ); + pVal->Value <<= aTmp; + pVal++; + // Predefined + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= rSymbol.IsPredefined(); + pVal++; + // FontFormatId + SmFontFormat aFntFmt( rSymbol.GetFace() ); + OUString aFntFmtId( GetFontFormatList().GetFontFormatId( aFntFmt, true ) ); + OSL_ENSURE( !aFntFmtId.isEmpty(), "FontFormatId not found" ); + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmtId; + pVal++; + } + OSL_ENSURE( pVal - pValues == sal::static_int_cast< ptrdiff_t >(nCount * nSymbolProps), "properties missing" ); + ReplaceSetProperties( SYMBOL_LIST, aValues ); + + StripFontFormatList( rNewSymbols ); + SaveFontFormatList(); +} + + +SmFontFormatList & SmMathConfig::GetFontFormatList() +{ + if (!pFontFormatList) + { + LoadFontFormatList(); + } + return *pFontFormatList; +} + + +void SmMathConfig::LoadFontFormatList() +{ + if (!pFontFormatList) + pFontFormatList.reset(new SmFontFormatList); + else + pFontFormatList->Clear(); + + const Sequence< OUString > aNodes( GetNodeNames( FONT_FORMAT_LIST ) ); + + for (const OUString& rNode : aNodes) + { + SmFontFormat aFntFmt; + ReadFontFormat( aFntFmt, rNode, FONT_FORMAT_LIST ); + if (!pFontFormatList->GetFontFormat( rNode )) + pFontFormatList->AddFontFormat( rNode, aFntFmt ); + } + pFontFormatList->SetModified( false ); +} + + +void SmMathConfig::ReadFontFormat( SmFontFormat &rFontFormat, + const OUString &rSymbolName, const OUString &rBaseNode ) const +{ + Sequence< OUString > aNames = lcl_GetFontPropertyNames(); + sal_Int32 nProps = aNames.getLength(); + + OUString aDelim( "/" ); + for (auto& rName : aNames) + rName = rBaseNode + aDelim + rSymbolName + aDelim + rName; + + const Sequence< Any > aValues = const_cast<SmMathConfig*>(this)->GetProperties(aNames); + + if (!(nProps && aValues.getLength() == nProps)) + return; + + const Any * pValue = aValues.getConstArray(); + + OUString aTmpStr; + sal_Int16 nTmp16 = 0; + + bool bOK = true; + if (pValue->hasValue() && (*pValue >>= aTmpStr)) + rFontFormat.aName = aTmpStr; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nCharSet = nTmp16; // 6.0 file-format GetSOLoadTextEncoding not needed + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nFamily = nTmp16; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nPitch = nTmp16; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nWeight = nTmp16; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nItalic = nTmp16; + else + bOK = false; + ++pValue; + + OSL_ENSURE( bOK, "read FontFormat failed" ); +} + + +void SmMathConfig::SaveFontFormatList() +{ + SmFontFormatList &rFntFmtList = GetFontFormatList(); + + if (!rFntFmtList.IsModified()) + return; + + Sequence< OUString > aNames = lcl_GetFontPropertyNames(); + sal_Int32 nSymbolProps = aNames.getLength(); + + size_t nCount = rFntFmtList.GetCount(); + + Sequence< PropertyValue > aValues( nCount * nSymbolProps ); + PropertyValue *pValues = aValues.getArray(); + + PropertyValue *pVal = pValues; + OUString aDelim( "/" ); + for (size_t i = 0; i < nCount; ++i) + { + OUString aFntFmtId(rFntFmtList.GetFontFormatId(i)); + const SmFontFormat *pFntFmt = rFntFmtList.GetFontFormat(i); + assert(pFntFmt); + const SmFontFormat aFntFmt(*pFntFmt); + + OUString aNodeNameDelim = FONT_FORMAT_LIST + + aDelim + + aFntFmtId + + aDelim; + + const OUString *pName = aNames.getConstArray(); + + // Name + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.aName; + pVal++; + // CharSet + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nCharSet; // 6.0 file-format GetSOStoreTextEncoding not needed + pVal++; + // Family + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nFamily; + pVal++; + // Pitch + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nPitch; + pVal++; + // Weight + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nWeight; + pVal++; + // Italic + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nItalic; + pVal++; + } + OSL_ENSURE( sal::static_int_cast<size_t>(pVal - pValues) == nCount * nSymbolProps, "properties missing" ); + ReplaceSetProperties( FONT_FORMAT_LIST, aValues ); + + rFntFmtList.SetModified( false ); +} + + +void SmMathConfig::StripFontFormatList( const std::vector< SmSym > &rSymbols ) +{ + size_t i; + + // build list of used font-formats only + //!! font-format IDs may be different !! + SmFontFormatList aUsedList; + for (i = 0; i < rSymbols.size(); ++i) + { + OSL_ENSURE( rSymbols[i].GetName().getLength() > 0, "non named symbol" ); + aUsedList.GetFontFormatId( SmFontFormat( rSymbols[i].GetFace() ) , true ); + } + const SmFormat & rStdFmt = GetStandardFormat(); + for (i = FNT_BEGIN; i <= FNT_END; ++i) + { + aUsedList.GetFontFormatId( SmFontFormat( rStdFmt.GetFont( i ) ) , true ); + } + + // remove unused font-formats from list + SmFontFormatList &rFntFmtList = GetFontFormatList(); + size_t nCnt = rFntFmtList.GetCount(); + std::unique_ptr<SmFontFormat[]> pTmpFormat(new SmFontFormat[ nCnt ]); + std::unique_ptr<OUString[]> pId(new OUString[ nCnt ]); + size_t k; + for (k = 0; k < nCnt; ++k) + { + pTmpFormat[k] = *rFntFmtList.GetFontFormat( k ); + pId[k] = rFntFmtList.GetFontFormatId( k ); + } + for (k = 0; k < nCnt; ++k) + { + if (aUsedList.GetFontFormatId( pTmpFormat[k] ).isEmpty()) + { + rFntFmtList.RemoveFontFormat( pId[k] ); + } + } +} + + +void SmMathConfig::LoadOther() +{ + if (!pOther) + pOther.reset(new SmCfgOther); + + pOther->bPrintTitle = officecfg::Office::Math::Print::Title::get(); + pOther->bPrintFormulaText = officecfg::Office::Math::Print::FormulaText::get(); + pOther->bPrintFrame = officecfg::Office::Math::Print::Frame::get(); + pOther->ePrintSize = static_cast<SmPrintSize>(officecfg::Office::Math::Print::Size::get()); + pOther->nPrintZoomFactor = officecfg::Office::Math::Print::ZoomFactor::get(); + pOther->bIsSaveOnlyUsedSymbols = officecfg::Office::Math::LoadSave::IsSaveOnlyUsedSymbols::get(); + pOther->bIsAutoCloseBrackets = officecfg::Office::Math::Misc::AutoCloseBrackets::get(); + pOther->bIgnoreSpacesRight = officecfg::Office::Math::Misc::IgnoreSpacesRight::get(); + pOther->bToolboxVisible = officecfg::Office::Math::View::ToolboxVisible::get(); + pOther->bAutoRedraw = officecfg::Office::Math::View::AutoRedraw::get(); + pOther->bFormulaCursor = officecfg::Office::Math::View::FormulaCursor::get(); + SetOtherModified( false ); +} + + +void SmMathConfig::SaveOther() +{ + if (!pOther || !IsOtherModified()) + return; + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + + officecfg::Office::Math::Print::Title::set(pOther->bPrintTitle, batch); + officecfg::Office::Math::Print::FormulaText::set(pOther->bPrintFormulaText, batch); + officecfg::Office::Math::Print::Frame::set(pOther->bPrintFrame, batch); + officecfg::Office::Math::Print::Size::set(pOther->ePrintSize, batch); + officecfg::Office::Math::Print::ZoomFactor::set(pOther->nPrintZoomFactor, batch); + officecfg::Office::Math::LoadSave::IsSaveOnlyUsedSymbols::set(pOther->bIsSaveOnlyUsedSymbols, batch); + officecfg::Office::Math::Misc::AutoCloseBrackets::set(pOther->bIsAutoCloseBrackets, batch); + officecfg::Office::Math::Misc::IgnoreSpacesRight::set(pOther->bIgnoreSpacesRight, batch); + officecfg::Office::Math::View::ToolboxVisible::set(pOther->bToolboxVisible, batch); + officecfg::Office::Math::View::AutoRedraw::set(pOther->bAutoRedraw, batch); + officecfg::Office::Math::View::FormulaCursor::set(pOther->bFormulaCursor, batch); + + batch->commit(); + SetOtherModified( false ); +} + +namespace { + +// Latin default-fonts +const DefaultFontType aLatinDefFnts[FNT_END] = +{ + DefaultFontType::SERIF, // FNT_VARIABLE + DefaultFontType::SERIF, // FNT_FUNCTION + DefaultFontType::SERIF, // FNT_NUMBER + DefaultFontType::SERIF, // FNT_TEXT + DefaultFontType::SERIF, // FNT_SERIF + DefaultFontType::SANS, // FNT_SANS + DefaultFontType::FIXED // FNT_FIXED + //OpenSymbol, // FNT_MATH +}; + +// CJK default-fonts +//! we use non-asian fonts for variables, functions and numbers since they +//! look better and even in asia only latin letters will be used for those. +//! At least that's what I was told... +const DefaultFontType aCJKDefFnts[FNT_END] = +{ + DefaultFontType::SERIF, // FNT_VARIABLE + DefaultFontType::SERIF, // FNT_FUNCTION + DefaultFontType::SERIF, // FNT_NUMBER + DefaultFontType::CJK_TEXT, // FNT_TEXT + DefaultFontType::CJK_TEXT, // FNT_SERIF + DefaultFontType::CJK_DISPLAY, // FNT_SANS + DefaultFontType::CJK_TEXT // FNT_FIXED + //OpenSymbol, // FNT_MATH +}; + +// CTL default-fonts +const DefaultFontType aCTLDefFnts[FNT_END] = +{ + DefaultFontType::CTL_TEXT, // FNT_VARIABLE + DefaultFontType::CTL_TEXT, // FNT_FUNCTION + DefaultFontType::CTL_TEXT, // FNT_NUMBER + DefaultFontType::CTL_TEXT, // FNT_TEXT + DefaultFontType::CTL_TEXT, // FNT_SERIF + DefaultFontType::CTL_TEXT, // FNT_SANS + DefaultFontType::CTL_TEXT // FNT_FIXED + //OpenSymbol, // FNT_MATH +}; + + +OUString lcl_GetDefaultFontName( LanguageType nLang, sal_uInt16 nIdent ) +{ + assert(nIdent < FNT_END); + const DefaultFontType *pTable; + switch ( SvtLanguageOptions::GetScriptTypeOfLanguage( nLang ) ) + { + case SvtScriptType::LATIN : pTable = aLatinDefFnts; break; + case SvtScriptType::ASIAN : pTable = aCJKDefFnts; break; + case SvtScriptType::COMPLEX : pTable = aCTLDefFnts; break; + default : + pTable = aLatinDefFnts; + SAL_WARN("starmath", "unknown script-type"); + } + + return OutputDevice::GetDefaultFont(pTable[ nIdent ], nLang, + GetDefaultFontFlags::OnlyOne ).GetFamilyName(); +} + +} + + +void SmMathConfig::LoadFormat() +{ + if (!pFormat) + pFormat.reset(new SmFormat); + + + Sequence< OUString > aNames = lcl_GetFormatPropertyNames(); + + sal_Int32 nProps = aNames.getLength(); + + Sequence< Any > aValues( GetProperties( aNames ) ); + if (!(nProps && aValues.getLength() == nProps)) + return; + + const Any *pValues = aValues.getConstArray(); + const Any *pVal = pValues; + + OUString aTmpStr; + sal_Int16 nTmp16 = 0; + bool bTmp = false; + + // StandardFormat/Textmode + if (pVal->hasValue() && (*pVal >>= bTmp)) + pFormat->SetTextmode( bTmp ); + ++pVal; + // StandardFormat/GreekCharStyle + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetGreekCharStyle( nTmp16 ); + ++pVal; + // StandardFormat/ScaleNormalBracket + if (pVal->hasValue() && (*pVal >>= bTmp)) + pFormat->SetScaleNormalBrackets( bTmp ); + ++pVal; + // StandardFormat/HorizontalAlignment + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetHorAlign( static_cast<SmHorAlign>(nTmp16) ); + ++pVal; + // StandardFormat/BaseSize + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetBaseSize( Size(0, SmPtsTo100th_mm( nTmp16 )) ); + ++pVal; + + sal_uInt16 i; + for (i = SIZ_BEGIN; i <= SIZ_END; ++i) + { + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetRelSize( i, nTmp16 ); + ++pVal; + } + + for (i = DIS_BEGIN; i <= DIS_END; ++i) + { + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetDistance( i, nTmp16 ); + ++pVal; + } + + LanguageType nLang = Application::GetSettings().GetUILanguageTag().getLanguageType(); + for (i = FNT_BEGIN; i < FNT_END; ++i) + { + vcl::Font aFnt; + bool bUseDefaultFont = true; + if (pVal->hasValue() && (*pVal >>= aTmpStr)) + { + bUseDefaultFont = aTmpStr.isEmpty(); + if (bUseDefaultFont) + { + aFnt = pFormat->GetFont( i ); + aFnt.SetFamilyName( lcl_GetDefaultFontName( nLang, i ) ); + } + else + { + const SmFontFormat *pFntFmt = GetFontFormatList().GetFontFormat( aTmpStr ); + OSL_ENSURE( pFntFmt, "unknown FontFormat" ); + if (pFntFmt) + aFnt = pFntFmt->GetFont(); + } + } + ++pVal; + + aFnt.SetFontSize( pFormat->GetBaseSize() ); + pFormat->SetFont( i, aFnt, bUseDefaultFont ); + } + + OSL_ENSURE( pVal - pValues == nProps, "property mismatch" ); + SetFormatModified( false ); +} + + +void SmMathConfig::SaveFormat() +{ + if (!pFormat || !IsFormatModified()) + return; + + const Sequence< OUString > aNames = lcl_GetFormatPropertyNames(); + sal_Int32 nProps = aNames.getLength(); + + Sequence< Any > aValues( nProps ); + Any *pValues = aValues.getArray(); + Any *pValue = pValues; + + // StandardFormat/Textmode + *pValue++ <<= pFormat->IsTextmode(); + // StandardFormat/GreekCharStyle + *pValue++ <<= pFormat->GetGreekCharStyle(); + // StandardFormat/ScaleNormalBracket + *pValue++ <<= pFormat->IsScaleNormalBrackets(); + // StandardFormat/HorizontalAlignment + *pValue++ <<= static_cast<sal_Int16>(pFormat->GetHorAlign()); + // StandardFormat/BaseSize + *pValue++ <<= static_cast<sal_Int16>(SmRoundFraction( Sm100th_mmToPts( + pFormat->GetBaseSize().Height() ) )); + + sal_uInt16 i; + for (i = SIZ_BEGIN; i <= SIZ_END; ++i) + *pValue++ <<= static_cast<sal_Int16>(pFormat->GetRelSize( i )); + + for (i = DIS_BEGIN; i <= DIS_END; ++i) + *pValue++ <<= static_cast<sal_Int16>(pFormat->GetDistance( i )); + + for (i = FNT_BEGIN; i < FNT_END; ++i) + { + OUString aFntFmtId; + + if (!pFormat->IsDefaultFont( i )) + { + SmFontFormat aFntFmt( pFormat->GetFont( i ) ); + aFntFmtId = GetFontFormatList().GetFontFormatId( aFntFmt, true ); + OSL_ENSURE( !aFntFmtId.isEmpty(), "FontFormatId not found" ); + } + + *pValue++ <<= aFntFmtId; + } + + OSL_ENSURE( pValue - pValues == nProps, "property mismatch" ); + PutProperties( aNames , aValues ); + + SetFormatModified( false ); +} + + +const SmFormat & SmMathConfig::GetStandardFormat() const +{ + if (!pFormat) + const_cast<SmMathConfig*>(this)->LoadFormat(); + return *pFormat; +} + + +void SmMathConfig::SetStandardFormat( const SmFormat &rFormat, bool bSaveFontFormatList ) +{ + if (!pFormat) + LoadFormat(); + if (rFormat == *pFormat) + return; + + *pFormat = rFormat; + SetFormatModified( true ); + SaveFormat(); + + if (bSaveFontFormatList) + { + // needed for SmFontTypeDialog's DefaultButtonClickHdl + if (pFontFormatList) + pFontFormatList->SetModified( true ); + SaveFontFormatList(); + } +} + + +SmPrintSize SmMathConfig::GetPrintSize() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->ePrintSize; +} + + +void SmMathConfig::SetPrintSize( SmPrintSize eSize ) +{ + if (!pOther) + LoadOther(); + if (eSize != pOther->ePrintSize) + { + pOther->ePrintSize = eSize; + SetOtherModified( true ); + } +} + + +sal_uInt16 SmMathConfig::GetPrintZoomFactor() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->nPrintZoomFactor; +} + + +void SmMathConfig::SetPrintZoomFactor( sal_uInt16 nVal ) +{ + if (!pOther) + LoadOther(); + if (nVal != pOther->nPrintZoomFactor) + { + pOther->nPrintZoomFactor = nVal; + SetOtherModified( true ); + } +} + + +void SmMathConfig::SetOtherIfNotEqual( bool &rbItem, bool bNewVal ) +{ + if (bNewVal != rbItem) + { + rbItem = bNewVal; + SetOtherModified( true ); + } +} + + +bool SmMathConfig::IsPrintTitle() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bPrintTitle; +} + + +void SmMathConfig::SetPrintTitle( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bPrintTitle, bVal ); +} + + +bool SmMathConfig::IsPrintFormulaText() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bPrintFormulaText; +} + + +void SmMathConfig::SetPrintFormulaText( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bPrintFormulaText, bVal ); +} + +bool SmMathConfig::IsSaveOnlyUsedSymbols() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bIsSaveOnlyUsedSymbols; +} + +bool SmMathConfig::IsAutoCloseBrackets() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bIsAutoCloseBrackets; +} + +bool SmMathConfig::IsPrintFrame() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bPrintFrame; +} + + +void SmMathConfig::SetPrintFrame( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bPrintFrame, bVal ); +} + + +void SmMathConfig::SetSaveOnlyUsedSymbols( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bIsSaveOnlyUsedSymbols, bVal ); +} + + +void SmMathConfig::SetAutoCloseBrackets( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bIsAutoCloseBrackets, bVal ); +} + + +bool SmMathConfig::IsIgnoreSpacesRight() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bIgnoreSpacesRight; +} + + +void SmMathConfig::SetIgnoreSpacesRight( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bIgnoreSpacesRight, bVal ); +} + + +bool SmMathConfig::IsAutoRedraw() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bAutoRedraw; +} + + +void SmMathConfig::SetAutoRedraw( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bAutoRedraw, bVal ); +} + + +bool SmMathConfig::IsShowFormulaCursor() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bFormulaCursor; +} + + +void SmMathConfig::SetShowFormulaCursor( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bFormulaCursor, bVal ); +} + +void SmMathConfig::Notify( const css::uno::Sequence< OUString >& ) +{} + + +void SmMathConfig::ItemSetToConfig(const SfxItemSet &rSet) +{ + const SfxPoolItem *pItem = nullptr; + + sal_uInt16 nU16; + bool bVal; + if (rSet.GetItemState(SID_PRINTSIZE, true, &pItem) == SfxItemState::SET) + { nU16 = static_cast<const SfxUInt16Item *>(pItem)->GetValue(); + SetPrintSize( static_cast<SmPrintSize>(nU16) ); + } + if (rSet.GetItemState(SID_PRINTZOOM, true, &pItem) == SfxItemState::SET) + { nU16 = static_cast<const SfxUInt16Item *>(pItem)->GetValue(); + SetPrintZoomFactor( nU16 ); + } + if (rSet.GetItemState(SID_PRINTTITLE, true, &pItem) == SfxItemState::SET) + { bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + SetPrintTitle( bVal ); + } + if (rSet.GetItemState(SID_PRINTTEXT, true, &pItem) == SfxItemState::SET) + { bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + SetPrintFormulaText( bVal ); + } + if (rSet.GetItemState(SID_PRINTFRAME, true, &pItem) == SfxItemState::SET) + { bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + SetPrintFrame( bVal ); + } + if (rSet.GetItemState(SID_AUTOREDRAW, true, &pItem) == SfxItemState::SET) + { bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + SetAutoRedraw( bVal ); + } + if (rSet.GetItemState(SID_NO_RIGHT_SPACES, true, &pItem) == SfxItemState::SET) + { bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + if (IsIgnoreSpacesRight() != bVal) + { + SetIgnoreSpacesRight( bVal ); + + // reformat (displayed) formulas accordingly + Broadcast(SfxHint(SfxHintId::MathFormatChanged)); + } + } + if (rSet.GetItemState(SID_SAVE_ONLY_USED_SYMBOLS, true, &pItem) == SfxItemState::SET) + { bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + SetSaveOnlyUsedSymbols( bVal ); + } + + if (rSet.GetItemState(SID_AUTO_CLOSE_BRACKETS, true, &pItem) == SfxItemState::SET) + { + bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + SetAutoCloseBrackets( bVal ); + } + + SaveOther(); +} + + +void SmMathConfig::ConfigToItemSet(SfxItemSet &rSet) const +{ + const SfxItemPool *pPool = rSet.GetPool(); + + rSet.Put(SfxUInt16Item(pPool->GetWhich(SID_PRINTSIZE), + sal::static_int_cast<sal_uInt16>(GetPrintSize()))); + rSet.Put(SfxUInt16Item(pPool->GetWhich(SID_PRINTZOOM), + GetPrintZoomFactor())); + + rSet.Put(SfxBoolItem(pPool->GetWhich(SID_PRINTTITLE), IsPrintTitle())); + rSet.Put(SfxBoolItem(pPool->GetWhich(SID_PRINTTEXT), IsPrintFormulaText())); + rSet.Put(SfxBoolItem(pPool->GetWhich(SID_PRINTFRAME), IsPrintFrame())); + rSet.Put(SfxBoolItem(pPool->GetWhich(SID_AUTOREDRAW), IsAutoRedraw())); + rSet.Put(SfxBoolItem(pPool->GetWhich(SID_NO_RIGHT_SPACES), IsIgnoreSpacesRight())); + rSet.Put(SfxBoolItem(pPool->GetWhich(SID_SAVE_ONLY_USED_SYMBOLS), IsSaveOnlyUsedSymbols())); + rSet.Put(SfxBoolItem(pPool->GetWhich(SID_AUTO_CLOSE_BRACKETS), IsAutoCloseBrackets())); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/cfgitem.hxx b/starmath/source/cfgitem.hxx new file mode 100644 index 000000000..8fa101187 --- /dev/null +++ b/starmath/source/cfgitem.hxx @@ -0,0 +1,185 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_CFGITEM_HXX +#define INCLUDED_STARMATH_SOURCE_CFGITEM_HXX + +#include <utility.hxx> + +#include <vector> + +#include <rtl/ustring.hxx> +#include <svl/SfxBroadcaster.hxx> +#include <unotools/configitem.hxx> + +#include <types.hxx> +#include <memory> + +namespace com::sun::star::uno { template <class E> class Sequence; } + +class SmSym; +class SmSymbolManager; +class SmFormat; +namespace vcl { class Font; } +struct SmCfgOther; +class SfxItemSet; + +struct SmFontFormat +{ + OUString aName; + sal_Int16 nCharSet; + sal_Int16 nFamily; + sal_Int16 nPitch; + sal_Int16 nWeight; + sal_Int16 nItalic; + + SmFontFormat(); + explicit SmFontFormat( const vcl::Font &rFont ); + + vcl::Font GetFont() const; + bool operator == ( const SmFontFormat &rFntFmt ) const; +}; + +struct SmFntFmtListEntry +{ + OUString aId; + SmFontFormat aFntFmt; + + SmFntFmtListEntry( const OUString &rId, const SmFontFormat &rFntFmt ); +}; + +class SmFontFormatList +{ + std::vector<SmFntFmtListEntry> aEntries; + bool bModified; + + SmFontFormatList(const SmFontFormatList&) = delete; + SmFontFormatList& operator=(const SmFontFormatList&) = delete; + +public: + SmFontFormatList(); + + void Clear(); + void AddFontFormat( const OUString &rFntFmtId, const SmFontFormat &rFntFmt ); + void RemoveFontFormat( const OUString &rFntFmtId ); + + const SmFontFormat * GetFontFormat( const OUString &rFntFmtId ) const; + const SmFontFormat * GetFontFormat( size_t nPos ) const; + OUString GetFontFormatId( const SmFontFormat &rFntFmt ) const; + OUString GetFontFormatId( const SmFontFormat &rFntFmt, bool bAdd ); + OUString GetFontFormatId( size_t nPos ) const; + OUString GetNewFontFormatId() const; + size_t GetCount() const { return aEntries.size(); } + + bool IsModified() const { return bModified; } + void SetModified( bool bVal ) { bModified = bVal; } +}; + +class SmMathConfig final : public utl::ConfigItem, public SfxBroadcaster +{ + std::unique_ptr<SmFormat> pFormat; + std::unique_ptr<SmCfgOther> pOther; + std::unique_ptr<SmFontFormatList> pFontFormatList; + std::unique_ptr<SmSymbolManager> pSymbolMgr; + bool bIsOtherModified; + bool bIsFormatModified; + SmFontPickList vFontPickList[7]; + + SmMathConfig(const SmMathConfig&) = delete; + SmMathConfig& operator=(const SmMathConfig&) = delete; + + void StripFontFormatList( const std::vector< SmSym > &rSymbols ); + + + void Save(); + + void ReadSymbol( SmSym &rSymbol, + const OUString &rSymbolName, + const OUString &rBaseNode ) const; + void ReadFontFormat( SmFontFormat &rFontFormat, + const OUString &rSymbolName, + const OUString &rBaseNode ) const; + + void SetOtherIfNotEqual( bool &rbItem, bool bNewVal ); + + void LoadOther(); + void SaveOther(); + void LoadFormat(); + void SaveFormat(); + void LoadFontFormatList(); + void SaveFontFormatList(); + + void SetOtherModified( bool bVal ); + bool IsOtherModified() const { return bIsOtherModified; } + void SetFormatModified( bool bVal ); + bool IsFormatModified() const { return bIsFormatModified; } + + SmFontFormatList & GetFontFormatList(); + const SmFontFormatList & GetFontFormatList() const + { + return const_cast<SmMathConfig*>(this)->GetFontFormatList(); + } + + virtual void ImplCommit() override; + +public: + SmMathConfig(); + virtual ~SmMathConfig() override; + + // utl::ConfigItem + virtual void Notify( const css::uno::Sequence< OUString > &rPropertyNames ) override; + + SmSymbolManager & GetSymbolManager(); + void GetSymbols( std::vector< SmSym > &rSymbols ) const; + void SetSymbols( const std::vector< SmSym > &rNewSymbols ); + + const SmFormat & GetStandardFormat() const; + void SetStandardFormat( const SmFormat &rFormat, bool bSaveFontFormatList = false ); + + bool IsPrintTitle() const; + void SetPrintTitle( bool bVal ); + bool IsPrintFormulaText() const; + void SetPrintFormulaText( bool bVal ); + bool IsPrintFrame() const; + void SetPrintFrame( bool bVal ); + SmPrintSize GetPrintSize() const; + void SetPrintSize( SmPrintSize eSize ); + sal_uInt16 GetPrintZoomFactor() const; + void SetPrintZoomFactor( sal_uInt16 nVal ); + + bool IsSaveOnlyUsedSymbols() const; + void SetSaveOnlyUsedSymbols( bool bVal ); + bool IsAutoCloseBrackets() const; + void SetAutoCloseBrackets( bool bVal ); + bool IsIgnoreSpacesRight() const; + void SetIgnoreSpacesRight( bool bVal ); + bool IsAutoRedraw() const; + void SetAutoRedraw( bool bVal ); + bool IsShowFormulaCursor() const; + void SetShowFormulaCursor( bool bVal ); + + SmFontPickList & GetFontPickList(sal_uInt16 nIdent) { return vFontPickList[nIdent]; } + + void ItemSetToConfig(const SfxItemSet &rSet); + void ConfigToItemSet(SfxItemSet &rSet) const; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/cursor.cxx b/starmath/source/cursor.cxx new file mode 100644 index 000000000..2aae7adb5 --- /dev/null +++ b/starmath/source/cursor.cxx @@ -0,0 +1,1577 @@ +/* -*- 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 <memory> +#include <cursor.hxx> +#include <visitors.hxx> +#include <document.hxx> +#include <view.hxx> +#include <comphelper/string.hxx> +#include <editeng/editeng.hxx> +#include <osl/diagnose.h> +#include <cassert> + +void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){ + SmCaretPosGraphEntry* NewPos = nullptr; + switch(direction) + { + case MoveLeft: + if (mpPosition) + NewPos = mpPosition->Left; + OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!"); + break; + case MoveRight: + if (mpPosition) + NewPos = mpPosition->Right; + OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!"); + break; + case MoveUp: + //Implementation is practically identical to MoveDown, except for a single if statement + //so I've implemented them together and added a direction == MoveDown to the if statements. + case MoveDown: + if (mpPosition) + { + SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, mpPosition->CaretPos).GetResult(), + best_line, //Best approximated line found so far + curr_line; //Current line + long dbp_sq = 0; //Distance squared to best line + for(const auto &pEntry : *mpGraph) + { + //Reject it if it's the current position + if(pEntry->CaretPos == mpPosition->CaretPos) continue; + //Compute caret line + curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult(); + //Reject anything above if we're moving down + if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue; + //Reject anything below if we're moving up + if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight() + && direction == MoveUp) continue; + //Compare if it to what we have, if we have anything yet + if(NewPos){ + //Compute distance to current line squared, multiplied with a horizontal factor + long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR + + curr_line.SquaredDistanceY(from_line); + //Discard current line if best line is closer + if(dbp_sq <= dp_sq) continue; + } + //Take current line as the best + best_line = curr_line; + NewPos = pEntry.get(); + //Update distance to best line + dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR + + best_line.SquaredDistanceY(from_line); + } + } + break; + default: + assert(false); + } + if(NewPos){ + mpPosition = NewPos; + if(bMoveAnchor) + mpAnchor = NewPos; + RequestRepaint(); + } +} + +void SmCursor::MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor) +{ + SmCaretPosGraphEntry* NewPos = nullptr; + long dp_sq = 0, //Distance to current line squared + dbp_sq = 1; //Distance to best line squared + for(const auto &pEntry : *mpGraph) + { + OSL_ENSURE(pEntry->CaretPos.IsValid(), "The caret position graph may not have invalid positions!"); + //Compute current line + SmCaretLine curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult(); + //Compute squared distance to current line + dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos); + //If we have a position compare to it + if(NewPos){ + //If best line is closer, reject current line + if(dbp_sq <= dp_sq) continue; + } + //Accept current position as the best + NewPos = pEntry.get(); + //Update distance to best line + dbp_sq = dp_sq; + } + if(NewPos){ + mpPosition = NewPos; + if(bMoveAnchor) + mpAnchor = NewPos; + RequestRepaint(); + } +} + +void SmCursor::BuildGraph(){ + //Save the current anchor and position + SmCaretPos _anchor, _position; + //Release mpGraph if allocated + if(mpGraph){ + if(mpAnchor) + _anchor = mpAnchor->CaretPos; + if(mpPosition) + _position = mpPosition->CaretPos; + mpGraph.reset(); + //Reset anchor and position as they point into an old graph + mpAnchor = nullptr; + mpPosition = nullptr; + } + + //Build the new graph + mpGraph.reset(SmCaretPosGraphBuildingVisitor(mpTree).takeGraph()); + + //Restore anchor and position pointers + if(_anchor.IsValid() || _position.IsValid()){ + for(const auto &pEntry : *mpGraph) + { + if(_anchor == pEntry->CaretPos) + mpAnchor = pEntry.get(); + if(_position == pEntry->CaretPos) + mpPosition = pEntry.get(); + } + } + //Set position and anchor to first caret position + auto it = mpGraph->begin(); + assert(it != mpGraph->end()); + if(!mpPosition) + mpPosition = it->get(); + if(!mpAnchor) + mpAnchor = mpPosition; + + assert(mpPosition); + assert(mpAnchor); + OSL_ENSURE(mpPosition->CaretPos.IsValid(), "Position must be valid"); + OSL_ENSURE(mpAnchor->CaretPos.IsValid(), "Anchor must be valid"); +} + +bool SmCursor::SetCaretPosition(SmCaretPos pos){ + for(const auto &pEntry : *mpGraph) + { + if(pEntry->CaretPos == pos) + { + mpPosition = pEntry.get(); + mpAnchor = pEntry.get(); + return true; + } + } + return false; +} + +void SmCursor::AnnotateSelection(){ + //TODO: Manage a state, reset it upon modification and optimize this call + SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree); +} + +void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){ + SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible); +} + +void SmCursor::DeletePrev(OutputDevice* pDev){ + //Delete only a selection if there's a selection + if(HasSelection()){ + Delete(); + return; + } + + SmNode* pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + SmStructureNode* pLineParent = pLine->GetParent(); + int nLineOffsetIdx = pLineParent->IndexOfSubNode(pLine); + assert(nLineOffsetIdx >= 0); + + //If we're in front of a node who's parent is a TABLE + if (pLineParent->GetType() == SmNodeType::Table && mpPosition->CaretPos.nIndex == 0 && nLineOffsetIdx > 0) + { + size_t nLineOffset = nLineOffsetIdx; + //Now we can merge with nLineOffset - 1 + BeginEdit(); + //Line to merge things into, so we can delete pLine + SmNode* pMergeLine = pLineParent->GetSubNode(nLineOffset-1); + OSL_ENSURE(pMergeLine, "pMergeLine cannot be NULL!"); + SmCaretPos PosAfterDelete; + //Convert first line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pMergeLine, *pLineList); + if(!pLineList->empty()){ + //Find iterator to patch + SmNodeList::iterator patchPoint = pLineList->end(); + --patchPoint; + //Convert second line to list + NodeToList(pLine, *pLineList); + //Patch the line list + ++patchPoint; + PosAfterDelete = PatchLineList(pLineList.get(), patchPoint); + //Parse the line + pLine = SmNodeListParser().Parse(pLineList.get()); + } + pLineList.reset(); + pLineParent->SetSubNode(nLineOffset-1, pLine); + //Delete the removed line slot + SmNodeArray lines(pLineParent->GetNumSubNodes()-1); + for (size_t i = 0; i < pLineParent->GetNumSubNodes(); ++i) + { + if(i < nLineOffset) + lines[i] = pLineParent->GetSubNode(i); + else if(i > nLineOffset) + lines[i-1] = pLineParent->GetSubNode(i); + } + pLineParent->SetSubNodes(std::move(lines)); + //Rebuild graph + mpAnchor = nullptr; + mpPosition = nullptr; + BuildGraph(); + AnnotateSelection(); + //Set caret position + if(!SetCaretPosition(PosAfterDelete)) + SetCaretPosition(SmCaretPos(pLine, 0)); + //Finish editing + EndEdit(); + + //TODO: If we're in an empty (sub/super/*) script + /*}else if(pLineParent->GetType() == SmNodeType::SubSup && + nLineOffset != 0 && + pLine->GetType() == SmNodeType::Expression && + pLine->GetNumSubNodes() == 0){ + //There's a (sub/super) script we can delete + //Consider selecting the entire script if GetNumSubNodes() != 0 or pLine->GetType() != SmNodeType::Expression + //TODO: Handle case where we delete a limit + */ + + //Else move select, and delete if not complex + }else{ + Move(pDev, MoveLeft, false); + if(!HasComplexSelection()) + Delete(); + } +} + +void SmCursor::Delete(){ + //Return if we don't have a selection to delete + if(!HasSelection()) + return; + + //Enter edit section + BeginEdit(); + + //Set selected on nodes + AnnotateSelection(); + + //Find an arbitrary selected node + SmNode* pSNode = FindSelectedNode(mpTree); + assert(pSNode); + + //Find the topmost node of the line that holds the selection + SmNode* pLine = FindTopMostNodeInLine(pSNode, true); + OSL_ENSURE(pLine != mpTree, "Shouldn't be able to select the entire tree"); + + //Get the parent of the line + SmStructureNode* pLineParent = pLine->GetParent(); + //Find line offset in parent + int nLineOffset = pLineParent->IndexOfSubNode(pLine); + assert(nLineOffset >= 0); + + //Position after delete + SmCaretPos PosAfterDelete; + + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selected nodes and delete them... + SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList.get()); + + //Get the position to set after delete + PosAfterDelete = PatchLineList(pLineList.get(), patchIt); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nLineOffset, PosAfterDelete); +} + +void SmCursor::InsertNodes(std::unique_ptr<SmNodeList> pNewNodes){ + if(pNewNodes->empty()){ + return; + } + + //Begin edit section + BeginEdit(); + + //Get the current position + const SmCaretPos pos = mpPosition->CaretPos; + + //Find top most of line that holds position + SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode); + + //Find line parent and line index in parent + SmStructureNode* pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Find iterator for place to insert nodes + SmNodeList::iterator it = FindPositionInLineList(pLineList.get(), pos); + + //Insert all new nodes + SmNodeList::iterator newIt, + patchIt = it, // (pointless default value, fixes compiler warnings) + insIt; + for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); ++newIt){ + insIt = pLineList->insert(it, *newIt); + if(newIt == pNewNodes->begin()) + patchIt = insIt; + } + //Patch the places we've changed stuff + PatchLineList(pLineList.get(), patchIt); + SmCaretPos PosAfterInsert = PatchLineList(pLineList.get(), it); + //Release list, we've taken the nodes + pNewNodes.reset(); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); +} + +SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList, + const SmCaretPos& rCaretPos) +{ + //Find iterator for position + SmNodeList::iterator it = std::find(pLineList->begin(), pLineList->end(), rCaretPos.pSelectedNode); + if (it != pLineList->end()) + { + if((*it)->GetType() == SmNodeType::Text) + { + //Split textnode if needed + if(rCaretPos.nIndex > 0) + { + SmTextNode* pText = static_cast<SmTextNode*>(rCaretPos.pSelectedNode); + if (rCaretPos.nIndex == pText->GetText().getLength()) + return ++it; + OUString str1 = pText->GetText().copy(0, rCaretPos.nIndex); + OUString str2 = pText->GetText().copy(rCaretPos.nIndex); + pText->ChangeText(str1); + ++it; + //Insert str2 as new text node + assert(!str2.isEmpty()); + SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc()); + pNewText->ChangeText(str2); + it = pLineList->insert(it, pNewText); + } + }else + ++it; + //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly + return it; + } + //If we didn't find pSelectedNode, it must be because the caret is in front of the line + return pLineList->begin(); +} + +SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) { + //The nodes we should consider merging + SmNode *prev = nullptr, + *next = nullptr; + if(aIter != pLineList->end()) + next = *aIter; + if(aIter != pLineList->begin()) { + --aIter; + prev = *aIter; + ++aIter; + } + + //Check if there's textnodes to merge + if( prev && + next && + prev->GetType() == SmNodeType::Text && + next->GetType() == SmNodeType::Text && + ( prev->GetToken().eType != TNUMBER || + next->GetToken().eType == TNUMBER) ){ + SmTextNode *pText = static_cast<SmTextNode*>(prev), + *pOldN = static_cast<SmTextNode*>(next); + SmCaretPos retval(pText, pText->GetText().getLength()); + OUString newText = pText->GetText() + pOldN->GetText(); + pText->ChangeText(newText); + delete pOldN; + pLineList->erase(aIter); + return retval; + } + + //Check if there's a SmPlaceNode to remove: + if(prev && next && prev->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(next->GetToken())){ + --aIter; + aIter = pLineList->erase(aIter); + delete prev; + //Return caret pos in front of aIter + if(aIter != pLineList->begin()) + --aIter; //Thus find node before aIter + if(aIter == pLineList->begin()) + return SmCaretPos(); + return SmCaretPos::GetPosAfter(*aIter); + } + if(prev && next && next->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(prev->GetToken())){ + aIter = pLineList->erase(aIter); + delete next; + return SmCaretPos::GetPosAfter(prev); + } + + //If we didn't do anything return + if(!prev) //return an invalid to indicate we're in front of line + return SmCaretPos(); + return SmCaretPos::GetPosAfter(prev); +} + +SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList, + SmNodeList *pSelectedNodes) { + SmNodeList::iterator retval; + SmNodeList::iterator it = pLineList->begin(); + while(it != pLineList->end()){ + if((*it)->IsSelected()){ + //Split text nodes + if((*it)->GetType() == SmNodeType::Text) { + SmTextNode* pText = static_cast<SmTextNode*>(*it); + OUString aText = pText->GetText(); + //Start and lengths of the segments, 2 is the selected segment + int start2 = pText->GetSelectionStart(), + start3 = pText->GetSelectionEnd(), + len1 = start2 - 0, + len2 = start3 - start2, + len3 = aText.getLength() - start3; + SmToken aToken = pText->GetToken(); + sal_uInt16 eFontDesc = pText->GetFontDesc(); + //If we need make segment 1 + if(len1 > 0) { + OUString str = aText.copy(0, len1); + pText->ChangeText(str); + ++it; + } else {//Remove it if not needed + it = pLineList->erase(it); + delete pText; + } + //Set retval to be right after the selection + retval = it; + //if we need make segment 3 + if(len3 > 0) { + OUString str = aText.copy(start3, len3); + SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc); + pSeg3->ChangeText(str); + retval = pLineList->insert(it, pSeg3); + } + //If we need to save the selected text + if(pSelectedNodes && len2 > 0) { + OUString str = aText.copy(start2, len2); + SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc); + pSeg2->ChangeText(str); + pSelectedNodes->push_back(pSeg2); + } + } else { //if it's not textnode + SmNode* pNode = *it; + retval = it = pLineList->erase(it); + if(pSelectedNodes) + pSelectedNodes->push_back(pNode); + else + delete pNode; + } + } else + ++it; + } + return retval; +} + +void SmCursor::InsertSubSup(SmSubSup eSubSup) { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find Parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //TODO: Consider handling special cases where parent is an SmOperNode, + // Maybe this method should be able to add limits to an SmOperNode... + + //We begin modifying the tree here + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //Find node that this should be applied to + SmNode* pSubject; + bool bPatchLine = !pSelectedNodesList->empty(); //If the line should be patched later + if(it != pLineList->begin()) { + --it; + pSubject = *it; + ++it; + } else { + //Create a new place node + pSubject = new SmPlaceNode(); + pSubject->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + it = pLineList->insert(it, pSubject); + ++it; + bPatchLine = true; //We've modified the line it should be patched later. + } + + //Wrap the subject in a SmSubSupNode + SmSubSupNode* pSubSup; + if(pSubject->GetType() != SmNodeType::SubSup){ + SmToken token; + token.nGroup = TG::Power; + pSubSup = new SmSubSupNode(token); + pSubSup->SetBody(pSubject); + *(--it) = pSubSup; + ++it; + }else + pSubSup = static_cast<SmSubSupNode*>(pSubject); + //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit. + //and it pointer to the element following pSubSup in pLineList. + pSubject = nullptr; + + //Patch the line if we noted that was needed previously + if(bPatchLine) + PatchLineList(pLineList.get(), it); + + //Convert existing, if any, sub-/superscript line to list + SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup); + std::unique_ptr<SmNodeList> pScriptLineList(new SmNodeList); + NodeToList(pScriptLine, *pScriptLineList); + + //Add selection to pScriptLineList + unsigned int nOldSize = pScriptLineList->size(); + pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end()); + pSelectedNodesList.reset(); + + //Patch pScriptLineList if needed + if(0 < nOldSize && nOldSize < pScriptLineList->size()) { + SmNodeList::iterator iPatchPoint = pScriptLineList->begin(); + std::advance(iPatchPoint, nOldSize); + PatchLineList(pScriptLineList.get(), iPatchPoint); + } + + //Find caret pos, that should be used after sub-/superscription. + SmCaretPos PosAfterScript; //Leave invalid for first position + if (!pScriptLineList->empty()) + PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back()); + + //Parse pScriptLineList + pScriptLine = SmNodeListParser().Parse(pScriptLineList.get()); + pScriptLineList.reset(); + + //Insert pScriptLine back into the tree + pSubSup->SetSubSup(eSubSup, pScriptLine); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterScript, pScriptLine); +} + +void SmCursor::InsertBrackets(SmBracketType eBracketType) { + BeginEdit(); + + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //If there's no selected nodes, create a place node + std::unique_ptr<SmNode> pBodyNode; + SmCaretPos PosAfterInsert; + if(pSelectedNodesList->empty()) { + pBodyNode.reset(new SmPlaceNode()); + PosAfterInsert = SmCaretPos(pBodyNode.get(), 1); + } else + pBodyNode.reset(SmNodeListParser().Parse(pSelectedNodesList.get())); + + pSelectedNodesList.reset(); + + //Create SmBraceNode + SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5); + SmBraceNode *pBrace = new SmBraceNode(aTok); + pBrace->SetScaleMode(SmScaleMode::Height); + std::unique_ptr<SmNode> pLeft( CreateBracket(eBracketType, true) ), + pRight( CreateBracket(eBracketType, false) ); + std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken())); + pBody->SetSubNodes(std::move(pBodyNode), nullptr); + pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert into line + pLineList->insert(it, pBrace); + //Patch line (I think this is good enough) + SmCaretPos aAfter = PatchLineList(pLineList.get(), it); + if( !PosAfterInsert.IsValid() ) + PosAfterInsert = aAfter; + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); +} + +SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, bool bIsLeft) { + SmToken aTok; + if(bIsLeft){ + switch(eBracketType){ + case SmBracketType::Round: + aTok = SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5); + break; + case SmBracketType::Square: + aTok = SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5); + break; + case SmBracketType::Curly: + aTok = SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5); + break; + } + } else { + switch(eBracketType) { + case SmBracketType::Round: + aTok = SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5); + break; + case SmBracketType::Square: + aTok = SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5); + break; + case SmBracketType::Curly: + aTok = SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5); + break; + } + } + SmNode* pRetVal = new SmMathSymbolNode(aTok); + pRetVal->SetScaleMode(SmScaleMode::Height); + return pRetVal; +} + +bool SmCursor::InsertRow() { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Discover the context of this command + SmTableNode *pTable = nullptr; + SmMatrixNode *pMatrix = nullptr; + int nTableIndex = nParentIndex; + if(pLineParent->GetType() == SmNodeType::Table) + pTable = static_cast<SmTableNode*>(pLineParent); + //If it's wrapped in a SmLineNode, we can still insert a newline + else if(pLineParent->GetType() == SmNodeType::Line && + pLineParent->GetParent() && + pLineParent->GetParent()->GetType() == SmNodeType::Table) { + //NOTE: This hack might give problems if we stop ignoring SmAlignNode + pTable = static_cast<SmTableNode*>(pLineParent->GetParent()); + nTableIndex = pTable->IndexOfSubNode(pLineParent); + assert(nTableIndex >= 0); + } + if(pLineParent->GetType() == SmNodeType::Matrix) + pMatrix = static_cast<SmMatrixNode*>(pLineParent); + + //If we're not in a context that supports InsertRow, return sal_False + if(!pTable && !pMatrix) + return false; + + //Now we start editing + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Find position in line + SmNodeList::iterator it; + if(HasSelection()) { + //Take the selected nodes and delete them... + it = TakeSelectedNodesFromList(pLineList.get()); + } else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //New caret position after inserting the newline/row in whatever context + SmCaretPos PosAfterInsert; + + //If we're in the context of a table + if(pTable) { + std::unique_ptr<SmNodeList> pNewLineList(new SmNodeList); + //Move elements from pLineList to pNewLineList + SmNodeList& rLineList = *pLineList; + pNewLineList->splice(pNewLineList->begin(), rLineList, it, rLineList.end()); + //Make sure it is valid again + it = pLineList->end(); + if(it != pLineList->begin()) + --it; + if(pNewLineList->empty()) + pNewLineList->push_front(new SmPlaceNode()); + //Parse new line + std::unique_ptr<SmNode> pNewLine(SmNodeListParser().Parse(pNewLineList.get())); + pNewLineList.reset(); + //Wrap pNewLine in SmLineNode if needed + if(pLineParent->GetType() == SmNodeType::Line) { + std::unique_ptr<SmLineNode> pNewLineNode(new SmLineNode(SmToken(TNEWLINE, '\0', "newline"))); + pNewLineNode->SetSubNodes(std::move(pNewLine), nullptr); + pNewLine = std::move(pNewLineNode); + } + //Get position + PosAfterInsert = SmCaretPos(pNewLine.get(), 0); + //Move other nodes if needed + for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--) + pTable->SetSubNode(i, pTable->GetSubNode(i-1)); + + //Insert new line + pTable->SetSubNode(nTableIndex + 1, pNewLine.get()); + + //Check if we need to change token type: + if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) { + SmToken tok = pTable->GetToken(); + tok.eType = TSTACK; + pTable->SetToken(tok); + } + } + //If we're in the context of a matrix + else { + //Find position after insert and patch the list + PosAfterInsert = PatchLineList(pLineList.get(), it); + //Move other children + sal_uInt16 rows = pMatrix->GetNumRows(); + sal_uInt16 cols = pMatrix->GetNumCols(); + int nRowStart = (nParentIndex - nParentIndex % cols) + cols; + for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--) + pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols)); + for( int i = nRowStart; i < nRowStart + cols; i++) { + SmPlaceNode *pNewLine = new SmPlaceNode(); + if(i == nParentIndex + cols) + PosAfterInsert = SmCaretPos(pNewLine, 0); + pMatrix->SetSubNode(i, pNewLine); + } + pMatrix->SetRowCol(rows + 1, cols); + } + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); + //FinishEdit is actually used to handle situations where parent is an instance of + //SmSubSupNode. In this case parent should always be a table or matrix, however, for + //code reuse we just use FinishEdit() here too. + return true; +} + +void SmCursor::InsertFraction() { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find Parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //We begin modifying the tree here + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //Create pNum, and pDenom + bool bEmptyFraction = pSelectedNodesList->empty(); + std::unique_ptr<SmNode> pNum( bEmptyFraction + ? new SmPlaceNode() + : SmNodeListParser().Parse(pSelectedNodesList.get()) ); + std::unique_ptr<SmNode> pDenom(new SmPlaceNode()); + pSelectedNodesList.reset(); + + //Create new fraction + SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', "over", TG::Product, 0)); + std::unique_ptr<SmNode> pRect(new SmRectangleNode(SmToken())); + pFrac->SetSubNodes(std::move(pNum), std::move(pRect), std::move(pDenom)); + + //Insert in pLineList + SmNodeList::iterator patchIt = pLineList->insert(it, pFrac); + PatchLineList(pLineList.get(), patchIt); + PatchLineList(pLineList.get(), it); + + //Finish editing + SmNode *pSelectedNode = bEmptyFraction ? pFrac->GetSubNode(0) : pFrac->GetSubNode(2); + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, SmCaretPos(pSelectedNode, 1)); +} + +void SmCursor::InsertText(const OUString& aString) +{ + BeginEdit(); + + Delete(); + + SmToken token; + token.eType = TIDENT; + token.cMathChar = '\0'; + token.nGroup = TG::NONE; + token.nLevel = 5; + token.aText = aString; + + SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE); + pText->SetText(aString); + pText->AdjustFontDesc(); + pText->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pText); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertElement(SmFormulaElement element){ + BeginEdit(); + + Delete(); + + //Create new node + SmNode* pNewNode = nullptr; + switch(element){ + case BlankElement: + { + SmToken token; + token.eType = TBLANK; + token.nGroup = TG::Blank; + token.aText = "~"; + SmBlankNode* pBlankNode = new SmBlankNode(token); + pBlankNode->IncreaseBy(token); + pNewNode = pBlankNode; + }break; + case FactorialElement: + { + SmToken token(TFACT, MS_FACT, "fact", TG::UnOper, 5); + pNewNode = new SmMathSymbolNode(token); + }break; + case PlusElement: + { + SmToken token; + token.eType = TPLUS; + token.cMathChar = MS_PLUS; + token.nGroup = TG::UnOper | TG::Sum; + token.nLevel = 5; + token.aText = "+"; + pNewNode = new SmMathSymbolNode(token); + }break; + case MinusElement: + { + SmToken token; + token.eType = TMINUS; + token.cMathChar = MS_MINUS; + token.nGroup = TG::UnOper | TG::Sum; + token.nLevel = 5; + token.aText = "-"; + pNewNode = new SmMathSymbolNode(token); + }break; + case CDotElement: + { + SmToken token; + token.eType = TCDOT; + token.cMathChar = MS_CDOT; + token.nGroup = TG::Product; + token.aText = "cdot"; + pNewNode = new SmMathSymbolNode(token); + }break; + case EqualElement: + { + SmToken token; + token.eType = TASSIGN; + token.cMathChar = MS_ASSIGN; + token.nGroup = TG::Relation; + token.aText = "="; + pNewNode = new SmMathSymbolNode(token); + }break; + case LessThanElement: + { + SmToken token; + token.eType = TLT; + token.cMathChar = MS_LT; + token.nGroup = TG::Relation; + token.aText = "<"; + pNewNode = new SmMathSymbolNode(token); + }break; + case GreaterThanElement: + { + SmToken token; + token.eType = TGT; + token.cMathChar = MS_GT; + token.nGroup = TG::Relation; + token.aText = ">"; + pNewNode = new SmMathSymbolNode(token); + }break; + case PercentElement: + { + SmToken token; + token.eType = TTEXT; + token.cMathChar = MS_PERCENT; + token.nGroup = TG::NONE; + token.aText = "\"%\""; + pNewNode = new SmMathSymbolNode(token); + }break; + } + assert(pNewNode); + + //Prepare the new node + pNewNode->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert new node + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pNewNode); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertSpecial(const OUString& _aString) +{ + BeginEdit(); + Delete(); + + OUString aString = comphelper::string::strip(_aString, ' '); + + //Create instance of special node + SmToken token; + token.eType = TSPECIAL; + token.cMathChar = '\0'; + token.nGroup = TG::NONE; + token.nLevel = 5; + token.aText = aString; + SmSpecialNode* pSpecial = new SmSpecialNode(token); + + //Prepare the special node + pSpecial->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert the node + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pSpecial); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertCommandText(const OUString& aCommandText) { + //Parse the sub expression + auto xSubExpr = SmParser().ParseExpression(aCommandText); + + //Prepare the subtree + xSubExpr->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Convert subtree to list + SmNode* pSubExpr = xSubExpr.release(); + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pSubExpr, *pLineList); + + BeginEdit(); + + //Delete any selection + Delete(); + + //Insert it + InsertNodes(std::move(pLineList)); + + EndEdit(); +} + +void SmCursor::Copy(){ + if(!HasSelection()) + return; + + AnnotateSelection(); + //Find selected node + SmNode* pSNode = FindSelectedNode(mpTree); + assert(pSNode); + //Find visual line + SmNode* pLine = FindTopMostNodeInLine(pSNode, true); + assert(pLine); + + //Clone selected nodes + SmClipboard aClipboard; + if(IsLineCompositionNode(pLine)) + CloneLineToClipboard(static_cast<SmStructureNode*>(pLine), &aClipboard); + else{ + //Special care to only clone selected text + if(pLine->GetType() == SmNodeType::Text) { + SmTextNode *pText = static_cast<SmTextNode*>(pLine); + std::unique_ptr<SmTextNode> pClone(new SmTextNode( pText->GetToken(), pText->GetFontDesc() )); + int start = pText->GetSelectionStart(), + length = pText->GetSelectionEnd() - pText->GetSelectionStart(); + pClone->ChangeText(pText->GetText().copy(start, length)); + pClone->SetScaleMode(pText->GetScaleMode()); + aClipboard.push_front(std::move(pClone)); + } else { + SmCloningVisitor aCloneFactory; + aClipboard.push_front(std::unique_ptr<SmNode>(aCloneFactory.Clone(pLine))); + } + } + + //Set clipboard + if (!aClipboard.empty()) + maClipboard = std::move(aClipboard); +} + +void SmCursor::Paste() { + BeginEdit(); + Delete(); + + if (!maClipboard.empty()) + InsertNodes(CloneList(maClipboard)); + + EndEdit(); +} + +std::unique_ptr<SmNodeList> SmCursor::CloneList(SmClipboard &rClipboard){ + SmCloningVisitor aCloneFactory; + std::unique_ptr<SmNodeList> pClones(new SmNodeList); + + for(auto &xNode : rClipboard){ + SmNode *pClone = aCloneFactory.Clone(xNode.get()); + pClones->push_back(pClone); + } + + return pClones; +} + +SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){ + assert(pSNode); + //Move up parent until we find a node who's + //parent is NULL or isn't selected and not a type of: + // SmExpressionNode + // SmLineNode + // SmBinHorNode + // SmUnHorNode + // SmAlignNode + // SmFontNode + while(pSNode->GetParent() && + ((MoveUpIfSelected && + pSNode->GetParent()->IsSelected()) || + IsLineCompositionNode(pSNode->GetParent()))) + pSNode = pSNode->GetParent(); + //Now we have the selection line node + return pSNode; +} + +SmNode* SmCursor::FindSelectedNode(SmNode* pNode){ + if(pNode->GetNumSubNodes() == 0) + return nullptr; + for(auto pChild : *static_cast<SmStructureNode*>(pNode)) + { + if(!pChild) + continue; + if(pChild->IsSelected()) + return pChild; + SmNode* pRetVal = FindSelectedNode(pChild); + if(pRetVal) + return pRetVal; + } + return nullptr; +} + +void SmCursor::LineToList(SmStructureNode* pLine, SmNodeList& list){ + for(auto pChild : *pLine) + { + if (!pChild) + continue; + switch(pChild->GetType()){ + case SmNodeType::Line: + case SmNodeType::UnHor: + case SmNodeType::Expression: + case SmNodeType::BinHor: + case SmNodeType::Align: + case SmNodeType::Font: + LineToList(static_cast<SmStructureNode*>(pChild), list); + break; + case SmNodeType::Error: + delete pChild; + break; + default: + list.push_back(pChild); + } + } + pLine->ClearSubNodes(); + delete pLine; +} + +void SmCursor::CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard){ + SmCloningVisitor aCloneFactory; + for(auto pChild : *pLine) + { + if (!pChild) + continue; + if( IsLineCompositionNode( pChild ) ) + CloneLineToClipboard( static_cast<SmStructureNode*>(pChild), pClipboard ); + else if( pChild->IsSelected() && pChild->GetType() != SmNodeType::Error ) { + //Only clone selected text from SmTextNode + if(pChild->GetType() == SmNodeType::Text) { + SmTextNode *pText = static_cast<SmTextNode*>(pChild); + std::unique_ptr<SmTextNode> pClone(new SmTextNode( pChild->GetToken(), pText->GetFontDesc() )); + int start = pText->GetSelectionStart(), + length = pText->GetSelectionEnd() - pText->GetSelectionStart(); + pClone->ChangeText(pText->GetText().copy(start, length)); + pClone->SetScaleMode(pText->GetScaleMode()); + pClipboard->push_back(std::move(pClone)); + } else + pClipboard->push_back(std::unique_ptr<SmNode>(aCloneFactory.Clone(pChild))); + } + } +} + +bool SmCursor::IsLineCompositionNode(SmNode const * pNode){ + switch(pNode->GetType()){ + case SmNodeType::Line: + case SmNodeType::UnHor: + case SmNodeType::Expression: + case SmNodeType::BinHor: + case SmNodeType::Align: + case SmNodeType::Font: + return true; + default: + return false; + } +} + +int SmCursor::CountSelectedNodes(SmNode* pNode){ + if(pNode->GetNumSubNodes() == 0) + return 0; + int nCount = 0; + for(auto pChild : *static_cast<SmStructureNode*>(pNode)) + { + if (!pChild) + continue; + if(pChild->IsSelected() && !IsLineCompositionNode(pChild)) + nCount++; + nCount += CountSelectedNodes(pChild); + } + return nCount; +} + +bool SmCursor::HasComplexSelection(){ + if(!HasSelection()) + return false; + AnnotateSelection(); + + return CountSelectedNodes(mpTree) > 1; +} + +void SmCursor::FinishEdit(std::unique_ptr<SmNodeList> pLineList, + SmStructureNode* pParent, + int nParentIndex, + SmCaretPos PosAfterEdit, + SmNode* pStartLine) { + //Store number of nodes in line for later + int entries = pLineList->size(); + + //Parse list of nodes to a tree + SmNodeListParser parser; + std::unique_ptr<SmNode> pLine(parser.Parse(pLineList.get())); + pLineList.reset(); + + //Check if we're making the body of a subsup node bigger than one + if(pParent->GetType() == SmNodeType::SubSup && + nParentIndex == 0 && + entries > 1) { + //Wrap pLine in scalable round brackets + SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5); + std::unique_ptr<SmBraceNode> pBrace(new SmBraceNode(aTok)); + pBrace->SetScaleMode(SmScaleMode::Height); + std::unique_ptr<SmNode> pLeft( CreateBracket(SmBracketType::Round, true) ), + pRight( CreateBracket(SmBracketType::Round, false) ); + std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken())); + pBody->SetSubNodes(std::move(pLine), nullptr); + pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + pLine = std::move(pBrace); + //TODO: Consider the following alternative behavior: + //Consider the line: A + {B + C}^D lsub E + //Here pLineList is B, + and C and pParent is a subsup node with + //both RSUP and LSUB set. Imagine the user just inserted "B +" in + //the body of the subsup node... + //The most natural thing to do would be to make the line like this: + //A + B lsub E + C ^ D + //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP + //and RSUB to the last element in pLineList. But how should this act + //for CSUP and CSUB ??? + //For this reason and because brackets was faster to implement, this solution + //have been chosen. It might be worth working on the other solution later... + } + + //Set pStartLine if NULL + if(!pStartLine) + pStartLine = pLine.get(); + + //Insert it back into the parent + pParent->SetSubNode(nParentIndex, pLine.release()); + + //Rebuild graph of caret position + mpAnchor = nullptr; + mpPosition = nullptr; + BuildGraph(); + AnnotateSelection(); //Update selection annotation! + + //Set caret position + if(!SetCaretPosition(PosAfterEdit)) + SetCaretPosition(SmCaretPos(pStartLine, 0)); + + //End edit section + EndEdit(); +} + +void SmCursor::BeginEdit(){ + if(mnEditSections++ > 0) return; + + mbIsEnabledSetModifiedSmDocShell = mpDocShell->IsEnableSetModified(); + if( mbIsEnabledSetModifiedSmDocShell ) + mpDocShell->EnableSetModified( false ); +} + +void SmCursor::EndEdit(){ + if(--mnEditSections > 0) return; + + mpDocShell->SetFormulaArranged(false); + //Okay, I don't know what this does... :) + //It's used in SmDocShell::SetText and with places where everything is modified. + //I think it does some magic, with sfx, but everything is totally undocumented so + //it's kinda hard to tell... + if ( mbIsEnabledSetModifiedSmDocShell ) + mpDocShell->EnableSetModified( mbIsEnabledSetModifiedSmDocShell ); + //I think this notifies people around us that we've modified this document... + mpDocShell->SetModified(); + //I think SmDocShell uses this value when it sends an update graphics event + //Anyway comments elsewhere suggests it needs to be updated... + mpDocShell->mnModifyCount++; + + //TODO: Consider copying the update accessibility code from SmDocShell::SetText in here... + //This somehow updates the size of SmGraphicView if it is running in embedded mode + if( mpDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + mpDocShell->OnDocumentPrinterChanged(nullptr); + + //Request a repaint... + RequestRepaint(); + + //Update the edit engine and text of the document + OUString formula; + SmNodeToTextVisitor(mpTree, formula); + //mpTree->CreateTextFromNode(formula); + mpDocShell->maText = formula; + mpDocShell->GetEditEngine().QuickInsertText( formula, ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) ); + mpDocShell->GetEditEngine().QuickFormatDoc(); +} + +void SmCursor::RequestRepaint(){ + SmViewShell *pViewSh = SmGetActiveView(); + if( pViewSh ) { + if ( SfxObjectCreateMode::EMBEDDED == mpDocShell->GetCreateMode() ) + mpDocShell->Repaint(); + else + pViewSh->GetGraphicWindow().Invalidate(); + } +} + +bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType, SmBraceNode** ppBraceNode) const { + const SmCaretPos pos = GetPosition(); + if (!pos.IsValid()) { + return false; + } + + SmNode* pNode = pos.pSelectedNode; + + if (pNode->GetType() == SmNodeType::Text) { + SmTextNode* pTextNode = static_cast<SmTextNode*>(pNode); + if (pos.nIndex < pTextNode->GetText().getLength()) { + // The cursor is on a text node and at the middle of it. + return false; + } + } else { + if (pos.nIndex < 1) { + return false; + } + } + + while (true) { + SmStructureNode* pParentNode = pNode->GetParent(); + if (!pParentNode) { + // There's no brace body node in the ancestors. + return false; + } + + int index = pParentNode->IndexOfSubNode(pNode); + assert(index >= 0); + if (static_cast<size_t>(index + 1) != pParentNode->GetNumSubNodes()) { + // The cursor is not at the tail at one of ancestor nodes. + return false; + } + + pNode = pParentNode; + if (pNode->GetType() == SmNodeType::Bracebody) { + // Found the brace body node. + break; + } + } + + SmStructureNode* pBraceNodeTmp = pNode->GetParent(); + if (!pBraceNodeTmp || pBraceNodeTmp->GetType() != SmNodeType::Brace) { + // Brace node is invalid. + return false; + } + + SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp); + SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace(); + if (!pClosingNode) { + // Couldn't get closing symbol node. + return false; + } + + // Check if the closing brace matches eBracketType. + SmTokenType eClosingTokenType = pClosingNode->GetToken().eType; + switch (eBracketType) { + case SmBracketType::Round: if (eClosingTokenType != TRPARENT) { return false; } break; + case SmBracketType::Square: if (eClosingTokenType != TRBRACKET) { return false; } break; + case SmBracketType::Curly: if (eClosingTokenType != TRBRACE) { return false; } break; + default: + return false; + } + + if (ppBraceNode) { + *ppBraceNode = pBraceNode; + } + + return true; +} + +void SmCursor::MoveAfterBracket(SmBraceNode* pBraceNode) +{ + mpPosition->CaretPos.pSelectedNode = pBraceNode; + mpPosition->CaretPos.nIndex = 1; + mpAnchor->CaretPos.pSelectedNode = pBraceNode; + mpAnchor->CaretPos.nIndex = 1; + RequestRepaint(); +} + + +/////////////////////////////////////// SmNodeListParser + +SmNode* SmNodeListParser::Parse(SmNodeList* list){ + pList = list; + //Delete error nodes + SmNodeList::iterator it = pList->begin(); + while(it != pList->end()) { + if((*it)->GetType() == SmNodeType::Error){ + //Delete and erase + delete *it; + it = pList->erase(it); + }else + ++it; + } + SmNode* retval = Expression(); + pList = nullptr; + return retval; +} + +SmNode* SmNodeListParser::Expression(){ + SmNodeArray NodeArray; + //Accept as many relations as there is + while(Terminal()) + NodeArray.push_back(Relation()); + + //Create SmExpressionNode, I hope SmToken() will do :) + SmStructureNode* pExpr = new SmExpressionNode(SmToken()); + pExpr->SetSubNodes(std::move(NodeArray)); + return pExpr; +} + +SmNode* SmNodeListParser::Relation(){ + //Read a sum + std::unique_ptr<SmNode> pLeft(Sum()); + //While we have tokens and the next is a relation + while(Terminal() && IsRelationOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the relation + std::unique_ptr<SmNode> pRight(Sum()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Sum(){ + //Read a product + std::unique_ptr<SmNode> pLeft(Product()); + //While we have tokens and the next is a sum + while(Terminal() && IsSumOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the sum + std::unique_ptr<SmNode> pRight(Product()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Product(){ + //Read a Factor + std::unique_ptr<SmNode> pLeft(Factor()); + //While we have tokens and the next is a product + while(Terminal() && IsProductOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the operation + std::unique_ptr<SmNode> pRight(Factor()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Factor(){ + //Read unary operations + if(!Terminal()) + return Error(); + //Take care of unary operators + else if(IsUnaryOperator(Terminal()->GetToken())) + { + SmStructureNode *pUnary = new SmUnHorNode(SmToken()); + std::unique_ptr<SmNode> pOper(Terminal()), + pArg; + + if(Next()) + pArg.reset(Factor()); + else + pArg.reset(Error()); + + pUnary->SetSubNodes(std::move(pOper), std::move(pArg)); + return pUnary; + } + return Postfix(); +} + +SmNode* SmNodeListParser::Postfix(){ + if(!Terminal()) + return Error(); + std::unique_ptr<SmNode> pArg; + if(IsPostfixOperator(Terminal()->GetToken())) + pArg.reset(Error()); + else if(IsOperator(Terminal()->GetToken())) + return Error(); + else + pArg.reset(Take()); + while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) { + std::unique_ptr<SmStructureNode> pUnary(new SmUnHorNode(SmToken()) ); + std::unique_ptr<SmNode> pOper(Take()); + pUnary->SetSubNodes(std::move(pArg), std::move(pOper)); + pArg = std::move(pUnary); + } + return pArg.release(); +} + +SmNode* SmNodeListParser::Error(){ + return new SmErrorNode(SmToken()); +} + +bool SmNodeListParser::IsOperator(const SmToken &token) { + return IsRelationOperator(token) || + IsSumOperator(token) || + IsProductOperator(token) || + IsUnaryOperator(token) || + IsPostfixOperator(token); +} + +bool SmNodeListParser::IsRelationOperator(const SmToken &token) { + return bool(token.nGroup & TG::Relation); +} + +bool SmNodeListParser::IsSumOperator(const SmToken &token) { + return bool(token.nGroup & TG::Sum); +} + +bool SmNodeListParser::IsProductOperator(const SmToken &token) { + return token.nGroup & TG::Product && + token.eType != TWIDESLASH && + token.eType != TWIDEBACKSLASH && + token.eType != TUNDERBRACE && + token.eType != TOVERBRACE && + token.eType != TOVER; +} + +bool SmNodeListParser::IsUnaryOperator(const SmToken &token) { + return token.nGroup & TG::UnOper && + (token.eType == TPLUS || + token.eType == TMINUS || + token.eType == TPLUSMINUS || + token.eType == TMINUSPLUS || + token.eType == TNEG || + token.eType == TUOPER); +} + +bool SmNodeListParser::IsPostfixOperator(const SmToken &token) { + return token.eType == TFACT; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/dialog.cxx b/starmath/source/dialog.cxx new file mode 100644 index 000000000..77fc823ba --- /dev/null +++ b/starmath/source/dialog.cxx @@ -0,0 +1,2048 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <cassert> + +#include <comphelper/string.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <svtools/ctrltool.hxx> +#include <vcl/settings.hxx> +#include <vcl/wall.hxx> +#include <vcl/fontcharmap.hxx> +#include <sfx2/dispatch.hxx> +#include <svx/charmap.hxx> +#include <svx/ucsubset.hxx> + +#include <dialog.hxx> +#include <starmath.hrc> +#include <strings.hrc> +#include <helpids.h> +#include "cfgitem.hxx" +#include <smmod.hxx> +#include <symbol.hxx> +#include <view.hxx> + +#include <algorithm> + +namespace +{ + +void lclGetSettingColors(Color& rBackgroundColor, Color& rTextColor) +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + if (rStyleSettings.GetHighContrastMode()) + { + rBackgroundColor = rStyleSettings.GetFieldColor(); + rTextColor = rStyleSettings.GetFieldTextColor(); + } + else + { + rBackgroundColor = COL_WHITE; + rTextColor = COL_BLACK; + } +} + +// Since it's better to set/query the FontStyle via its attributes rather +// than via the StyleName we create a way to translate +// Attribute <-> StyleName + +class SmFontStyles +{ + OUString aNormal; + OUString aBold; + OUString aItalic; + OUString aBoldItalic; + +public: + SmFontStyles(); + + static sal_uInt16 GetCount() { return 4; } + const OUString& GetStyleName(const vcl::Font& rFont) const; + const OUString& GetStyleName(sal_uInt16 nIdx) const; +}; + +} // end anonymous namespace + +SmFontStyles::SmFontStyles() + : aNormal(SmResId(RID_FONTREGULAR)) + , aBold(SmResId(RID_FONTBOLD)) + , aItalic(SmResId(RID_FONTITALIC)) +{ + aBoldItalic = aBold; + aBoldItalic += ", "; + aBoldItalic += aItalic; +} + +const OUString& SmFontStyles::GetStyleName(const vcl::Font& rFont) const +{ + //! compare also SmSpecialNode::Prepare + bool bBold = IsBold( rFont ), + bItalic = IsItalic( rFont ); + + if (bBold && bItalic) + return aBoldItalic; + else if (bItalic) + return aItalic; + else if (bBold) + return aBold; + return aNormal; +} + +const OUString& SmFontStyles::GetStyleName( sal_uInt16 nIdx ) const +{ + // 0 = "normal", 1 = "italic", + // 2 = "bold", 3 = "bold italic" + + assert( nIdx < GetCount() ); + switch (nIdx) + { + case 0 : return aNormal; + case 1 : return aItalic; + case 2 : return aBold; + default: /*case 3:*/ return aBoldItalic; + } +} + +static const SmFontStyles & GetFontStyles() +{ + static const SmFontStyles aImpl; + return aImpl; +} + +void SetFontStyle(const OUString &rStyleName, vcl::Font &rFont) +{ + // Find index related to StyleName. For an empty StyleName it's assumed to be + // 0 (neither bold nor italic). + sal_uInt16 nIndex = 0; + if (!rStyleName.isEmpty()) + { + sal_uInt16 i; + const SmFontStyles &rStyles = GetFontStyles(); + for (i = 0; i < SmFontStyles::GetCount(); ++i) + if (rStyleName == rStyles.GetStyleName(i)) + break; + assert(i < SmFontStyles::GetCount() && "style-name unknown"); + nIndex = i; + } + + rFont.SetItalic((nIndex & 0x1) ? ITALIC_NORMAL : ITALIC_NONE); + rFont.SetWeight((nIndex & 0x2) ? WEIGHT_BOLD : WEIGHT_NORMAL); +} + +IMPL_LINK_NOARG(SmPrintOptionsTabPage, SizeButtonClickHdl, weld::ToggleButton&, void) +{ + m_xZoom->set_sensitive(m_xSizeZoomed->get_active()); +} + +SmPrintOptionsTabPage::SmPrintOptionsTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rOptions) + : SfxTabPage(pPage, pController, "modules/smath/ui/smathsettings.ui", "SmathSettings", &rOptions) + , m_xTitle(m_xBuilder->weld_check_button("title")) + , m_xText(m_xBuilder->weld_check_button("text")) + , m_xFrame(m_xBuilder->weld_check_button("frame")) + , m_xSizeNormal(m_xBuilder->weld_radio_button("sizenormal")) + , m_xSizeScaled(m_xBuilder->weld_radio_button("sizescaled")) + , m_xSizeZoomed(m_xBuilder->weld_radio_button("sizezoomed")) + , m_xZoom(m_xBuilder->weld_metric_spin_button("zoom", FieldUnit::PERCENT)) + , m_xNoRightSpaces(m_xBuilder->weld_check_button("norightspaces")) + , m_xSaveOnlyUsedSymbols(m_xBuilder->weld_check_button("saveonlyusedsymbols")) + , m_xAutoCloseBrackets(m_xBuilder->weld_check_button("autoclosebrackets")) +{ + m_xSizeNormal->connect_toggled(LINK(this, SmPrintOptionsTabPage, SizeButtonClickHdl)); + m_xSizeScaled->connect_toggled(LINK(this, SmPrintOptionsTabPage, SizeButtonClickHdl)); + m_xSizeZoomed->connect_toggled(LINK(this, SmPrintOptionsTabPage, SizeButtonClickHdl)); + + Reset(&rOptions); +} + +SmPrintOptionsTabPage::~SmPrintOptionsTabPage() +{ +} + +bool SmPrintOptionsTabPage::FillItemSet(SfxItemSet* rSet) +{ + sal_uInt16 nPrintSize; + if (m_xSizeNormal->get_active()) + nPrintSize = PRINT_SIZE_NORMAL; + else if (m_xSizeScaled->get_active()) + nPrintSize = PRINT_SIZE_SCALED; + else + nPrintSize = PRINT_SIZE_ZOOMED; + + rSet->Put(SfxUInt16Item(GetWhich(SID_PRINTSIZE), nPrintSize)); + rSet->Put(SfxUInt16Item(GetWhich(SID_PRINTZOOM), sal::static_int_cast<sal_uInt16>(m_xZoom->get_value(FieldUnit::PERCENT)))); + rSet->Put(SfxBoolItem(GetWhich(SID_PRINTTITLE), m_xTitle->get_active())); + rSet->Put(SfxBoolItem(GetWhich(SID_PRINTTEXT), m_xText->get_active())); + rSet->Put(SfxBoolItem(GetWhich(SID_PRINTFRAME), m_xFrame->get_active())); + rSet->Put(SfxBoolItem(GetWhich(SID_NO_RIGHT_SPACES), m_xNoRightSpaces->get_active())); + rSet->Put(SfxBoolItem(GetWhich(SID_SAVE_ONLY_USED_SYMBOLS), m_xSaveOnlyUsedSymbols->get_active())); + rSet->Put(SfxBoolItem(GetWhich(SID_AUTO_CLOSE_BRACKETS), m_xAutoCloseBrackets->get_active())); + + return true; +} + +void SmPrintOptionsTabPage::Reset(const SfxItemSet* rSet) +{ + SmPrintSize ePrintSize = static_cast<SmPrintSize>(static_cast<const SfxUInt16Item &>(rSet->Get(GetWhich(SID_PRINTSIZE))).GetValue()); + + m_xSizeNormal->set_active(ePrintSize == PRINT_SIZE_NORMAL); + m_xSizeScaled->set_active(ePrintSize == PRINT_SIZE_SCALED); + m_xSizeZoomed->set_active(ePrintSize == PRINT_SIZE_ZOOMED); + + m_xZoom->set_sensitive(m_xSizeZoomed->get_active()); + + m_xZoom->set_value(static_cast<const SfxUInt16Item &>(rSet->Get(GetWhich(SID_PRINTZOOM))).GetValue(), FieldUnit::PERCENT); + + m_xTitle->set_active(static_cast<const SfxBoolItem &>(rSet->Get(GetWhich(SID_PRINTTITLE))).GetValue()); + m_xNoRightSpaces->set_active(static_cast<const SfxBoolItem &>(rSet->Get(GetWhich(SID_NO_RIGHT_SPACES))).GetValue()); + m_xSaveOnlyUsedSymbols->set_active(static_cast<const SfxBoolItem &>(rSet->Get(GetWhich(SID_SAVE_ONLY_USED_SYMBOLS))).GetValue()); + m_xAutoCloseBrackets->set_active(static_cast<const SfxBoolItem &>(rSet->Get(GetWhich(SID_AUTO_CLOSE_BRACKETS))).GetValue()); +} + +std::unique_ptr<SfxTabPage> SmPrintOptionsTabPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet) +{ + return std::make_unique<SmPrintOptionsTabPage>(pPage, pController, rSet); +} + +void SmShowFont::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/) +{ + Color aBackColor; + Color aTextColor; + lclGetSettingColors(aBackColor, aTextColor); + + rRenderContext.SetBackground(Wallpaper(aBackColor)); + + vcl::Font aFont(maFont); + aFont.SetFontSize(Size(0, 24 * rRenderContext.GetDPIScaleFactor())); + aFont.SetAlignment(ALIGN_TOP); + rRenderContext.SetFont(aFont); + rRenderContext.SetTextColor(aTextColor); + + OUString sText(rRenderContext.GetFont().GetFamilyName()); + Size aTextSize(rRenderContext.GetTextWidth(sText), rRenderContext.GetTextHeight()); + + rRenderContext.DrawText(Point((rRenderContext.GetOutputSize().Width() - aTextSize.Width()) / 2, + (rRenderContext.GetOutputSize().Height() - aTextSize.Height()) / 2), sText); +} + +void SmShowFont::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + Size aSize(pDrawingArea->get_ref_device().LogicToPixel(Size(111 , 31), MapMode(MapUnit::MapAppFont))); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); +} + +void SmShowFont::SetFont(const vcl::Font& rFont) +{ + maFont = rFont; + Invalidate(); +} + +IMPL_LINK( SmFontDialog, FontSelectHdl, weld::ComboBox&, rComboBox, void ) +{ + maFont.SetFamilyName(rComboBox.get_active_text()); + m_aShowFont.SetFont(maFont); +} + +IMPL_LINK_NOARG(SmFontDialog, AttrChangeHdl, weld::ToggleButton&, void) +{ + if (m_xBoldCheckBox->get_active()) + maFont.SetWeight(WEIGHT_BOLD); + else + maFont.SetWeight(WEIGHT_NORMAL); + + if (m_xItalicCheckBox->get_active()) + maFont.SetItalic(ITALIC_NORMAL); + else + maFont.SetItalic(ITALIC_NONE); + + m_aShowFont.SetFont(maFont); +} + +void SmFontDialog::SetFont(const vcl::Font &rFont) +{ + maFont = rFont; + + m_xFontBox->set_active_text(maFont.GetFamilyName()); + m_xBoldCheckBox->set_active(IsBold(maFont)); + m_xItalicCheckBox->set_active(IsItalic(maFont)); + m_aShowFont.SetFont(maFont); +} + +SmFontDialog::SmFontDialog(weld::Window * pParent, OutputDevice *pFntListDevice, bool bHideCheckboxes) + : GenericDialogController(pParent, "modules/smath/ui/fontdialog.ui", "FontDialog") + , m_xFontBox(m_xBuilder->weld_entry_tree_view("fontgrid", "font", "fonts")) + , m_xAttrFrame(m_xBuilder->weld_widget("attrframe")) + , m_xBoldCheckBox(m_xBuilder->weld_check_button("bold")) + , m_xItalicCheckBox(m_xBuilder->weld_check_button("italic")) + , m_xShowFont(new weld::CustomWeld(*m_xBuilder, "preview", m_aShowFont)) +{ + m_xFontBox->set_height_request_by_rows(8); + + { + weld::WaitObject aWait(pParent); + + FontList aFontList( pFntListDevice ); + + sal_uInt16 nCount = aFontList.GetFontNameCount(); + for (sal_uInt16 i = 0; i < nCount; ++i) + { + m_xFontBox->append_text(aFontList.GetFontName(i).GetFamilyName()); + } + maFont.SetFontSize(Size(0, 24)); + maFont.SetWeight(WEIGHT_NORMAL); + maFont.SetItalic(ITALIC_NONE); + maFont.SetFamily(FAMILY_DONTKNOW); + maFont.SetPitch(PITCH_DONTKNOW); + maFont.SetCharSet(RTL_TEXTENCODING_DONTKNOW); + maFont.SetTransparent(true); + } + + m_xFontBox->connect_changed(LINK(this, SmFontDialog, FontSelectHdl)); + m_xBoldCheckBox->connect_toggled(LINK(this, SmFontDialog, AttrChangeHdl)); + m_xItalicCheckBox->connect_toggled(LINK(this, SmFontDialog, AttrChangeHdl)); + + if (bHideCheckboxes) + { + m_xBoldCheckBox->set_active(false); + m_xBoldCheckBox->set_sensitive(false); + m_xItalicCheckBox->set_active(false); + m_xItalicCheckBox->set_sensitive(false); + m_xAttrFrame->hide(); + } +} + +SmFontDialog::~SmFontDialog() +{ +} + +namespace { + +class SaveDefaultsQuery : public weld::MessageDialogController +{ +public: + explicit SaveDefaultsQuery(weld::Widget* pParent) + : MessageDialogController(pParent, "modules/smath/ui/savedefaultsdialog.ui", + "SaveDefaultsDialog") + { + } +}; + +} + +IMPL_LINK_NOARG( SmFontSizeDialog, DefaultButtonClickHdl, weld::Button&, void ) +{ + SaveDefaultsQuery aQuery(m_xDialog.get()); + if (aQuery.run() == RET_YES) + { + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt ); + } +} + +SmFontSizeDialog::SmFontSizeDialog(weld::Window* pParent) + : GenericDialogController(pParent, "modules/smath/ui/fontsizedialog.ui", "FontSizeDialog") + , m_xBaseSize(m_xBuilder->weld_metric_spin_button("spinB_baseSize", FieldUnit::POINT)) + , m_xTextSize(m_xBuilder->weld_metric_spin_button("spinB_text", FieldUnit::PERCENT)) + , m_xIndexSize(m_xBuilder->weld_metric_spin_button("spinB_index", FieldUnit::PERCENT)) + , m_xFunctionSize(m_xBuilder->weld_metric_spin_button("spinB_function", FieldUnit::PERCENT)) + , m_xOperatorSize(m_xBuilder->weld_metric_spin_button("spinB_operator", FieldUnit::PERCENT)) + , m_xBorderSize(m_xBuilder->weld_metric_spin_button("spinB_limit", FieldUnit::PERCENT)) + , m_xDefaultButton(m_xBuilder->weld_button("default")) +{ + m_xDefaultButton->connect_clicked(LINK(this, SmFontSizeDialog, DefaultButtonClickHdl)); +} + +SmFontSizeDialog::~SmFontSizeDialog() +{ +} + +void SmFontSizeDialog::ReadFrom(const SmFormat &rFormat) +{ + //! watch out: round properly! + m_xBaseSize->set_value( SmRoundFraction( + Sm100th_mmToPts( rFormat.GetBaseSize().Height() ) ), FieldUnit::NONE ); + + m_xTextSize->set_value( rFormat.GetRelSize(SIZ_TEXT), FieldUnit::NONE ); + m_xIndexSize->set_value( rFormat.GetRelSize(SIZ_INDEX), FieldUnit::NONE ); + m_xFunctionSize->set_value( rFormat.GetRelSize(SIZ_FUNCTION), FieldUnit::NONE ); + m_xOperatorSize->set_value( rFormat.GetRelSize(SIZ_OPERATOR), FieldUnit::NONE ); + m_xBorderSize->set_value( rFormat.GetRelSize(SIZ_LIMITS), FieldUnit::NONE ); +} + +void SmFontSizeDialog::WriteTo(SmFormat &rFormat) const +{ + rFormat.SetBaseSize( Size(0, SmPtsTo100th_mm( static_cast< long >(m_xBaseSize->get_value(FieldUnit::NONE)))) ); + + rFormat.SetRelSize(SIZ_TEXT, sal::static_int_cast<sal_uInt16>(m_xTextSize->get_value(FieldUnit::NONE))); + rFormat.SetRelSize(SIZ_INDEX, sal::static_int_cast<sal_uInt16>(m_xIndexSize->get_value(FieldUnit::NONE))); + rFormat.SetRelSize(SIZ_FUNCTION, sal::static_int_cast<sal_uInt16>(m_xFunctionSize->get_value(FieldUnit::NONE))); + rFormat.SetRelSize(SIZ_OPERATOR, sal::static_int_cast<sal_uInt16>(m_xOperatorSize->get_value(FieldUnit::NONE))); + rFormat.SetRelSize(SIZ_LIMITS, sal::static_int_cast<sal_uInt16>(m_xBorderSize->get_value(FieldUnit::NONE))); + + const Size aTmp (rFormat.GetBaseSize()); + for (sal_uInt16 i = FNT_BEGIN; i <= FNT_END; i++) + rFormat.SetFontSize(i, aTmp); + + rFormat.RequestApplyChanges(); +} + +IMPL_LINK(SmFontTypeDialog, MenuSelectHdl, const OString&, rIdent, void) +{ + SmFontPickListBox *pActiveListBox; + + bool bHideCheckboxes = false; + if (rIdent == "variables") + pActiveListBox = m_xVariableFont.get(); + else if (rIdent == "functions") + pActiveListBox = m_xFunctionFont.get(); + else if (rIdent == "numbers") + pActiveListBox = m_xNumberFont.get(); + else if (rIdent == "text") + pActiveListBox = m_xTextFont.get(); + else if (rIdent == "serif") + { + pActiveListBox = m_xSerifFont.get(); + bHideCheckboxes = true; + } + else if (rIdent == "sansserif") + { + pActiveListBox = m_xSansFont.get(); + bHideCheckboxes = true; + } + else if (rIdent == "fixedwidth") + { + pActiveListBox = m_xFixedFont.get(); + bHideCheckboxes = true; + } + else + pActiveListBox = nullptr; + + if (pActiveListBox) + { + SmFontDialog aFontDialog(m_xDialog.get(), pFontListDev, bHideCheckboxes); + + pActiveListBox->WriteTo(aFontDialog); + if (aFontDialog.run() == RET_OK) + pActiveListBox->ReadFrom(aFontDialog); + } +} + +IMPL_LINK_NOARG(SmFontTypeDialog, DefaultButtonClickHdl, weld::Button&, void) +{ + SaveDefaultsQuery aQuery(m_xDialog.get()); + if (aQuery.run() == RET_YES) + { + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt, true ); + } +} + +SmFontTypeDialog::SmFontTypeDialog(weld::Window* pParent, OutputDevice *pFntListDevice) + : GenericDialogController(pParent, "modules/smath/ui/fonttypedialog.ui", "FontsDialog") + , pFontListDev(pFntListDevice) + , m_xVariableFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("variableCB"))) + , m_xFunctionFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("functionCB"))) + , m_xNumberFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("numberCB"))) + , m_xTextFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("textCB"))) + , m_xSerifFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("serifCB"))) + , m_xSansFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("sansCB"))) + , m_xFixedFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("fixedCB"))) + , m_xMenuButton(m_xBuilder->weld_menu_button("modify")) + , m_xDefaultButton(m_xBuilder->weld_button("default")) +{ + m_xDefaultButton->connect_clicked(LINK(this, SmFontTypeDialog, DefaultButtonClickHdl)); + m_xMenuButton->connect_selected(LINK(this, SmFontTypeDialog, MenuSelectHdl)); +} + +SmFontTypeDialog::~SmFontTypeDialog() +{ +} + +void SmFontTypeDialog::ReadFrom(const SmFormat &rFormat) +{ + SmModule *pp = SM_MOD(); + + *m_xVariableFont = pp->GetConfig()->GetFontPickList(FNT_VARIABLE); + *m_xFunctionFont = pp->GetConfig()->GetFontPickList(FNT_FUNCTION); + *m_xNumberFont = pp->GetConfig()->GetFontPickList(FNT_NUMBER); + *m_xTextFont = pp->GetConfig()->GetFontPickList(FNT_TEXT); + *m_xSerifFont = pp->GetConfig()->GetFontPickList(FNT_SERIF); + *m_xSansFont = pp->GetConfig()->GetFontPickList(FNT_SANS); + *m_xFixedFont = pp->GetConfig()->GetFontPickList(FNT_FIXED); + + m_xVariableFont->Insert( rFormat.GetFont(FNT_VARIABLE) ); + m_xFunctionFont->Insert( rFormat.GetFont(FNT_FUNCTION) ); + m_xNumberFont->Insert( rFormat.GetFont(FNT_NUMBER) ); + m_xTextFont->Insert( rFormat.GetFont(FNT_TEXT) ); + m_xSerifFont->Insert( rFormat.GetFont(FNT_SERIF) ); + m_xSansFont->Insert( rFormat.GetFont(FNT_SANS) ); + m_xFixedFont->Insert( rFormat.GetFont(FNT_FIXED) ); +} + + +void SmFontTypeDialog::WriteTo(SmFormat &rFormat) const +{ + SmModule *pp = SM_MOD(); + + pp->GetConfig()->GetFontPickList(FNT_VARIABLE) = *m_xVariableFont; + pp->GetConfig()->GetFontPickList(FNT_FUNCTION) = *m_xFunctionFont; + pp->GetConfig()->GetFontPickList(FNT_NUMBER) = *m_xNumberFont; + pp->GetConfig()->GetFontPickList(FNT_TEXT) = *m_xTextFont; + pp->GetConfig()->GetFontPickList(FNT_SERIF) = *m_xSerifFont; + pp->GetConfig()->GetFontPickList(FNT_SANS) = *m_xSansFont; + pp->GetConfig()->GetFontPickList(FNT_FIXED) = *m_xFixedFont; + + rFormat.SetFont( FNT_VARIABLE, m_xVariableFont->Get() ); + rFormat.SetFont( FNT_FUNCTION, m_xFunctionFont->Get() ); + rFormat.SetFont( FNT_NUMBER, m_xNumberFont->Get() ); + rFormat.SetFont( FNT_TEXT, m_xTextFont->Get() ); + rFormat.SetFont( FNT_SERIF, m_xSerifFont->Get() ); + rFormat.SetFont( FNT_SANS, m_xSansFont->Get() ); + rFormat.SetFont( FNT_FIXED, m_xFixedFont->Get() ); + + rFormat.RequestApplyChanges(); +} + +/**************************************************************************/ + +namespace { + +struct FieldMinMax +{ + sal_uInt16 nMin, nMax; +}; + +} + +// Data for min and max values of the 4 metric fields +// for each of the 10 categories +static const FieldMinMax pMinMaxData[10][4] = +{ + // 0 + {{ 0, 200 }, { 0, 200 }, { 0, 100 }, { 0, 0 }}, + // 1 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 2 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 3 + {{ 0, 100 }, { 1, 100 }, { 0, 0 }, { 0, 0 }}, + // 4 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 5 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 100 }}, + // 6 + {{ 0, 300 }, { 0, 300 }, { 0, 0 }, { 0, 0 }}, + // 7 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 8 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 9 + {{ 0, 10000 }, { 0, 10000 }, { 0, 10000 }, { 0, 10000 }} +}; + +SmCategoryDesc::SmCategoryDesc(weld::Builder& rBuilder, sal_uInt16 nCategoryIdx) +{ + ++nCategoryIdx; + std::unique_ptr<weld::Label> xTitle(rBuilder.weld_label(OString::number(nCategoryIdx)+"title")); + if (xTitle) + { + Name = xTitle->get_label(); + } + for (int i = 0; i < 4; ++i) + { + std::unique_ptr<weld::Label> xLabel(rBuilder.weld_label(OString::number(nCategoryIdx)+"label"+OString::number(i+1))); + + if (xLabel) + { + Strings[i] = xLabel->get_label(); + Graphics[i] = rBuilder.weld_widget(OString::number(nCategoryIdx)+"image"+OString::number(i+1)); + } + else + { + Strings[i].clear(); + Graphics[i].reset(); + } + + const FieldMinMax& rMinMax = pMinMaxData[ nCategoryIdx-1 ][i]; + Value[i] = Minimum[i] = rMinMax.nMin; + Maximum[i] = rMinMax.nMax; + } +} + +SmCategoryDesc::~SmCategoryDesc() +{ +} + +/**************************************************************************/ + +IMPL_LINK( SmDistanceDialog, GetFocusHdl, weld::Widget&, rControl, void ) +{ + if (!m_xCategories[nActiveCategory]) + return; + + sal_uInt16 i; + + if (&rControl == &m_xMetricField1->get_widget()) + i = 0; + else if (&rControl == &m_xMetricField2->get_widget()) + i = 1; + else if (&rControl == &m_xMetricField3->get_widget()) + i = 2; + else if (&rControl == &m_xMetricField4->get_widget()) + i = 3; + else + return; + if (m_pCurrentImage) + m_pCurrentImage->hide(); + m_pCurrentImage = m_xCategories[nActiveCategory]->GetGraphic(i); + m_pCurrentImage->show(); +} + +IMPL_LINK(SmDistanceDialog, MenuSelectHdl, const OString&, rId, void) +{ + assert(rId.startsWith("menuitem")); + SetCategory(rId.replaceFirst("menuitem", "").toInt32() - 1); +} + +IMPL_LINK_NOARG( SmDistanceDialog, DefaultButtonClickHdl, weld::Button&, void ) +{ + SaveDefaultsQuery aQuery(m_xDialog.get()); + if (aQuery.run() == RET_YES) + { + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt ); + } +} + +IMPL_LINK( SmDistanceDialog, CheckBoxClickHdl, weld::ToggleButton&, rCheckBox, void ) +{ + if (&rCheckBox == m_xCheckBox1.get()) + { + bool bChecked = m_xCheckBox1->get_active(); + m_xFixedText4->set_sensitive( bChecked ); + m_xMetricField4->set_sensitive( bChecked ); + } +} + +void SmDistanceDialog::SetCategory(sal_uInt16 nCategory) +{ + assert(nCategory < NOCATEGORIES && "Sm: wrong category number in SmDistanceDialog"); + + // array to convert category- and metricfield-number in help ids. + // 0 is used in case of unused combinations. + assert(NOCATEGORIES == 10 && "Sm : array doesn't fit into the number of categories"); + static const char * aCatMf2Hid[10][4] = + { + { HID_SMA_DEFAULT_DIST, HID_SMA_LINE_DIST, HID_SMA_ROOT_DIST, nullptr }, + { HID_SMA_SUP_DIST, HID_SMA_SUB_DIST , nullptr, nullptr }, + { HID_SMA_NUMERATOR_DIST, HID_SMA_DENOMINATOR_DIST, nullptr, nullptr }, + { HID_SMA_FRACLINE_EXCWIDTH, HID_SMA_FRACLINE_LINEWIDTH, nullptr, nullptr }, + { HID_SMA_UPPERLIMIT_DIST, HID_SMA_LOWERLIMIT_DIST, nullptr, nullptr }, + { HID_SMA_BRACKET_EXCHEIGHT, HID_SMA_BRACKET_DIST, nullptr, HID_SMA_BRACKET_EXCHEIGHT2 }, + { HID_SMA_MATRIXROW_DIST, HID_SMA_MATRIXCOL_DIST, nullptr, nullptr }, + { HID_SMA_ATTRIBUT_DIST, HID_SMA_INTERATTRIBUT_DIST, nullptr, nullptr }, + { HID_SMA_OPERATOR_EXCHEIGHT, HID_SMA_OPERATOR_DIST, nullptr, nullptr }, + { HID_SMA_LEFTBORDER_DIST, HID_SMA_RIGHTBORDER_DIST, HID_SMA_UPPERBORDER_DIST, HID_SMA_LOWERBORDER_DIST } + }; + + // array to help iterate over the controls + std::pair<weld::Label*, weld::MetricSpinButton*> const aWin[4] = + { + { m_xFixedText1.get(), m_xMetricField1.get() }, + { m_xFixedText2.get(), m_xMetricField2.get() }, + { m_xFixedText3.get(), m_xMetricField3.get() }, + { m_xFixedText4.get(), m_xMetricField4.get() } + }; + + SmCategoryDesc *pCat; + + // remember the (maybe new) settings of the active SmCategoryDesc + // before switching to the new one + if (nActiveCategory != CATEGORY_NONE) + { + pCat = m_xCategories[nActiveCategory].get(); + pCat->SetValue(0, sal::static_int_cast<sal_uInt16>(m_xMetricField1->get_value(FieldUnit::NONE))); + pCat->SetValue(1, sal::static_int_cast<sal_uInt16>(m_xMetricField2->get_value(FieldUnit::NONE))); + pCat->SetValue(2, sal::static_int_cast<sal_uInt16>(m_xMetricField3->get_value(FieldUnit::NONE))); + pCat->SetValue(3, sal::static_int_cast<sal_uInt16>(m_xMetricField4->get_value(FieldUnit::NONE))); + + if (nActiveCategory == 5) + bScaleAllBrackets = m_xCheckBox1->get_active(); + + m_xMenuButton->set_item_active("menuitem" + OString::number(nActiveCategory + 1), false); + } + + // activation/deactivation of the associated controls depending on the chosen category + bool bActive; + for (sal_uInt16 i = 0; i < 4; i++) + { + weld::Label *pFT = aWin[i].first; + weld::MetricSpinButton *pMF = aWin[i].second; + + // To determine which Controls should be active, the existence + // of an associated HelpID is checked + bActive = aCatMf2Hid[nCategory][i] != nullptr; + + pFT->set_visible(bActive); + pFT->set_sensitive(bActive); + pMF->set_visible(bActive); + pMF->set_sensitive(bActive); + + // set measurement unit and number of decimal places + FieldUnit eUnit; + sal_uInt16 nDigits; + if (nCategory < 9) + { + eUnit = FieldUnit::PERCENT; + nDigits = 0; + } + else + { + eUnit = FieldUnit::MM_100TH; + nDigits = 2; + } + pMF->set_unit(eUnit); // changes the value + pMF->set_digits(nDigits); + + if (bActive) + { + pCat = m_xCategories[nCategory].get(); + pFT->set_label(pCat->GetString(i)); + + pMF->set_range(pCat->GetMinimum(i), pCat->GetMaximum(i), FieldUnit::NONE); + pMF->set_value(pCat->GetValue(i), FieldUnit::NONE); + + pMF->set_help_id(aCatMf2Hid[nCategory][i]); + } + } + // activate the CheckBox and the associated MetricField if we're dealing with the brackets menu + bActive = nCategory == 5; + m_xCheckBox1->set_visible(bActive); + m_xCheckBox1->set_sensitive(bActive); + if (bActive) + { + m_xCheckBox1->set_active(bScaleAllBrackets); + + bool bChecked = m_xCheckBox1->get_active(); + m_xFixedText4->set_sensitive( bChecked ); + m_xMetricField4->set_sensitive( bChecked ); + } + + m_xMenuButton->set_item_active("menuitem" + OString::number(nCategory + 1), true); + m_xFrame->set_label(m_xCategories[nCategory]->GetName()); + + nActiveCategory = nCategory; + + m_xMetricField1->grab_focus(); +} + +SmDistanceDialog::SmDistanceDialog(weld::Window *pParent) + : GenericDialogController(pParent, "modules/smath/ui/spacingdialog.ui", "SpacingDialog") + , m_xFrame(m_xBuilder->weld_frame("template")) + , m_xFixedText1(m_xBuilder->weld_label("label1")) + , m_xMetricField1(m_xBuilder->weld_metric_spin_button("spinbutton1", FieldUnit::CM)) + , m_xFixedText2(m_xBuilder->weld_label("label2")) + , m_xMetricField2(m_xBuilder->weld_metric_spin_button("spinbutton2", FieldUnit::CM)) + , m_xFixedText3(m_xBuilder->weld_label("label3")) + , m_xMetricField3(m_xBuilder->weld_metric_spin_button("spinbutton3", FieldUnit::CM)) + , m_xCheckBox1(m_xBuilder->weld_check_button("checkbutton")) + , m_xFixedText4(m_xBuilder->weld_label("label4")) + , m_xMetricField4(m_xBuilder->weld_metric_spin_button("spinbutton4", FieldUnit::CM)) + , m_xMenuButton(m_xBuilder->weld_menu_button("category")) + , m_xDefaultButton(m_xBuilder->weld_button("default")) + , m_xBitmap(m_xBuilder->weld_widget("image")) + , m_pCurrentImage(m_xBitmap.get()) +{ + for (sal_uInt16 i = 0; i < NOCATEGORIES; ++i) + m_xCategories[i].reset( new SmCategoryDesc(*m_xBuilder, i) ); + nActiveCategory = CATEGORY_NONE; + bScaleAllBrackets = false; + + m_xMetricField1->connect_focus_in(LINK(this, SmDistanceDialog, GetFocusHdl)); + m_xMetricField2->connect_focus_in(LINK(this, SmDistanceDialog, GetFocusHdl)); + m_xMetricField3->connect_focus_in(LINK(this, SmDistanceDialog, GetFocusHdl)); + m_xMetricField4->connect_focus_in(LINK(this, SmDistanceDialog, GetFocusHdl)); + m_xCheckBox1->connect_toggled(LINK(this, SmDistanceDialog, CheckBoxClickHdl)); + m_xMenuButton->connect_selected(LINK(this, SmDistanceDialog, MenuSelectHdl)); + m_xDefaultButton->connect_clicked(LINK(this, SmDistanceDialog, DefaultButtonClickHdl)); + + //set the initial size, with max visible widgets visible, as preferred size + m_xDialog->set_size_request(-1, m_xDialog->get_preferred_size().Height()); +} + +SmDistanceDialog::~SmDistanceDialog() +{ +} + +void SmDistanceDialog::ReadFrom(const SmFormat &rFormat) +{ + m_xCategories[0]->SetValue(0, rFormat.GetDistance(DIS_HORIZONTAL)); + m_xCategories[0]->SetValue(1, rFormat.GetDistance(DIS_VERTICAL)); + m_xCategories[0]->SetValue(2, rFormat.GetDistance(DIS_ROOT)); + m_xCategories[1]->SetValue(0, rFormat.GetDistance(DIS_SUPERSCRIPT)); + m_xCategories[1]->SetValue(1, rFormat.GetDistance(DIS_SUBSCRIPT)); + m_xCategories[2]->SetValue(0, rFormat.GetDistance(DIS_NUMERATOR)); + m_xCategories[2]->SetValue(1, rFormat.GetDistance(DIS_DENOMINATOR)); + m_xCategories[3]->SetValue(0, rFormat.GetDistance(DIS_FRACTION)); + m_xCategories[3]->SetValue(1, rFormat.GetDistance(DIS_STROKEWIDTH)); + m_xCategories[4]->SetValue(0, rFormat.GetDistance(DIS_UPPERLIMIT)); + m_xCategories[4]->SetValue(1, rFormat.GetDistance(DIS_LOWERLIMIT)); + m_xCategories[5]->SetValue(0, rFormat.GetDistance(DIS_BRACKETSIZE)); + m_xCategories[5]->SetValue(1, rFormat.GetDistance(DIS_BRACKETSPACE)); + m_xCategories[5]->SetValue(3, rFormat.GetDistance(DIS_NORMALBRACKETSIZE)); + m_xCategories[6]->SetValue(0, rFormat.GetDistance(DIS_MATRIXROW)); + m_xCategories[6]->SetValue(1, rFormat.GetDistance(DIS_MATRIXCOL)); + m_xCategories[7]->SetValue(0, rFormat.GetDistance(DIS_ORNAMENTSIZE)); + m_xCategories[7]->SetValue(1, rFormat.GetDistance(DIS_ORNAMENTSPACE)); + m_xCategories[8]->SetValue(0, rFormat.GetDistance(DIS_OPERATORSIZE)); + m_xCategories[8]->SetValue(1, rFormat.GetDistance(DIS_OPERATORSPACE)); + m_xCategories[9]->SetValue(0, rFormat.GetDistance(DIS_LEFTSPACE)); + m_xCategories[9]->SetValue(1, rFormat.GetDistance(DIS_RIGHTSPACE)); + m_xCategories[9]->SetValue(2, rFormat.GetDistance(DIS_TOPSPACE)); + m_xCategories[9]->SetValue(3, rFormat.GetDistance(DIS_BOTTOMSPACE)); + + bScaleAllBrackets = rFormat.IsScaleNormalBrackets(); + + // force update (even of category 0) by setting nActiveCategory to a + // non-existent category number + nActiveCategory = CATEGORY_NONE; + SetCategory(0); +} + + +void SmDistanceDialog::WriteTo(SmFormat &rFormat) /*const*/ +{ + // TODO can they actually be different? + // if that's not the case 'const' could be used above! + SetCategory(nActiveCategory); + + rFormat.SetDistance( DIS_HORIZONTAL, m_xCategories[0]->GetValue(0) ); + rFormat.SetDistance( DIS_VERTICAL, m_xCategories[0]->GetValue(1) ); + rFormat.SetDistance( DIS_ROOT, m_xCategories[0]->GetValue(2) ); + rFormat.SetDistance( DIS_SUPERSCRIPT, m_xCategories[1]->GetValue(0) ); + rFormat.SetDistance( DIS_SUBSCRIPT, m_xCategories[1]->GetValue(1) ); + rFormat.SetDistance( DIS_NUMERATOR, m_xCategories[2]->GetValue(0) ); + rFormat.SetDistance( DIS_DENOMINATOR, m_xCategories[2]->GetValue(1) ); + rFormat.SetDistance( DIS_FRACTION, m_xCategories[3]->GetValue(0) ); + rFormat.SetDistance( DIS_STROKEWIDTH, m_xCategories[3]->GetValue(1) ); + rFormat.SetDistance( DIS_UPPERLIMIT, m_xCategories[4]->GetValue(0) ); + rFormat.SetDistance( DIS_LOWERLIMIT, m_xCategories[4]->GetValue(1) ); + rFormat.SetDistance( DIS_BRACKETSIZE, m_xCategories[5]->GetValue(0) ); + rFormat.SetDistance( DIS_BRACKETSPACE, m_xCategories[5]->GetValue(1) ); + rFormat.SetDistance( DIS_MATRIXROW, m_xCategories[6]->GetValue(0) ); + rFormat.SetDistance( DIS_MATRIXCOL, m_xCategories[6]->GetValue(1) ); + rFormat.SetDistance( DIS_ORNAMENTSIZE, m_xCategories[7]->GetValue(0) ); + rFormat.SetDistance( DIS_ORNAMENTSPACE, m_xCategories[7]->GetValue(1) ); + rFormat.SetDistance( DIS_OPERATORSIZE, m_xCategories[8]->GetValue(0) ); + rFormat.SetDistance( DIS_OPERATORSPACE, m_xCategories[8]->GetValue(1) ); + rFormat.SetDistance( DIS_LEFTSPACE, m_xCategories[9]->GetValue(0) ); + rFormat.SetDistance( DIS_RIGHTSPACE, m_xCategories[9]->GetValue(1) ); + rFormat.SetDistance( DIS_TOPSPACE, m_xCategories[9]->GetValue(2) ); + rFormat.SetDistance( DIS_BOTTOMSPACE, m_xCategories[9]->GetValue(3) ); + rFormat.SetDistance( DIS_NORMALBRACKETSIZE, m_xCategories[5]->GetValue(3) ); + + rFormat.SetScaleNormalBrackets( bScaleAllBrackets ); + + rFormat.RequestApplyChanges(); +} + +IMPL_LINK_NOARG( SmAlignDialog, DefaultButtonClickHdl, weld::Button&, void ) +{ + SaveDefaultsQuery aQuery(m_xDialog.get()); + if (aQuery.run() == RET_YES) + { + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt ); + } +} + +SmAlignDialog::SmAlignDialog(weld::Window* pParent) + : GenericDialogController(pParent, "modules/smath/ui/alignmentdialog.ui", "AlignmentDialog") + , m_xLeft(m_xBuilder->weld_radio_button("left")) + , m_xCenter(m_xBuilder->weld_radio_button("center")) + , m_xRight(m_xBuilder->weld_radio_button("right")) + , m_xDefaultButton(m_xBuilder->weld_button("default")) +{ + m_xDefaultButton->connect_clicked(LINK(this, SmAlignDialog, DefaultButtonClickHdl)); +} + +SmAlignDialog::~SmAlignDialog() +{ +} + +void SmAlignDialog::ReadFrom(const SmFormat &rFormat) +{ + switch (rFormat.GetHorAlign()) + { + case SmHorAlign::Left: + m_xLeft->set_active(true); + break; + case SmHorAlign::Center: + m_xCenter->set_active(true); + break; + case SmHorAlign::Right: + m_xRight->set_active(true); + break; + } +} + +void SmAlignDialog::WriteTo(SmFormat &rFormat) const +{ + if (m_xLeft->get_active()) + rFormat.SetHorAlign(SmHorAlign::Left); + else if (m_xRight->get_active()) + rFormat.SetHorAlign(SmHorAlign::Right); + else + rFormat.SetHorAlign(SmHorAlign::Center); + + rFormat.RequestApplyChanges(); +} + +SmShowSymbolSet::SmShowSymbolSet(std::unique_ptr<weld::ScrolledWindow> pScrolledWindow) + : nLen(0) + , nRows(0) + , nColumns(0) + , nXOffset(0) + , nYOffset(0) + , nSelectSymbol(SYMBOL_NONE) + , m_xScrolledWindow(std::move(pScrolledWindow)) +{ + m_xScrolledWindow->set_user_managed_scrolling(); + m_xScrolledWindow->connect_vadjustment_changed(LINK(this, SmShowSymbolSet, ScrollHdl)); +} + +Point SmShowSymbolSet::OffsetPoint(const Point &rPoint) const +{ + return Point(rPoint.X() + nXOffset, rPoint.Y() + nYOffset); +} + +void SmShowSymbolSet::Resize() +{ + CustomWidgetController::Resize(); + Size aWinSize(GetOutputSizePixel()); + if (aWinSize != m_aOldSize) + { + calccols(GetDrawingArea()->get_ref_device()); + m_aOldSize = aWinSize; + } +} + +void SmShowSymbolSet::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + Color aBackgroundColor; + Color aTextColor; + lclGetSettingColors(aBackgroundColor, aTextColor); + + rRenderContext.SetBackground(Wallpaper(aBackgroundColor)); + rRenderContext.SetTextColor(aTextColor); + + rRenderContext.Push(PushFlags::MAPMODE); + + // set MapUnit for which 'nLen' has been calculated + rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel)); + + sal_uInt16 v = sal::static_int_cast< sal_uInt16 >(m_xScrolledWindow->vadjustment_get_value() * nColumns); + size_t nSymbols = aSymbolSet.size(); + + Color aTxtColor(rRenderContext.GetTextColor()); + for (size_t i = v; i < nSymbols ; i++) + { + SmSym aSymbol(*aSymbolSet[i]); + vcl::Font aFont(aSymbol.GetFace()); + aFont.SetAlignment(ALIGN_TOP); + + // taking a FontSize which is a bit smaller (compared to nLen) in order to have a buffer + // (hopefully enough for left and right, too) + aFont.SetFontSize(Size(0, nLen - (nLen / 3))); + rRenderContext.SetFont(aFont); + // keep text color + rRenderContext.SetTextColor(aTxtColor); + + int nIV = i - v; + sal_UCS4 cChar = aSymbol.GetCharacter(); + OUString aText(&cChar, 1); + Size aSize(rRenderContext.GetTextWidth( aText ), rRenderContext.GetTextHeight()); + + Point aPoint((nIV % nColumns) * nLen + (nLen - aSize.Width()) / 2, + (nIV / nColumns) * nLen + (nLen - aSize.Height()) / 2); + + rRenderContext.DrawText(OffsetPoint(aPoint), aText); + } + + if (nSelectSymbol != SYMBOL_NONE) + { + Point aPoint(((nSelectSymbol - v) % nColumns) * nLen, + ((nSelectSymbol - v) / nColumns) * nLen); + + rRenderContext.Invert(tools::Rectangle(OffsetPoint(aPoint), Size(nLen, nLen))); + + } + + rRenderContext.Pop(); +} + +bool SmShowSymbolSet::MouseButtonDown(const MouseEvent& rMEvt) +{ + GrabFocus(); + + Size aOutputSize(nColumns * nLen, nRows * nLen); + aOutputSize.AdjustWidth(nXOffset ); + aOutputSize.AdjustHeight(nYOffset ); + Point aPoint(rMEvt.GetPosPixel()); + aPoint.AdjustX( -nXOffset ); + aPoint.AdjustY( -nYOffset ); + + if (rMEvt.IsLeft() && tools::Rectangle(Point(0, 0), aOutputSize).IsInside(rMEvt.GetPosPixel())) + { + long nPos = (aPoint.Y() / nLen) * nColumns + (aPoint.X() / nLen) + + m_xScrolledWindow->vadjustment_get_value() * nColumns; + SelectSymbol( sal::static_int_cast< sal_uInt16 >(nPos) ); + + aSelectHdlLink.Call(*this); + + if (rMEvt.GetClicks() > 1) + aDblClickHdlLink.Call(*this); + } + + return true; +} + +bool SmShowSymbolSet::KeyInput(const KeyEvent& rKEvt) +{ + sal_uInt16 n = nSelectSymbol; + + if (n != SYMBOL_NONE) + { + switch (rKEvt.GetKeyCode().GetCode()) + { + case KEY_DOWN: n = n + nColumns; break; + case KEY_UP: n = n - nColumns; break; + case KEY_LEFT: n -= 1; break; + case KEY_RIGHT: n += 1; break; + case KEY_HOME: n = 0; break; + case KEY_END: n = static_cast< sal_uInt16 >(aSymbolSet.size() - 1); break; + case KEY_PAGEUP: n -= nColumns * nRows; break; + case KEY_PAGEDOWN: n += nColumns * nRows; break; + default: + return false; + } + } + else + n = 0; + + if (n >= aSymbolSet.size()) + n = nSelectSymbol; + + // adjust scrollbar + if ((n < sal::static_int_cast<sal_uInt16>(m_xScrolledWindow->vadjustment_get_value() * nColumns)) || + (n >= sal::static_int_cast<sal_uInt16>((m_xScrolledWindow->vadjustment_get_value() + nRows) * nColumns))) + { + m_xScrolledWindow->vadjustment_set_value(n / nColumns); + Invalidate(); + } + + SelectSymbol(n); + aSelectHdlLink.Call(*this); + + return true; +} + +void SmShowSymbolSet::calccols(const vcl::RenderContext& rRenderContext) +{ + // Height of 16pt in pixels (matching 'aOutputSize') + nLen = rRenderContext.LogicToPixel(Size(0, 16), MapMode(MapUnit::MapPoint)).Height(); + + Size aOutputSize(GetOutputSizePixel()); + + nColumns = aOutputSize.Width() / nLen; + nRows = aOutputSize.Height() / nLen; + nColumns = std::max<long>(1, nColumns); + nRows = std::max<long>(1, nRows); + + nXOffset = (aOutputSize.Width() - (nColumns * nLen)) / 2; + nYOffset = (aOutputSize.Height() - (nRows * nLen)) / 2; + + SetScrollBarRange(); +} + +void SmShowSymbolSet::SetSymbolSet(const SymbolPtrVec_t& rSymbolSet) +{ + aSymbolSet = rSymbolSet; + SetScrollBarRange(); + Invalidate(); +} + +void SmShowSymbolSet::SetScrollBarRange() +{ + const int nLastRow = (aSymbolSet.size() - 1 + nColumns) / nColumns; + m_xScrolledWindow->vadjustment_configure(m_xScrolledWindow->vadjustment_get_value(), 0, nLastRow, 1, nRows - 1, nRows); + Invalidate(); +} + +void SmShowSymbolSet::SelectSymbol(sal_uInt16 nSymbol) +{ + int v = m_xScrolledWindow->vadjustment_get_value() * nColumns; + + if (nSelectSymbol != SYMBOL_NONE && nColumns) + { + Point aPoint(OffsetPoint(Point(((nSelectSymbol - v) % nColumns) * nLen, + ((nSelectSymbol - v) / nColumns) * nLen))); + Invalidate(tools::Rectangle(aPoint, Size(nLen, nLen))); + } + + if (nSymbol < aSymbolSet.size()) + nSelectSymbol = nSymbol; + + if (aSymbolSet.empty()) + nSelectSymbol = SYMBOL_NONE; + + if (nSelectSymbol != SYMBOL_NONE && nColumns) + { + Point aPoint(OffsetPoint(Point(((nSelectSymbol - v) % nColumns) * nLen, + ((nSelectSymbol - v) / nColumns) * nLen))); + Invalidate(tools::Rectangle(aPoint, Size(nLen, nLen))); + } + + if (!nColumns) + Invalidate(); +} + +IMPL_LINK_NOARG(SmShowSymbolSet, ScrollHdl, weld::ScrolledWindow&, void) +{ + Invalidate(); +} + +SmShowSymbol::SmShowSymbol() +{ +} + +void SmShowSymbol::setFontSize(vcl::Font &rFont) const +{ + Size aSize(GetOutputSizePixel()); + rFont.SetFontSize(Size(0, aSize.Height() - aSize.Height() / 3)); +} + +void SmShowSymbol::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + Color aBackgroundColor; + Color aTextColor; + lclGetSettingColors(aBackgroundColor, aTextColor); + rRenderContext.SetBackground(Wallpaper(aBackgroundColor)); + rRenderContext.SetTextColor(aTextColor); + rRenderContext.Erase(); + + vcl::Font aFont(GetFont()); + setFontSize(aFont); + rRenderContext.SetFont(aFont); + + const OUString &rText = GetText(); + Size aTextSize(rRenderContext.GetTextWidth(rText), rRenderContext.GetTextHeight()); + + rRenderContext.DrawText(Point((rRenderContext.GetOutputSize().Width() - aTextSize.Width()) / 2, + (rRenderContext.GetOutputSize().Height() * 7 / 10)), rText); +} + +bool SmShowSymbol::MouseButtonDown(const MouseEvent& rMEvt) +{ + if (rMEvt.GetClicks() > 1) + aDblClickHdlLink.Call(*this); + return true; +} + +void SmShowSymbol::SetSymbol(const SmSym *pSymbol) +{ + if (pSymbol) + { + vcl::Font aFont(pSymbol->GetFace()); + aFont.SetAlignment(ALIGN_BASELINE); + SetFont(aFont); + + sal_UCS4 cChar = pSymbol->GetCharacter(); + OUString aText(&cChar, 1); + SetText( aText ); + } + + Invalidate(); +} + +void SmSymbolDialog::FillSymbolSets() + // populate the entries of possible SymbolsSets in the dialog with + // current values of the SymbolSet manager but selects none of those +{ + m_xSymbolSets->clear(); + m_xSymbolSets->set_active(-1); + + std::set< OUString > aSymbolSetNames( rSymbolMgr.GetSymbolSetNames() ); + for (const auto& rSymbolSetName : aSymbolSetNames) + m_xSymbolSets->append_text(rSymbolSetName); +} + +IMPL_LINK_NOARG( SmSymbolDialog, SymbolSetChangeHdl, weld::ComboBox&, void ) +{ + SelectSymbolSet(m_xSymbolSets->get_active_text()); +} + +IMPL_LINK_NOARG( SmSymbolDialog, SymbolChangeHdl, SmShowSymbolSet&, void ) +{ + SelectSymbol(m_xSymbolSetDisplay->GetSelectSymbol()); +} + +IMPL_LINK_NOARG(SmSymbolDialog, EditClickHdl, weld::Button&, void) +{ + SmSymDefineDialog aDialog(m_xDialog.get(), pFontListDev, rSymbolMgr); + + // set current symbol and SymbolSet for the new dialog + const OUString aSymSetName (m_xSymbolSets->get_active_text()), + aSymName (m_xSymbolName->get_label()); + aDialog.SelectOldSymbolSet(aSymSetName); + aDialog.SelectOldSymbol(aSymName); + aDialog.SelectSymbolSet(aSymSetName); + aDialog.SelectSymbol(aSymName); + + // remember old SymbolSet + OUString aOldSymbolSet (m_xSymbolSets->get_active_text()); + + sal_uInt16 nSymPos = m_xSymbolSetDisplay->GetSelectSymbol(); + + // adapt dialog to data of the SymbolSet manager, which might have changed + if (aDialog.run() == RET_OK && rSymbolMgr.IsModified()) + { + rSymbolMgr.Save(); + FillSymbolSets(); + } + + // if the old SymbolSet doesn't exist anymore, go to the first one SymbolSet (if one exists) + if (!SelectSymbolSet(aOldSymbolSet) && m_xSymbolSets->get_count() > 0) + SelectSymbolSet(m_xSymbolSets->get_text(0)); + else + { + // just update display of current symbol set + assert(aSymSetName == aSymSetName); //unexpected change in symbol set name + aSymbolSet = rSymbolMgr.GetSymbolSet( aSymbolSetName ); + m_xSymbolSetDisplay->SetSymbolSet( aSymbolSet ); + } + + if (nSymPos >= aSymbolSet.size()) + nSymPos = static_cast< sal_uInt16 >(aSymbolSet.size()) - 1; + SelectSymbol( nSymPos ); +} + +IMPL_LINK_NOARG( SmSymbolDialog, SymbolDblClickHdl2, SmShowSymbolSet&, void ) +{ + SymbolDblClickHdl(); +} + +IMPL_LINK_NOARG( SmSymbolDialog, SymbolDblClickHdl, SmShowSymbol&, void ) +{ + SymbolDblClickHdl(); +} + +void SmSymbolDialog::SymbolDblClickHdl() +{ + GetClickHdl(*m_xGetBtn); + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(SmSymbolDialog, GetClickHdl, weld::Button&, void) +{ + const SmSym *pSym = GetSymbol(); + if (pSym) + { + OUString aText = "%" + pSym->GetName() + " "; + + rViewSh.GetViewFrame()->GetDispatcher()->ExecuteList( + SID_INSERTSPECIAL, SfxCallMode::RECORD, + { new SfxStringItem(SID_INSERTSPECIAL, aText) }); + } +} + +SmSymbolDialog::SmSymbolDialog(weld::Window *pParent, OutputDevice *pFntListDevice, + SmSymbolManager &rMgr, SmViewShell &rViewShell) + : GenericDialogController(pParent, "modules/smath/ui/catalogdialog.ui", "CatalogDialog") + , rViewSh(rViewShell) + , rSymbolMgr(rMgr) + , pFontListDev(pFntListDevice) + , m_xSymbolSets(m_xBuilder->weld_combo_box("symbolset")) + , m_xSymbolSetDisplay(new SmShowSymbolSet(m_xBuilder->weld_scrolled_window("scrolledwindow"))) + , m_xSymbolSetDisplayArea(new weld::CustomWeld(*m_xBuilder, "symbolsetdisplay", *m_xSymbolSetDisplay)) + , m_xSymbolName(m_xBuilder->weld_label("symbolname")) + , m_xSymbolDisplay(new weld::CustomWeld(*m_xBuilder, "preview", m_aSymbolDisplay)) + , m_xGetBtn(m_xBuilder->weld_button("ok")) + , m_xEditBtn(m_xBuilder->weld_button("edit")) +{ + m_xSymbolSets->make_sorted(); + + aSymbolSetName.clear(); + aSymbolSet.clear(); + FillSymbolSets(); + if (m_xSymbolSets->get_count() > 0) + SelectSymbolSet(m_xSymbolSets->get_text(0)); + + m_xSymbolSets->connect_changed(LINK(this, SmSymbolDialog, SymbolSetChangeHdl)); + m_xSymbolSetDisplay->SetSelectHdl(LINK(this, SmSymbolDialog, SymbolChangeHdl)); + m_xSymbolSetDisplay->SetDblClickHdl(LINK(this, SmSymbolDialog, SymbolDblClickHdl2)); + m_aSymbolDisplay.SetDblClickHdl(LINK(this, SmSymbolDialog, SymbolDblClickHdl)); + m_xEditBtn->connect_clicked(LINK(this, SmSymbolDialog, EditClickHdl)); + m_xGetBtn->connect_clicked(LINK(this, SmSymbolDialog, GetClickHdl)); +} + +SmSymbolDialog::~SmSymbolDialog() +{ +} + +bool SmSymbolDialog::SelectSymbolSet(const OUString &rSymbolSetName) +{ + bool bRet = false; + sal_Int32 nPos = m_xSymbolSets->find_text(rSymbolSetName); + + aSymbolSetName.clear(); + aSymbolSet.clear(); + if (nPos != -1) + { + m_xSymbolSets->set_active(nPos); + + aSymbolSetName = rSymbolSetName; + aSymbolSet = rSymbolMgr.GetSymbolSet( aSymbolSetName ); + + // sort symbols by Unicode position (useful for displaying Greek characters alphabetically) + std::sort( aSymbolSet.begin(), aSymbolSet.end(), + [](const SmSym *pSym1, const SmSym *pSym2) + { + return pSym1->GetCharacter() < pSym2->GetCharacter(); + } ); + + m_xSymbolSetDisplay->SetSymbolSet( aSymbolSet ); + if (!aSymbolSet.empty()) + SelectSymbol(0); + + bRet = true; + } + else + m_xSymbolSets->set_active(-1); + + return bRet; +} + +void SmSymbolDialog::SelectSymbol(sal_uInt16 nSymbolNo) +{ + const SmSym *pSym = nullptr; + if (!aSymbolSetName.isEmpty() && nSymbolNo < static_cast< sal_uInt16 >(aSymbolSet.size())) + pSym = aSymbolSet[ nSymbolNo ]; + + m_xSymbolSetDisplay->SelectSymbol(nSymbolNo); + m_aSymbolDisplay.SetSymbol(pSym); + m_xSymbolName->set_label(pSym ? pSym->GetName() : OUString()); +} + +const SmSym* SmSymbolDialog::GetSymbol() const +{ + sal_uInt16 nSymbolNo = m_xSymbolSetDisplay->GetSelectSymbol(); + bool bValid = !aSymbolSetName.isEmpty() && nSymbolNo < static_cast< sal_uInt16 >(aSymbolSet.size()); + return bValid ? aSymbolSet[ nSymbolNo ] : nullptr; +} + +void SmShowChar::Resize() +{ + const OUString &rText = GetText(); + if (rText.isEmpty()) + return; + sal_Int32 nStrIndex = 0; + sal_UCS4 cChar = rText.iterateCodePoints(&nStrIndex); + SetSymbol(cChar, GetFont()); //force recalculation of size +} + +void SmShowChar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + Color aTextCol = rRenderContext.GetTextColor(); + Color aFillCol = rRenderContext.GetFillColor(); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const Color aWindowTextColor(rStyleSettings.GetDialogTextColor()); + const Color aWindowColor(rStyleSettings.GetWindowColor()); + rRenderContext.SetTextColor(aWindowTextColor); + rRenderContext.SetFillColor(aWindowColor); + + Size aSize(GetOutputSizePixel()); + rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), aSize)); + + OUString aText(GetText()); + if (!aText.isEmpty()) + { + vcl::Font aFont(m_aFont); + aFont.SetAlignment(ALIGN_TOP); + rRenderContext.SetFont(aFont); + + Size aTextSize(rRenderContext.GetTextWidth(aText), rRenderContext.GetTextHeight()); + + rRenderContext.DrawText(Point((aSize.Width() - aTextSize.Width()) / 2, + (aSize.Height() - aTextSize.Height()) / 2), aText); + } + + rRenderContext.SetTextColor(aTextCol); + rRenderContext.SetFillColor(aFillCol); +} + +void SmShowChar::SetSymbol( const SmSym *pSym ) +{ + if (pSym) + SetSymbol( pSym->GetCharacter(), pSym->GetFace() ); +} + + +void SmShowChar::SetSymbol( sal_UCS4 cChar, const vcl::Font &rFont ) +{ + vcl::Font aFont( rFont ); + Size aSize(GetOutputSizePixel()); + aFont.SetFontSize(Size(0, aSize.Height() - aSize.Height() / 3)); + aFont.SetAlignment(ALIGN_BASELINE); + SetFont(aFont); + + OUString aText(&cChar, 1); + SetText( aText ); + + Invalidate(); +} + +void SmSymDefineDialog::FillSymbols(weld::ComboBox& rComboBox, bool bDeleteText) +{ + assert((&rComboBox == m_xOldSymbols.get() || &rComboBox == m_xSymbols.get()) && "Sm : wrong ComboBox"); + + rComboBox.clear(); + if (bDeleteText) + rComboBox.set_entry_text(OUString()); + + weld::ComboBox& rBox = &rComboBox == m_xOldSymbols.get() ? *m_xOldSymbolSets : *m_xSymbolSets; + SymbolPtrVec_t aSymSet(m_aSymbolMgrCopy.GetSymbolSet(rBox.get_active_text())); + for (const SmSym* i : aSymSet) + rComboBox.append_text(i->GetName()); +} + +void SmSymDefineDialog::FillSymbolSets(weld::ComboBox& rComboBox, bool bDeleteText) +{ + assert((&rComboBox == m_xOldSymbolSets.get() || &rComboBox == m_xSymbolSets.get()) && "Sm : wrong ComboBox"); + + rComboBox.clear(); + if (bDeleteText) + rComboBox.set_entry_text(OUString()); + + const std::set< OUString > aSymbolSetNames( m_aSymbolMgrCopy.GetSymbolSetNames() ); + for (const auto& rSymbolSetName : aSymbolSetNames) + rComboBox.append_text(rSymbolSetName); +} + +void SmSymDefineDialog::FillFonts() +{ + m_xFonts->clear(); + m_xFonts->set_active(-1); + + // Include all fonts of FontList into the font list. + // If there are duplicates, only include one entry of each font since the style will be + // already selected using the FontStyleBox. + if (m_xFontList) + { + sal_uInt16 nCount = m_xFontList->GetFontNameCount(); + for (sal_uInt16 i = 0; i < nCount; ++i) + m_xFonts->append_text(m_xFontList->GetFontName(i).GetFamilyName()); + } +} + +void SmSymDefineDialog::FillStyles() +{ + m_xStyles->clear(); +// pStyles->SetText(OUString()); + + OUString aText(m_xFonts->get_active_text()); + if (!aText.isEmpty()) + { + // use own StyleNames + const SmFontStyles &rStyles = GetFontStyles(); + for (sal_uInt16 i = 0; i < SmFontStyles::GetCount(); ++i) + m_xStyles->append_text(rStyles.GetStyleName(i)); + + assert(m_xStyles->get_count() > 0 && "Sm : no styles available"); + m_xStyles->set_active(0); + } +} + +SmSym* SmSymDefineDialog::GetSymbol(const weld::ComboBox& rComboBox) +{ + assert((&rComboBox == m_xOldSymbols.get() || &rComboBox == m_xSymbols.get()) && "Sm : wrong combobox"); + return m_aSymbolMgrCopy.GetSymbolByName(rComboBox.get_active_text()); +} + +IMPL_LINK(SmSymDefineDialog, OldSymbolChangeHdl, weld::ComboBox&, rComboBox, void) +{ + (void) rComboBox; + assert(&rComboBox == m_xOldSymbols.get() && "Sm : wrong argument"); + SelectSymbol(*m_xOldSymbols, m_xOldSymbols->get_active_text(), false); +} + +IMPL_LINK( SmSymDefineDialog, OldSymbolSetChangeHdl, weld::ComboBox&, rComboBox, void ) +{ + (void) rComboBox; + assert(&rComboBox == m_xOldSymbolSets.get() && "Sm : wrong argument"); + SelectSymbolSet(*m_xOldSymbolSets, m_xOldSymbolSets->get_active_text(), false); +} + +IMPL_LINK(SmSymDefineDialog, ModifyHdl, weld::ComboBox&, rComboBox, void) +{ + // remember cursor position for later restoring of it + int nStartPos, nEndPos; + rComboBox.get_entry_selection_bounds(nStartPos, nEndPos); + + if (&rComboBox == m_xSymbols.get()) + SelectSymbol(*m_xSymbols, m_xSymbols->get_active_text(), false); + else if (&rComboBox == m_xSymbolSets.get()) + SelectSymbolSet(*m_xSymbolSets, m_xSymbolSets->get_active_text(), false); + else if (&rComboBox == m_xOldSymbols.get()) + // allow only names from the list + SelectSymbol(*m_xOldSymbols, m_xOldSymbols->get_active_text(), true); + else if (&rComboBox == m_xOldSymbolSets.get()) + // allow only names from the list + SelectSymbolSet(*m_xOldSymbolSets, m_xOldSymbolSets->get_active_text(), true); + else if (&rComboBox == m_xStyles.get()) + // allow only names from the list (that's the case here anyway) + SelectStyle(m_xStyles->get_active_text(), true); + else + SAL_WARN("starmath", "wrong combobox argument"); + + rComboBox.select_entry_region(nStartPos, nEndPos); + + UpdateButtons(); +} + +IMPL_LINK(SmSymDefineDialog, FontChangeHdl, weld::ComboBox&, rListBox, void) +{ + (void) rListBox; + assert(&rListBox == m_xFonts.get() && "Sm : wrong argument"); + + SelectFont(m_xFonts->get_active_text()); +} + +IMPL_LINK_NOARG(SmSymDefineDialog, SubsetChangeHdl, weld::ComboBox&, void) +{ + int nPos = m_xFontsSubsetLB->get_active(); + if (nPos != -1) + { + const Subset* pSubset = reinterpret_cast<const Subset*>(m_xFontsSubsetLB->get_active_id().toUInt64()); + if (pSubset) + { + m_xCharsetDisplay->SelectCharacter( pSubset->GetRangeMin() ); + } + } +} + +IMPL_LINK( SmSymDefineDialog, StyleChangeHdl, weld::ComboBox&, rComboBox, void ) +{ + (void) rComboBox; + assert(&rComboBox == m_xStyles.get() && "Sm : wrong argument"); + + SelectStyle(m_xStyles->get_active_text()); +} + +IMPL_LINK_NOARG(SmSymDefineDialog, CharHighlightHdl, SvxShowCharSet*, void) +{ + sal_UCS4 cChar = m_xCharsetDisplay->GetSelectCharacter(); + + if (m_xSubsetMap) + { + const Subset* pSubset = m_xSubsetMap->GetSubsetByUnicode(cChar); + if (pSubset) + m_xFontsSubsetLB->set_active_text(pSubset->GetName()); + else + m_xFontsSubsetLB->set_active(-1); + } + + m_aSymbolDisplay.SetSymbol(cChar, m_xCharsetDisplay->GetFont()); + + UpdateButtons(); + + // display Unicode position as symbol name while iterating over characters + const OUString aHex(OUString::number(cChar, 16).toAsciiUpperCase()); + const OUString aPattern( (aHex.getLength() > 4) ? OUString("Ux000000") : OUString("Ux0000") ); + OUString aUnicodePos = aPattern.copy( 0, aPattern.getLength() - aHex.getLength() ) + + aHex; + m_xSymbols->set_entry_text(aUnicodePos); + m_xSymbolName->set_label(aUnicodePos); +} + +IMPL_LINK( SmSymDefineDialog, AddClickHdl, weld::Button&, rButton, void ) +{ + (void) rButton; + assert(&rButton == m_xAddBtn.get() && "Sm : wrong argument"); + assert(rButton.get_sensitive() && "Sm : requirements met ??"); + + // add symbol + const SmSym aNewSymbol(m_xSymbols->get_active_text(), m_xCharsetDisplay->GetFont(), + m_xCharsetDisplay->GetSelectCharacter(), m_xSymbolSets->get_active_text()); + //OSL_ENSURE( m_aSymbolMgrCopy.GetSymbolByName(aTmpSymbolName) == NULL, "symbol already exists" ); + m_aSymbolMgrCopy.AddOrReplaceSymbol( aNewSymbol ); + + // update display of new symbol + m_aSymbolDisplay.SetSymbol( &aNewSymbol ); + m_xSymbolName->set_label(aNewSymbol.GetName()); + m_xSymbolSetName->set_label(aNewSymbol.GetSymbolSetName()); + + // update list box entries + FillSymbolSets(*m_xOldSymbolSets, false); + FillSymbolSets(*m_xSymbolSets, false); + FillSymbols(*m_xOldSymbols, false); + FillSymbols(*m_xSymbols, false); + + UpdateButtons(); +} + +IMPL_LINK( SmSymDefineDialog, ChangeClickHdl, weld::Button&, rButton, void ) +{ + (void) rButton; + assert(&rButton == m_xChangeBtn.get() && "Sm : wrong argument"); + assert(m_xChangeBtn->get_sensitive() && "Sm : requirements met ??"); + + // get new Symbol to use + //! get font from symbol-disp lay since charset-display does not keep + //! the bold attribute. + const SmSym aNewSymbol(m_xSymbols->get_active_text(), m_xCharsetDisplay->GetFont(), + m_xCharsetDisplay->GetSelectCharacter(), m_xSymbolSets->get_active_text()); + + // remove old symbol if the name was changed then add new one + const bool bNameChanged = m_xOldSymbols->get_active_text() != m_xSymbols->get_active_text(); + if (bNameChanged) + m_aSymbolMgrCopy.RemoveSymbol(m_xOldSymbols->get_active_text()); + m_aSymbolMgrCopy.AddOrReplaceSymbol( aNewSymbol, true ); + + // clear display for original symbol if necessary + if (bNameChanged) + SetOrigSymbol(nullptr, OUString()); + + // update display of new symbol + m_aSymbolDisplay.SetSymbol(&aNewSymbol); + m_xSymbolName->set_label(aNewSymbol.GetName()); + m_xSymbolSetName->set_label(aNewSymbol.GetSymbolSetName()); + + // update list box entries + FillSymbolSets(*m_xOldSymbolSets, false); + FillSymbolSets(*m_xSymbolSets, false); + FillSymbols(*m_xOldSymbols, false); + FillSymbols(*m_xSymbols, false); + + UpdateButtons(); +} + +IMPL_LINK(SmSymDefineDialog, DeleteClickHdl, weld::Button&, rButton, void) +{ + (void) rButton; + assert(&rButton == m_xDeleteBtn.get() && "Sm : wrong argument"); + assert(m_xDeleteBtn->get_sensitive() && "Sm : requirements met ??"); + + if (m_xOrigSymbol) + { + m_aSymbolMgrCopy.RemoveSymbol(m_xOrigSymbol->GetName()); + + // clear display for original symbol + SetOrigSymbol(nullptr, OUString()); + + // update list box entries + FillSymbolSets(*m_xOldSymbolSets, false); + FillSymbolSets(*m_xSymbolSets, false); + FillSymbols(*m_xOldSymbols ,false); + FillSymbols(*m_xSymbols ,false); + } + + UpdateButtons(); +} + +void SmSymDefineDialog::UpdateButtons() +{ + bool bAdd = false, + bChange = false, + bDelete = false; + OUString aTmpSymbolName(m_xSymbols->get_active_text()), + aTmpSymbolSetName(m_xSymbolSets->get_active_text()); + + if (!aTmpSymbolName.isEmpty() && !aTmpSymbolSetName.isEmpty()) + { + // are all settings equal? + //! (Font-, Style- and SymbolSet name comparison is not case sensitive) + bool bEqual = m_xOrigSymbol + && aTmpSymbolSetName.equalsIgnoreAsciiCase(m_xOldSymbolSetName->get_label()) + && aTmpSymbolName == m_xOrigSymbol->GetName() + && m_xFonts->get_active_text().equalsIgnoreAsciiCase( + m_xOrigSymbol->GetFace().GetFamilyName()) + && m_xStyles->get_active_text().equalsIgnoreAsciiCase( + GetFontStyles().GetStyleName(m_xOrigSymbol->GetFace())) + && m_xCharsetDisplay->GetSelectCharacter() == m_xOrigSymbol->GetCharacter(); + + // only add it if there isn't already a symbol with the same name + bAdd = m_aSymbolMgrCopy.GetSymbolByName(aTmpSymbolName) == nullptr; + + // only delete it if all settings are equal + bDelete = bool(m_xOrigSymbol); + + // only change it if the old symbol exists and the new one is different + bChange = m_xOrigSymbol && !bEqual; + } + + m_xAddBtn->set_sensitive(bAdd); + m_xChangeBtn->set_sensitive(bChange); + m_xDeleteBtn->set_sensitive(bDelete); +} + +SmSymDefineDialog::SmSymDefineDialog(weld::Window* pParent, OutputDevice *pFntListDevice, SmSymbolManager &rMgr) + : GenericDialogController(pParent, "modules/smath/ui/symdefinedialog.ui", "EditSymbols") + , m_xVirDev(VclPtr<VirtualDevice>::Create()) + , m_rSymbolMgr(rMgr) + , m_xFontList(new FontList(pFntListDevice)) + , m_xOldSymbols(m_xBuilder->weld_combo_box("oldSymbols")) + , m_xOldSymbolSets(m_xBuilder->weld_combo_box("oldSymbolSets")) + , m_xSymbols(m_xBuilder->weld_combo_box("symbols")) + , m_xSymbolSets(m_xBuilder->weld_combo_box("symbolSets")) + , m_xFonts(m_xBuilder->weld_combo_box("fonts")) + , m_xFontsSubsetLB(m_xBuilder->weld_combo_box("fontsSubsetLB")) + , m_xStyles(m_xBuilder->weld_combo_box("styles")) + , m_xOldSymbolName(m_xBuilder->weld_label("oldSymbolName")) + , m_xOldSymbolSetName(m_xBuilder->weld_label("oldSymbolSetName")) + , m_xSymbolName(m_xBuilder->weld_label("symbolName")) + , m_xSymbolSetName(m_xBuilder->weld_label("symbolSetName")) + , m_xAddBtn(m_xBuilder->weld_button("add")) + , m_xChangeBtn(m_xBuilder->weld_button("modify")) + , m_xDeleteBtn(m_xBuilder->weld_button("delete")) + , m_xOldSymbolDisplay(new weld::CustomWeld(*m_xBuilder, "oldSymbolDisplay", m_aOldSymbolDisplay)) + , m_xSymbolDisplay(new weld::CustomWeld(*m_xBuilder, "symbolDisplay", m_aSymbolDisplay)) + , m_xCharsetDisplay(new SvxShowCharSet(m_xBuilder->weld_scrolled_window("showscroll"), m_xVirDev)) + , m_xCharsetDisplayArea(new weld::CustomWeld(*m_xBuilder, "charsetDisplay", *m_xCharsetDisplay)) +{ + // auto completion is troublesome since that symbols character also gets automatically selected in the + // display and if the user previously selected a character to define/redefine that one this is bad + m_xOldSymbols->set_entry_completion(false); + m_xSymbols->set_entry_completion(false); + + FillFonts(); + if (m_xFonts->get_count() > 0) + SelectFont(m_xFonts->get_text(0)); + + SetSymbolSetManager(m_rSymbolMgr); + + m_xOldSymbols->connect_changed(LINK(this, SmSymDefineDialog, OldSymbolChangeHdl)); + m_xOldSymbolSets->connect_changed(LINK(this, SmSymDefineDialog, OldSymbolSetChangeHdl)); + m_xSymbolSets->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xOldSymbolSets->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xSymbols->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xOldSymbols->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xStyles->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xFonts->connect_changed(LINK(this, SmSymDefineDialog, FontChangeHdl)); + m_xFontsSubsetLB->connect_changed(LINK(this, SmSymDefineDialog, SubsetChangeHdl)); + m_xStyles->connect_changed(LINK(this, SmSymDefineDialog, StyleChangeHdl)); + m_xAddBtn->connect_clicked(LINK(this, SmSymDefineDialog, AddClickHdl)); + m_xChangeBtn->connect_clicked(LINK(this, SmSymDefineDialog, ChangeClickHdl)); + m_xDeleteBtn->connect_clicked(LINK(this, SmSymDefineDialog, DeleteClickHdl)); + m_xCharsetDisplay->SetHighlightHdl( LINK( this, SmSymDefineDialog, CharHighlightHdl ) ); +} + +SmSymDefineDialog::~SmSymDefineDialog() +{ +} + +short SmSymDefineDialog::run() +{ + short nResult = GenericDialogController::run(); + + // apply changes if dialog was closed by clicking OK + if (m_aSymbolMgrCopy.IsModified() && nResult == RET_OK) + m_rSymbolMgr = m_aSymbolMgrCopy; + + return nResult; +} + +void SmSymDefineDialog::SetSymbolSetManager(const SmSymbolManager &rMgr) +{ + m_aSymbolMgrCopy = rMgr; + + // Set the modified flag of the copy to false so that + // we can check later on if anything has been changed + m_aSymbolMgrCopy.SetModified(false); + + FillSymbolSets(*m_xOldSymbolSets); + if (m_xOldSymbolSets->get_count() > 0) + SelectSymbolSet(m_xOldSymbolSets->get_text(0)); + FillSymbolSets(*m_xSymbolSets); + if (m_xSymbolSets->get_count() > 0) + SelectSymbolSet(m_xSymbolSets->get_text(0)); + FillSymbols(*m_xOldSymbols); + if (m_xOldSymbols->get_count() > 0) + SelectSymbol(m_xOldSymbols->get_text(0)); + FillSymbols(*m_xSymbols); + if (m_xSymbols->get_count() > 0) + SelectSymbol(m_xSymbols->get_text(0)); + + UpdateButtons(); +} + +bool SmSymDefineDialog::SelectSymbolSet(weld::ComboBox& rComboBox, + const OUString &rSymbolSetName, bool bDeleteText) +{ + assert((&rComboBox == m_xOldSymbolSets.get() || &rComboBox == m_xSymbolSets.get()) && "Sm : wrong ComboBox"); + + // trim SymbolName (no leading and trailing blanks) + OUString aNormName = comphelper::string::stripStart(rSymbolSetName, ' '); + aNormName = comphelper::string::stripEnd(aNormName, ' '); + // and remove possible deviations within the input + rComboBox.set_entry_text(aNormName); + + bool bRet = false; + int nPos = rComboBox.find_text(aNormName); + + if (nPos != -1) + { + rComboBox.set_active(nPos); + bRet = true; + } + else if (bDeleteText) + rComboBox.set_entry_text(OUString()); + + bool bIsOld = &rComboBox == m_xOldSymbolSets.get(); + + // setting the SymbolSet name at the associated display + weld::Label& rFT = bIsOld ? *m_xOldSymbolSetName : *m_xSymbolSetName; + rFT.set_label(rComboBox.get_active_text()); + + // set the symbol name which belongs to the SymbolSet at the associated combobox + weld::ComboBox& rCB = bIsOld ? *m_xOldSymbols : *m_xSymbols; + FillSymbols(rCB, false); + + // display a valid respectively no symbol when changing the SymbolSets + if (bIsOld) + { + OUString aTmpOldSymbolName; + if (m_xOldSymbols->get_count() > 0) + aTmpOldSymbolName = m_xOldSymbols->get_text(0); + SelectSymbol(*m_xOldSymbols, aTmpOldSymbolName, true); + } + + UpdateButtons(); + + return bRet; +} + +void SmSymDefineDialog::SetOrigSymbol(const SmSym *pSymbol, + const OUString &rSymbolSetName) +{ + // clear old symbol + m_xOrigSymbol.reset(); + + OUString aSymName, + aSymSetName; + if (pSymbol) + { + // set new symbol + m_xOrigSymbol.reset(new SmSym(*pSymbol)); + + aSymName = pSymbol->GetName(); + aSymSetName = rSymbolSetName; + m_aOldSymbolDisplay.SetSymbol( pSymbol ); + } + else + { // delete displayed symbols + m_aOldSymbolDisplay.SetText(OUString()); + m_aOldSymbolDisplay.Invalidate(); + } + m_xOldSymbolName->set_label(aSymName); + m_xOldSymbolSetName->set_label(aSymSetName); +} + + +bool SmSymDefineDialog::SelectSymbol(weld::ComboBox& rComboBox, + const OUString &rSymbolName, bool bDeleteText) +{ + assert((&rComboBox == m_xOldSymbols.get() || &rComboBox == m_xSymbols.get()) && "Sm : wrong ComboBox"); + + // trim SymbolName (no blanks) + OUString aNormName = rSymbolName.replaceAll(" ", ""); + // and remove possible deviations within the input + rComboBox.set_entry_text(aNormName); + + bool bRet = false; + int nPos = rComboBox.find_text(aNormName); + + bool bIsOld = &rComboBox == m_xOldSymbols.get(); + + if (nPos != -1) + { + rComboBox.set_active(nPos); + + if (!bIsOld) + { + const SmSym *pSymbol = GetSymbol(*m_xSymbols); + if (pSymbol) + { + // choose font and style accordingly + const vcl::Font &rFont = pSymbol->GetFace(); + SelectFont(rFont.GetFamilyName(), false); + SelectStyle(GetFontStyles().GetStyleName(rFont), false); + + // Since setting the Font via the Style name of the SymbolFonts doesn't + // work really well (e.g. it can be empty even though the font itself is + // bold or italic) we're manually setting the Font with respect to the Symbol + m_xCharsetDisplay->SetFont(rFont); + m_aSymbolDisplay.SetFont(rFont); + + // select associated character + SelectChar(pSymbol->GetCharacter()); + + // since SelectChar will also set the unicode point as text in the + // symbols box, we have to set the symbol name again to get that one displayed + m_xSymbols->set_entry_text(pSymbol->GetName()); + } + } + + bRet = true; + } + else if (bDeleteText) + rComboBox.set_entry_text(OUString()); + + if (bIsOld) + { + // if there's a change of the old symbol, show only the available ones, otherwise show none + const SmSym *pOldSymbol = nullptr; + OUString aTmpOldSymbolSetName; + if (nPos != -1) + { + pOldSymbol = m_aSymbolMgrCopy.GetSymbolByName(aNormName); + aTmpOldSymbolSetName = m_xOldSymbolSets->get_active_text(); + } + SetOrigSymbol(pOldSymbol, aTmpOldSymbolSetName); + } + else + m_xSymbolName->set_label(rComboBox.get_active_text()); + + UpdateButtons(); + + return bRet; +} + + +void SmSymDefineDialog::SetFont(const OUString &rFontName, const OUString &rStyleName) +{ + // get Font (FontInfo) matching name and style + FontMetric aFontMetric; + if (m_xFontList) + aFontMetric = m_xFontList->Get(rFontName, WEIGHT_NORMAL, ITALIC_NONE); + SetFontStyle(rStyleName, aFontMetric); + + m_xCharsetDisplay->SetFont(aFontMetric); + m_aSymbolDisplay.SetFont(aFontMetric); + + // update subset listbox for new font's unicode subsets + FontCharMapRef xFontCharMap = m_xCharsetDisplay->GetFontCharMap(); + m_xSubsetMap.reset(new SubsetMap( xFontCharMap )); + + m_xFontsSubsetLB->clear(); + bool bFirst = true; + for (auto & subset : m_xSubsetMap->GetSubsetMap()) + { + m_xFontsSubsetLB->append(OUString::number(reinterpret_cast<sal_uInt64>(&subset)), subset.GetName()); + // subset must live at least as long as the selected font !!! + if (bFirst) + m_xFontsSubsetLB->set_active(0); + bFirst = false; + } + if (bFirst) + m_xFontsSubsetLB->set_active(-1); + m_xFontsSubsetLB->set_sensitive(!bFirst); +} + +bool SmSymDefineDialog::SelectFont(const OUString &rFontName, bool bApplyFont) +{ + bool bRet = false; + int nPos = m_xFonts->find_text(rFontName); + + if (nPos != -1) + { + m_xFonts->set_active(nPos); + if (m_xStyles->get_count() > 0) + SelectStyle(m_xStyles->get_text(0)); + if (bApplyFont) + { + SetFont(m_xFonts->get_active_text(), m_xStyles->get_active_text()); + m_aSymbolDisplay.SetSymbol(m_xCharsetDisplay->GetSelectCharacter(), m_xCharsetDisplay->GetFont()); + } + bRet = true; + } + else + m_xFonts->set_active(-1); + FillStyles(); + + UpdateButtons(); + + return bRet; +} + + +bool SmSymDefineDialog::SelectStyle(const OUString &rStyleName, bool bApplyFont) +{ + bool bRet = false; + int nPos = m_xStyles->find_text(rStyleName); + + // if the style is not available take the first available one (if existent) + if (nPos == -1 && m_xStyles->get_count() > 0) + nPos = 0; + + if (nPos != -1) + { + m_xStyles->set_active(nPos); + if (bApplyFont) + { + SetFont(m_xFonts->get_active_text(), m_xStyles->get_active_text()); + m_aSymbolDisplay.SetSymbol(m_xCharsetDisplay->GetSelectCharacter(), m_xCharsetDisplay->GetFont()); + } + bRet = true; + } + else + m_xStyles->set_entry_text(OUString()); + + UpdateButtons(); + + return bRet; +} + +void SmSymDefineDialog::SelectChar(sal_Unicode cChar) +{ + m_xCharsetDisplay->SelectCharacter( cChar ); + m_aSymbolDisplay.SetSymbol(cChar, m_xCharsetDisplay->GetFont()); + + UpdateButtons(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/document.cxx b/starmath/source/document.cxx new file mode 100644 index 000000000..8f9925c3c --- /dev/null +++ b/starmath/source/document.cxx @@ -0,0 +1,1291 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/uno/Any.h> + +#include <comphelper/fileformat.h> +#include <comphelper/accessibletexthelper.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <unotools/eventcfg.hxx> +#include <sfx2/event.hxx> +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/request.hxx> +#include <sfx2/viewfrm.hxx> +#include <comphelper/classids.hxx> +#include <sot/formats.hxx> +#include <sot/storage.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/itempool.hxx> +#include <svl/slstitm.hxx> +#include <svl/hint.hxx> +#include <svl/stritem.hxx> +#include <svl/undo.hxx> +#include <svl/whiter.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editstat.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <vcl/mapmod.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <tools/mapunit.hxx> +#include <vcl/settings.hxx> + +#include <document.hxx> +#include <action.hxx> +#include <dialog.hxx> +#include <format.hxx> +#include <starmath.hrc> +#include <strings.hrc> +#include <smmod.hxx> +#include <symbol.hxx> +#include <unomodel.hxx> +#include <utility.hxx> +#include <view.hxx> +#include "mathtype.hxx" +#include "ooxmlexport.hxx" +#include "ooxmlimport.hxx" +#include "rtfexport.hxx" +#include "mathmlimport.hxx" +#include "mathmlexport.hxx" +#include <svx/svxids.hrc> +#include <cursor.hxx> +#include <tools/diagnose_ex.h> +#include <visitors.hxx> +#include "accessibility.hxx" +#include "cfgitem.hxx" +#include <memory> +#include <utility> +#include <oox/mathml/export.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +#define ShellClass_SmDocShell +#include <smslots.hxx> + + +SFX_IMPL_SUPERCLASS_INTERFACE(SmDocShell, SfxObjectShell) + +void SmDocShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterPopupMenu("view"); +} + +SFX_IMPL_OBJECTFACTORY(SmDocShell, SvGlobalName(SO3_SM_CLASSID), "smath" ) + +void SmDocShell::Notify(SfxBroadcaster&, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::MathFormatChanged) + { + SetFormulaArranged(false); + + mnModifyCount++; //! see comment for SID_GAPHIC_SM in SmDocShell::GetState + + Repaint(); + } +} + +void SmDocShell::LoadSymbols() +{ + SmModule *pp = SM_MOD(); + pp->GetSymbolManager().Load(); +} + + +OUString SmDocShell::GetComment() const +{ + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + return xDocProps->getDescription(); +} + + +void SmDocShell::SetText(const OUString& rBuffer) +{ + if (rBuffer == maText) + return; + + bool bIsEnabled = IsEnableSetModified(); + if( bIsEnabled ) + EnableSetModified( false ); + + maText = rBuffer; + SetFormulaArranged( false ); + + Parse(); + + SmViewShell *pViewSh = SmGetActiveView(); + if( pViewSh ) + { + pViewSh->GetViewFrame()->GetBindings().Invalidate(SID_TEXT); + if ( SfxObjectCreateMode::EMBEDDED == GetCreateMode() ) + { + // have SwOleClient::FormatChanged() to align the modified formula properly + // even if the visible area does not change (e.g. when formula text changes from + // "{a over b + c} over d" to "d over {a over b + c}" + SfxGetpApp()->NotifyEvent(SfxEventHint( SfxEventHintId::VisAreaChanged, GlobalEventConfig::GetEventName(GlobalEventId::VISAREACHANGED), this)); + + Repaint(); + } + else + pViewSh->GetGraphicWindow().Invalidate(); + } + + if ( bIsEnabled ) + EnableSetModified( bIsEnabled ); + SetModified(); + + // launch accessible event if necessary + SmGraphicAccessible *pAcc = pViewSh ? pViewSh->GetGraphicWindow().GetAccessible_Impl() : nullptr; + if (pAcc) + { + Any aOldValue, aNewValue; + if ( comphelper::OCommonAccessibleText::implInitTextChangedEvent( maText, rBuffer, aOldValue, aNewValue ) ) + { + pAcc->LaunchEvent( AccessibleEventId::TEXT_CHANGED, + aOldValue, aNewValue ); + } + } + + if ( GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + OnDocumentPrinterChanged(nullptr); +} + +void SmDocShell::SetFormat(SmFormat const & rFormat) +{ + maFormat = rFormat; + SetFormulaArranged( false ); + SetModified(); + + mnModifyCount++; //! see comment for SID_GAPHIC_SM in SmDocShell::GetState + + // don't use SmGetActiveView since the view shell might not be active (0 pointer) + // if for example the Basic Macro dialog currently has the focus. Thus: + SfxViewFrame* pFrm = SfxViewFrame::GetFirst( this ); + while (pFrm) + { + pFrm->GetBindings().Invalidate(SID_GAPHIC_SM); + pFrm = SfxViewFrame::GetNext( *pFrm, this ); + } +} + +OUString const & SmDocShell::GetAccessibleText() +{ + ArrangeFormula(); + if (maAccText.isEmpty()) + { + OSL_ENSURE( mpTree, "Tree missing" ); + if (mpTree) + { + OUStringBuffer aBuf; + mpTree->GetAccessibleText(aBuf); + maAccText = aBuf.makeStringAndClear(); + } + } + return maAccText; +} + +void SmDocShell::Parse() +{ + mpTree.reset(); + ReplaceBadChars(); + mpTree = maParser.Parse(maText); + mnModifyCount++; //! see comment for SID_GAPHIC_SM in SmDocShell::GetState + SetFormulaArranged( false ); + InvalidateCursor(); + maUsedSymbols = maParser.GetUsedSymbols(); +} + + +void SmDocShell::ArrangeFormula() +{ + if (mbFormulaArranged) + return; + + // Only for the duration of the existence of this object the correct settings + // at the printer are guaranteed! + SmPrinterAccess aPrtAcc(*this); + OutputDevice* pOutDev = aPrtAcc.GetRefDev(); + + SAL_WARN_IF( !pOutDev, "starmath", "!! SmDocShell::ArrangeFormula: reference device missing !!"); + + // if necessary get another OutputDevice for which we format + if (!pOutDev) + { + SmViewShell *pView = SmGetActiveView(); + if (pView) + pOutDev = &pView->GetGraphicWindow(); + else + { + pOutDev = &SM_MOD()->GetDefaultVirtualDev(); + pOutDev->SetMapMode( MapMode(MapUnit::Map100thMM) ); + } + } + OSL_ENSURE(pOutDev->GetMapMode().GetMapUnit() == MapUnit::Map100thMM, + "Sm : wrong MapMode"); + + const SmFormat &rFormat = GetFormat(); + mpTree->Prepare(rFormat, *this, 0); + + // format/draw formulas always from left to right, + // and numbers should not be converted + ComplexTextLayoutFlags nLayoutMode = pOutDev->GetLayoutMode(); + pOutDev->SetLayoutMode( ComplexTextLayoutFlags::Default ); + LanguageType nDigitLang = pOutDev->GetDigitLanguage(); + pOutDev->SetDigitLanguage( LANGUAGE_ENGLISH ); + + mpTree->Arrange(*pOutDev, rFormat); + + pOutDev->SetLayoutMode( nLayoutMode ); + pOutDev->SetDigitLanguage( nDigitLang ); + + SetFormulaArranged(true); + + // invalidate accessible text + maAccText.clear(); +} + +void SmDocShell::UpdateEditEngineDefaultFonts(const Color& aTextColor) +{ + assert(mpEditEngineItemPool); + if (!mpEditEngineItemPool) + return; + + // set fonts to be used + struct FontDta { + LanguageType nFallbackLang; + LanguageType nLang; + DefaultFontType nFontType; + sal_uInt16 nFontInfoId; + } aTable[3] = + { + // info to get western font to be used + { LANGUAGE_ENGLISH_US, LANGUAGE_NONE, + DefaultFontType::FIXED, EE_CHAR_FONTINFO }, + // info to get CJK font to be used + { LANGUAGE_JAPANESE, LANGUAGE_NONE, + DefaultFontType::CJK_TEXT, EE_CHAR_FONTINFO_CJK }, + // info to get CTL font to be used + { LANGUAGE_ARABIC_SAUDI_ARABIA, LANGUAGE_NONE, + DefaultFontType::CTL_TEXT, EE_CHAR_FONTINFO_CTL } + }; + + aTable[0].nLang = maLinguOptions.nDefaultLanguage; + aTable[1].nLang = maLinguOptions.nDefaultLanguage_CJK; + aTable[2].nLang = maLinguOptions.nDefaultLanguage_CTL; + + for (const FontDta & rFntDta : aTable) + { + LanguageType nLang = (LANGUAGE_NONE == rFntDta.nLang) ? + rFntDta.nFallbackLang : rFntDta.nLang; + vcl::Font aFont = OutputDevice::GetDefaultFont( + rFntDta.nFontType, nLang, GetDefaultFontFlags::OnlyOne ); + aFont.SetColor(aTextColor); + mpEditEngineItemPool->SetPoolDefaultItem( + SvxFontItem( aFont.GetFamilyType(), aFont.GetFamilyName(), + aFont.GetStyleName(), aFont.GetPitch(), aFont.GetCharSet(), + rFntDta.nFontInfoId ) ); + } + + // set font heights + SvxFontHeightItem aFontHeigt( + Application::GetDefaultDevice()->LogicToPixel( + Size( 0, 11 ), MapMode( MapUnit::MapPoint ) ).Height(), 100, + EE_CHAR_FONTHEIGHT ); + mpEditEngineItemPool->SetPoolDefaultItem( aFontHeigt ); + aFontHeigt.SetWhich( EE_CHAR_FONTHEIGHT_CJK ); + mpEditEngineItemPool->SetPoolDefaultItem( aFontHeigt ); + aFontHeigt.SetWhich( EE_CHAR_FONTHEIGHT_CTL ); + mpEditEngineItemPool->SetPoolDefaultItem( aFontHeigt ); +} + +EditEngine& SmDocShell::GetEditEngine() +{ + if (!mpEditEngine) + { + //! + //! see also SmEditWindow::DataChanged ! + //! + + mpEditEngineItemPool = EditEngine::CreatePool(); + + const StyleSettings& rStyleSettings = Application::GetDefaultDevice()->GetSettings().GetStyleSettings(); + UpdateEditEngineDefaultFonts(rStyleSettings.GetFieldTextColor()); + + mpEditEngine.reset( new EditEngine( mpEditEngineItemPool ) ); + + mpEditEngine->SetAddExtLeading(true); + + mpEditEngine->EnableUndo( true ); + mpEditEngine->SetDefTab( sal_uInt16( + Application::GetDefaultDevice()->GetTextWidth("XXXX")) ); + + mpEditEngine->SetBackgroundColor(rStyleSettings.GetFieldColor()); + + mpEditEngine->SetControlWord( + (mpEditEngine->GetControlWord() | EEControlBits::AUTOINDENTING) & + EEControlBits(~EEControlBits::UNDOATTRIBS) & + EEControlBits(~EEControlBits::PASTESPECIAL) ); + + mpEditEngine->SetWordDelimiters(" .=+-*/(){}[];\""); + mpEditEngine->SetRefMapMode(MapMode(MapUnit::MapPixel)); + + mpEditEngine->SetPaperSize( Size( 800, 0 ) ); + + mpEditEngine->EraseVirtualDevice(); + + // set initial text if the document already has some... + // (may be the case when reloading a doc) + OUString aTxt( GetText() ); + if (!aTxt.isEmpty()) + mpEditEngine->SetText( aTxt ); + + mpEditEngine->ClearModifyFlag(); + + } + return *mpEditEngine; +} + + +void SmDocShell::DrawFormula(OutputDevice &rDev, Point &rPosition, bool bDrawSelection) +{ + if (!mpTree) + Parse(); + OSL_ENSURE(mpTree, "Sm : NULL pointer"); + + ArrangeFormula(); + + // Problem: What happens to WYSIWYG? While we're active inplace, we don't have a reference + // device and aren't aligned to that either. So now there can be a difference between the + // VisArea (i.e. the size within the client) and the current size. + // Idea: The difference could be adapted with SmNod::SetSize (no long-term solution) + + rPosition.AdjustX(maFormat.GetDistance( DIS_LEFTSPACE ) ); + rPosition.AdjustY(maFormat.GetDistance( DIS_TOPSPACE ) ); + + //! in case of high contrast-mode (accessibility option!) + //! the draw mode needs to be set to default, because when embedding + //! Math for example in Calc in "a over b" the fraction bar may not + //! be visible else. More generally: the FillColor may have been changed. + DrawModeFlags nOldDrawMode = DrawModeFlags::Default; + bool bRestoreDrawMode = false; + if (OUTDEV_WINDOW == rDev.GetOutDevType() && + static_cast<vcl::Window &>(rDev).GetSettings().GetStyleSettings().GetHighContrastMode()) + { + nOldDrawMode = rDev.GetDrawMode(); + rDev.SetDrawMode( DrawModeFlags::Default ); + bRestoreDrawMode = true; + } + + // format/draw formulas always from left to right + // and numbers should not be converted + ComplexTextLayoutFlags nLayoutMode = rDev.GetLayoutMode(); + rDev.SetLayoutMode( ComplexTextLayoutFlags::Default ); + LanguageType nDigitLang = rDev.GetDigitLanguage(); + rDev.SetDigitLanguage( LANGUAGE_ENGLISH ); + + //Set selection if any + if(mpCursor && bDrawSelection){ + mpCursor->AnnotateSelection(); + SmSelectionDrawingVisitor(rDev, mpTree.get(), rPosition); + } + + //Drawing using visitor + SmDrawingVisitor(rDev, rPosition, mpTree.get()); + + + rDev.SetLayoutMode( nLayoutMode ); + rDev.SetDigitLanguage( nDigitLang ); + + if (bRestoreDrawMode) + rDev.SetDrawMode( nOldDrawMode ); +} + +Size SmDocShell::GetSize() +{ + Size aRet; + + if (!mpTree) + Parse(); + + if (mpTree) + { + ArrangeFormula(); + aRet = mpTree->GetSize(); + + if ( !aRet.Width() ) + aRet.setWidth( 2000 ); + else + aRet.AdjustWidth(maFormat.GetDistance( DIS_LEFTSPACE ) + + maFormat.GetDistance( DIS_RIGHTSPACE ) ); + if ( !aRet.Height() ) + aRet.setHeight( 1000 ); + else + aRet.AdjustHeight(maFormat.GetDistance( DIS_TOPSPACE ) + + maFormat.GetDistance( DIS_BOTTOMSPACE ) ); + } + + return aRet; +} + +void SmDocShell::InvalidateCursor(){ + mpCursor.reset(); +} + +SmCursor& SmDocShell::GetCursor(){ + if(!mpCursor) + mpCursor.reset(new SmCursor(mpTree.get(), this)); + return *mpCursor; +} + +bool SmDocShell::HasCursor() const { return mpCursor != nullptr; } + +SmPrinterAccess::SmPrinterAccess( SmDocShell &rDocShell ) +{ + pPrinter = rDocShell.GetPrt(); + if ( pPrinter ) + { + pPrinter->Push( PushFlags::MAPMODE ); + if ( SfxObjectCreateMode::EMBEDDED == rDocShell.GetCreateMode() ) + { + // if it is an embedded object (without its own printer) + // we change the MapMode temporarily. + //!If it is a document with its own printer the MapMode should + //!be set correct (once) elsewhere(!), in order to avoid numerous + //!superfluous pushing and popping of the MapMode when using + //!this class. + + const MapUnit eOld = pPrinter->GetMapMode().GetMapUnit(); + if ( MapUnit::Map100thMM != eOld ) + { + MapMode aMap( pPrinter->GetMapMode() ); + aMap.SetMapUnit( MapUnit::Map100thMM ); + Point aTmp( aMap.GetOrigin() ); + aTmp.setX( OutputDevice::LogicToLogic( aTmp.X(), eOld, MapUnit::Map100thMM ) ); + aTmp.setY( OutputDevice::LogicToLogic( aTmp.Y(), eOld, MapUnit::Map100thMM ) ); + aMap.SetOrigin( aTmp ); + pPrinter->SetMapMode( aMap ); + } + } + } + pRefDev = rDocShell.GetRefDev(); + if ( !pRefDev || pPrinter.get() == pRefDev.get() ) + return; + + pRefDev->Push( PushFlags::MAPMODE ); + if ( SfxObjectCreateMode::EMBEDDED != rDocShell.GetCreateMode() ) + return; + + // if it is an embedded object (without its own printer) + // we change the MapMode temporarily. + //!If it is a document with its own printer the MapMode should + //!be set correct (once) elsewhere(!), in order to avoid numerous + //!superfluous pushing and popping of the MapMode when using + //!this class. + + const MapUnit eOld = pRefDev->GetMapMode().GetMapUnit(); + if ( MapUnit::Map100thMM != eOld ) + { + MapMode aMap( pRefDev->GetMapMode() ); + aMap.SetMapUnit( MapUnit::Map100thMM ); + Point aTmp( aMap.GetOrigin() ); + aTmp.setX( OutputDevice::LogicToLogic( aTmp.X(), eOld, MapUnit::Map100thMM ) ); + aTmp.setY( OutputDevice::LogicToLogic( aTmp.Y(), eOld, MapUnit::Map100thMM ) ); + aMap.SetOrigin( aTmp ); + pRefDev->SetMapMode( aMap ); + } +} + +SmPrinterAccess::~SmPrinterAccess() +{ + if ( pPrinter ) + pPrinter->Pop(); + if ( pRefDev && pRefDev != pPrinter ) + pRefDev->Pop(); +} + +Printer* SmDocShell::GetPrt() +{ + if (SfxObjectCreateMode::EMBEDDED == GetCreateMode()) + { + // Normally the server provides the printer. But if it doesn't provide one (e.g. because + // there is no connection) it still can be the case that we know the printer because it + // has been passed on by the server in OnDocumentPrinterChanged and being kept temporarily. + Printer* pPrt = GetDocumentPrinter(); + if (!pPrt && mpTmpPrinter) + pPrt = mpTmpPrinter; + return pPrt; + } + else if (!mpPrinter) + { + auto pOptions = std::make_unique<SfxItemSet>( + GetPool(), + svl::Items< + SID_PRINTTITLE, SID_PRINTZOOM, + SID_NO_RIGHT_SPACES, SID_SAVE_ONLY_USED_SYMBOLS, + SID_AUTO_CLOSE_BRACKETS, SID_AUTO_CLOSE_BRACKETS>{}); + SmModule *pp = SM_MOD(); + pp->GetConfig()->ConfigToItemSet(*pOptions); + mpPrinter = VclPtr<SfxPrinter>::Create(std::move(pOptions)); + mpPrinter->SetMapMode(MapMode(MapUnit::Map100thMM)); + } + return mpPrinter; +} + +OutputDevice* SmDocShell::GetRefDev() +{ + if (SfxObjectCreateMode::EMBEDDED == GetCreateMode()) + { + OutputDevice* pOutDev = GetDocumentRefDev(); + if (pOutDev) + return pOutDev; + } + + return GetPrt(); +} + +void SmDocShell::SetPrinter( SfxPrinter *pNew ) +{ + mpPrinter.disposeAndClear(); + mpPrinter = pNew; //Transfer ownership + mpPrinter->SetMapMode( MapMode(MapUnit::Map100thMM) ); + SetFormulaArranged(false); + Repaint(); +} + +void SmDocShell::OnDocumentPrinterChanged( Printer *pPrt ) +{ + mpTmpPrinter = pPrt; + SetFormulaArranged(false); + Size aOldSize = GetVisArea().GetSize(); + Repaint(); + if( aOldSize != GetVisArea().GetSize() && !maText.isEmpty() ) + SetModified(); + mpTmpPrinter = nullptr; +} + +void SmDocShell::Repaint() +{ + bool bIsEnabled = IsEnableSetModified(); + if (bIsEnabled) + EnableSetModified( false ); + + SetFormulaArranged(false); + + Size aVisSize = GetSize(); + SetVisAreaSize(aVisSize); + SmViewShell* pViewSh = SmGetActiveView(); + if (pViewSh) + pViewSh->GetGraphicWindow().Invalidate(); + + if (bIsEnabled) + EnableSetModified(bIsEnabled); +} + +SmDocShell::SmDocShell( SfxModelFlags i_nSfxCreationFlags ) + : SfxObjectShell(i_nSfxCreationFlags) + , mpEditEngineItemPool(nullptr) + , mpPrinter(nullptr) + , mpTmpPrinter(nullptr) + , mnModifyCount(0) + , mbFormulaArranged(false) +{ + SvtLinguConfig().GetOptions(maLinguOptions); + + SetPool(&SfxGetpApp()->GetPool()); + + SmModule *pp = SM_MOD(); + maFormat = pp->GetConfig()->GetStandardFormat(); + + StartListening(maFormat); + StartListening(*pp->GetConfig()); + + SetBaseModel(new SmModel(this)); +} + +SmDocShell::~SmDocShell() +{ + SmModule *pp = SM_MOD(); + + EndListening(maFormat); + EndListening(*pp->GetConfig()); + + mpCursor.reset(); + mpEditEngine.reset(); + SfxItemPool::Free(mpEditEngineItemPool); + mpPrinter.disposeAndClear(); +} + +bool SmDocShell::ConvertFrom(SfxMedium &rMedium) +{ + bool bSuccess = false; + const OUString& rFltName = rMedium.GetFilter()->GetFilterName(); + + OSL_ENSURE( rFltName != STAROFFICE_XML, "Wrong filter!"); + + if ( rFltName == MATHML_XML ) + { + if (mpTree) + { + mpTree.reset(); + InvalidateCursor(); + } + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLImportWrapper aEquation(xModel); + bSuccess = ( ERRCODE_NONE == aEquation.Import(rMedium) ); + } + else + { + SvStream *pStream = rMedium.GetInStream(); + if ( pStream ) + { + if ( SotStorage::IsStorageFile( pStream ) ) + { + tools::SvRef<SotStorage> aStorage = new SotStorage( pStream, false ); + if ( aStorage->IsStream("Equation Native") ) + { + // is this a MathType Storage? + OUStringBuffer aBuffer; + MathType aEquation(aBuffer); + bSuccess = aEquation.Parse( aStorage.get() ); + if ( bSuccess ) + { + maText = aBuffer.makeStringAndClear(); + Parse(); + } + } + } + } + } + + if ( GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + { + SetFormulaArranged( false ); + Repaint(); + } + + FinishedLoading(); + return bSuccess; +} + + +bool SmDocShell::InitNew( const uno::Reference < embed::XStorage >& xStorage ) +{ + bool bRet = false; + if ( SfxObjectShell::InitNew( xStorage ) ) + { + bRet = true; + SetVisArea(tools::Rectangle(Point(0, 0), Size(2000, 1000))); + } + return bRet; +} + + +bool SmDocShell::Load( SfxMedium& rMedium ) +{ + bool bRet = false; + if( SfxObjectShell::Load( rMedium )) + { + uno::Reference < embed::XStorage > xStorage = GetMedium()->GetStorage(); + if (xStorage->hasByName("content.xml") && xStorage->isStreamElement("content.xml")) + { + // is this a fabulous math package ? + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLImportWrapper aEquation(xModel); + auto nError = aEquation.Import(rMedium); + bRet = ERRCODE_NONE == nError; + SetError(nError); + } + } + + if ( GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + { + SetFormulaArranged( false ); + Repaint(); + } + + FinishedLoading(); + return bRet; +} + + +bool SmDocShell::Save() +{ + //! apply latest changes if necessary + UpdateText(); + + if ( SfxObjectShell::Save() ) + { + if (!mpTree) + Parse(); + if( mpTree ) + ArrangeFormula(); + + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLExportWrapper aEquation(xModel); + aEquation.SetFlat(false); + return aEquation.Export(*GetMedium()); + } + + return false; +} + +/* + * replace bad characters that can not be saved. (#i74144) + * */ +void SmDocShell::ReplaceBadChars() +{ + bool bReplace = false; + + if (!mpEditEngine) + return; + + OUStringBuffer aBuf( mpEditEngine->GetText() ); + + for (sal_Int32 i = 0; i < aBuf.getLength(); ++i) + { + if (aBuf[i] < ' ' && aBuf[i] != '\r' && aBuf[i] != '\n' && aBuf[i] != '\t') + { + aBuf[i] = ' '; + bReplace = true; + } + } + + if (bReplace) + maText = aBuf.makeStringAndClear(); +} + + +void SmDocShell::UpdateText() +{ + if (mpEditEngine && mpEditEngine->IsModified()) + { + OUString aEngTxt( mpEditEngine->GetText() ); + if (GetText() != aEngTxt) + SetText( aEngTxt ); + } +} + + +bool SmDocShell::SaveAs( SfxMedium& rMedium ) +{ + bool bRet = false; + + //! apply latest changes if necessary + UpdateText(); + + if ( SfxObjectShell::SaveAs( rMedium ) ) + { + if (!mpTree) + Parse(); + if( mpTree ) + ArrangeFormula(); + + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLExportWrapper aEquation(xModel); + aEquation.SetFlat(false); + bRet = aEquation.Export(rMedium); + } + return bRet; +} + +bool SmDocShell::ConvertTo( SfxMedium &rMedium ) +{ + bool bRet = false; + std::shared_ptr<const SfxFilter> pFlt = rMedium.GetFilter(); + if( pFlt ) + { + if( !mpTree ) + Parse(); + if( mpTree ) + ArrangeFormula(); + + const OUString& rFltName = pFlt->GetFilterName(); + if(rFltName == STAROFFICE_XML) + { + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLExportWrapper aEquation(xModel); + aEquation.SetFlat(false); + bRet = aEquation.Export(rMedium); + } + else if(rFltName == MATHML_XML) + { + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLExportWrapper aEquation(xModel); + aEquation.SetFlat(true); + bRet = aEquation.Export(rMedium); + } + else if (pFlt->GetFilterName() == "MathType 3.x") + bRet = WriteAsMathType3( rMedium ); + } + return bRet; +} + +void SmDocShell::writeFormulaOoxml( + ::sax_fastparser::FSHelperPtr const& pSerializer, + oox::core::OoxmlVersion const version, + oox::drawingml::DocumentType const documentType, + const sal_Int8 nAlign) +{ + if( !mpTree ) + Parse(); + if( mpTree ) + ArrangeFormula(); + SmOoxmlExport aEquation(mpTree.get(), version, documentType); + if(documentType == oox::drawingml::DOCUMENT_DOCX) + aEquation.ConvertFromStarMath( pSerializer, nAlign); + else + aEquation.ConvertFromStarMath(pSerializer, oox::FormulaExportBase::eFormulaAlign::INLINE); +} + +void SmDocShell::writeFormulaRtf(OStringBuffer& rBuffer, rtl_TextEncoding nEncoding) +{ + if (!mpTree) + Parse(); + if (mpTree) + ArrangeFormula(); + SmRtfExport aEquation(mpTree.get()); + aEquation.ConvertFromStarMath(rBuffer, nEncoding); +} + +void SmDocShell::readFormulaOoxml( oox::formulaimport::XmlStream& stream ) +{ + SmOoxmlImport aEquation( stream ); + SetText( aEquation.ConvertToStarMath()); +} + +void SmDocShell::Execute(SfxRequest& rReq) +{ + switch (rReq.GetSlot()) + { + case SID_TEXTMODE: + { + SmFormat aOldFormat = GetFormat(); + SmFormat aNewFormat( aOldFormat ); + aNewFormat.SetTextmode(!aOldFormat.IsTextmode()); + + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + break; + + case SID_AUTO_REDRAW : + { + SmModule *pp = SM_MOD(); + bool bRedraw = pp->GetConfig()->IsAutoRedraw(); + pp->GetConfig()->SetAutoRedraw(!bRedraw); + } + break; + + case SID_LOADSYMBOLS: + LoadSymbols(); + break; + + case SID_SAVESYMBOLS: + SaveSymbols(); + break; + + case SID_FONT: + { + // get device used to retrieve the FontList + OutputDevice *pDev = GetPrinter(); + if (!pDev || pDev->GetDevFontCount() == 0) + pDev = &SM_MOD()->GetDefaultVirtualDev(); + OSL_ENSURE (pDev, "device for font list missing" ); + + SmFontTypeDialog aFontTypeDialog(rReq.GetFrameWeld(), pDev); + + SmFormat aOldFormat = GetFormat(); + aFontTypeDialog.ReadFrom( aOldFormat ); + if (aFontTypeDialog.run() == RET_OK) + { + SmFormat aNewFormat( aOldFormat ); + + aFontTypeDialog.WriteTo(aNewFormat); + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + } + break; + + case SID_FONTSIZE: + { + SmFontSizeDialog aFontSizeDialog(rReq.GetFrameWeld()); + + SmFormat aOldFormat = GetFormat(); + aFontSizeDialog.ReadFrom( aOldFormat ); + if (aFontSizeDialog.run() == RET_OK) + { + SmFormat aNewFormat( aOldFormat ); + + aFontSizeDialog.WriteTo(aNewFormat); + + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + } + break; + + case SID_DISTANCE: + { + SmDistanceDialog aDistanceDialog(rReq.GetFrameWeld()); + + SmFormat aOldFormat = GetFormat(); + aDistanceDialog.ReadFrom( aOldFormat ); + if (aDistanceDialog.run() == RET_OK) + { + SmFormat aNewFormat( aOldFormat ); + + aDistanceDialog.WriteTo(aNewFormat); + + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + } + break; + + case SID_ALIGN: + { + SmAlignDialog aAlignDialog(rReq.GetFrameWeld()); + + SmFormat aOldFormat = GetFormat(); + aAlignDialog.ReadFrom( aOldFormat ); + if (aAlignDialog.run() == RET_OK) + { + SmFormat aNewFormat( aOldFormat ); + + aAlignDialog.WriteTo(aNewFormat); + + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + aAlignDialog.WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt ); + + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + } + break; + + case SID_TEXT: + { + const SfxStringItem& rItem = static_cast<const SfxStringItem&>(rReq.GetArgs()->Get(SID_TEXT)); + if (GetText() != rItem.GetValue()) + SetText(rItem.GetValue()); + } + break; + + case SID_UNDO: + case SID_REDO: + { + SfxUndoManager* pTmpUndoMgr = GetUndoManager(); + if( pTmpUndoMgr ) + { + sal_uInt16 nId = rReq.GetSlot(), nCnt = 1; + const SfxItemSet* pArgs = rReq.GetArgs(); + const SfxPoolItem* pItem; + if( pArgs && SfxItemState::SET == pArgs->GetItemState( nId, false, &pItem )) + nCnt = static_cast<const SfxUInt16Item*>(pItem)->GetValue(); + + bool (SfxUndoManager:: *fnDo)(); + + size_t nCount; + if( SID_UNDO == rReq.GetSlot() ) + { + nCount = pTmpUndoMgr->GetUndoActionCount(); + fnDo = &SfxUndoManager::Undo; + } + else + { + nCount = pTmpUndoMgr->GetRedoActionCount(); + fnDo = &SfxUndoManager::Redo; + } + + try + { + for( ; nCnt && nCount; --nCnt, --nCount ) + (pTmpUndoMgr->*fnDo)(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("starmath"); + } + } + Repaint(); + UpdateText(); + SfxViewFrame* pFrm = SfxViewFrame::GetFirst( this ); + while( pFrm ) + { + SfxBindings& rBind = pFrm->GetBindings(); + rBind.Invalidate(SID_UNDO); + rBind.Invalidate(SID_REDO); + rBind.Invalidate(SID_REPEAT); + rBind.Invalidate(SID_CLEARHISTORY); + pFrm = SfxViewFrame::GetNext( *pFrm, this ); + } + } + break; + } + + rReq.Done(); +} + + +void SmDocShell::GetState(SfxItemSet &rSet) +{ + SfxWhichIter aIter(rSet); + + for (sal_uInt16 nWh = aIter.FirstWhich(); 0 != nWh; nWh = aIter.NextWhich()) + { + switch (nWh) + { + case SID_TEXTMODE: + rSet.Put(SfxBoolItem(SID_TEXTMODE, GetFormat().IsTextmode())); + break; + + case SID_DOCTEMPLATE : + rSet.DisableItem(SID_DOCTEMPLATE); + break; + + case SID_AUTO_REDRAW : + { + SmModule *pp = SM_MOD(); + bool bRedraw = pp->GetConfig()->IsAutoRedraw(); + + rSet.Put(SfxBoolItem(SID_AUTO_REDRAW, bRedraw)); + } + break; + + case SID_MODIFYSTATUS: + { + sal_Unicode cMod = ' '; + if (IsModified()) + cMod = '*'; + rSet.Put(SfxStringItem(SID_MODIFYSTATUS, OUString(cMod))); + } + break; + + case SID_TEXT: + rSet.Put(SfxStringItem(SID_TEXT, GetText())); + break; + + case SID_GAPHIC_SM: + //! very old (pre UNO) and ugly hack to invalidate the SmGraphicWindow. + //! If mnModifyCount gets changed then the call below will implicitly notify + //! SmGraphicController::StateChanged and there the window gets invalidated. + //! Thus all the 'mnModifyCount++' before invalidating this slot. + rSet.Put(SfxInt16Item(SID_GAPHIC_SM, mnModifyCount)); + break; + + case SID_UNDO: + case SID_REDO: + { + SfxViewFrame* pFrm = SfxViewFrame::GetFirst( this ); + if( pFrm ) + pFrm->GetSlotState( nWh, nullptr, &rSet ); + else + rSet.DisableItem( nWh ); + } + break; + + case SID_GETUNDOSTRINGS: + case SID_GETREDOSTRINGS: + { + SfxUndoManager* pTmpUndoMgr = GetUndoManager(); + if( pTmpUndoMgr ) + { + OUString(SfxUndoManager:: *fnGetComment)( size_t, bool const ) const; + + size_t nCount; + if( SID_GETUNDOSTRINGS == nWh ) + { + nCount = pTmpUndoMgr->GetUndoActionCount(); + fnGetComment = &SfxUndoManager::GetUndoActionComment; + } + else + { + nCount = pTmpUndoMgr->GetRedoActionCount(); + fnGetComment = &SfxUndoManager::GetRedoActionComment; + } + if (nCount) + { + OUStringBuffer aBuf; + for (size_t n = 0; n < nCount; ++n) + { + aBuf.append((pTmpUndoMgr->*fnGetComment)( n, SfxUndoManager::TopLevel )); + aBuf.append('\n'); + } + + SfxStringListItem aItem( nWh ); + aItem.SetString( aBuf.makeStringAndClear() ); + rSet.Put( aItem ); + } + } + else + rSet.DisableItem( nWh ); + } + break; + } + } +} + + +SfxUndoManager *SmDocShell::GetUndoManager() +{ + if (!mpEditEngine) + GetEditEngine(); + return &mpEditEngine->GetUndoManager(); +} + + +void SmDocShell::SaveSymbols() +{ + SmModule *pp = SM_MOD(); + pp->GetSymbolManager().Save(); +} + + +void SmDocShell::Draw(OutputDevice *pDevice, + const JobSetup &, + sal_uInt16 /*nAspect*/) +{ + pDevice->IntersectClipRegion(GetVisArea()); + Point atmppoint; + DrawFormula(*pDevice, atmppoint); +} + +SfxItemPool& SmDocShell::GetPool() +{ + return SfxGetpApp()->GetPool(); +} + +void SmDocShell::SetVisArea(const tools::Rectangle & rVisArea) +{ + tools::Rectangle aNewRect(rVisArea); + + aNewRect.SetPos(Point()); + + if (aNewRect.IsWidthEmpty()) + aNewRect.SetRight( 2000 ); + if (aNewRect.IsHeightEmpty()) + aNewRect.SetBottom( 1000 ); + + bool bIsEnabled = IsEnableSetModified(); + if ( bIsEnabled ) + EnableSetModified( false ); + + //TODO/LATER: it's unclear how this interacts with the SFX code + // If outplace editing, then don't resize the OutplaceWindow. But the + // ObjectShell has to resize. + bool bUnLockFrame; + if( GetCreateMode() == SfxObjectCreateMode::EMBEDDED && !IsInPlaceActive() && GetFrame() ) + { + GetFrame()->LockAdjustPosSizePixel(); + bUnLockFrame = true; + } + else + bUnLockFrame = false; + + SfxObjectShell::SetVisArea( aNewRect ); + + if( bUnLockFrame ) + GetFrame()->UnlockAdjustPosSizePixel(); + + if ( bIsEnabled ) + EnableSetModified( bIsEnabled ); +} + + +void SmDocShell::FillClass(SvGlobalName* pClassName, + SotClipboardFormatId* pFormat, + OUString* pFullTypeName, + sal_Int32 nFileFormat, + bool bTemplate /* = false */) const +{ + if (nFileFormat == SOFFICE_FILEFORMAT_60 ) + { + *pClassName = SvGlobalName(SO3_SM_CLASSID_60); + *pFormat = SotClipboardFormatId::STARMATH_60; + *pFullTypeName = SmResId(STR_MATH_DOCUMENT_FULLTYPE_CURRENT); + } + else if (nFileFormat == SOFFICE_FILEFORMAT_8 ) + { + *pClassName = SvGlobalName(SO3_SM_CLASSID_60); + *pFormat = bTemplate ? SotClipboardFormatId::STARMATH_8_TEMPLATE : SotClipboardFormatId::STARMATH_8; + *pFullTypeName = SmResId(STR_MATH_DOCUMENT_FULLTYPE_CURRENT); + } +} + +void SmDocShell::SetModified(bool bModified) +{ + if( IsEnableSetModified() ) + { + SfxObjectShell::SetModified( bModified ); + Broadcast(SfxHint(SfxHintId::DocChanged)); + } +} + +bool SmDocShell::WriteAsMathType3( SfxMedium& rMedium ) +{ + OUStringBuffer aTextAsBuffer(maText); + MathType aEquation(aTextAsBuffer, mpTree.get()); + return aEquation.ConvertFromStarMath( rMedium ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/edit.cxx b/starmath/source/edit.cxx new file mode 100644 index 000000000..755bb3b5c --- /dev/null +++ b/starmath/source/edit.cxx @@ -0,0 +1,999 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <starmath.hrc> +#include <helpids.h> + +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/settings.hxx> + +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/stritem.hxx> +#include <sfx2/viewfrm.hxx> +#include <svx/AccessibleTextHelper.hxx> +#include <svtools/colorcfg.hxx> +#include <osl/diagnose.h> + +#include <edit.hxx> +#include <smmod.hxx> +#include <view.hxx> +#include <document.hxx> +#include "cfgitem.hxx" +#include "accessibility.hxx" +#include <memory> + +#define SCROLL_LINE 24 + + +using namespace com::sun::star::accessibility; +using namespace com::sun::star; + + +void SmGetLeftSelectionPart(const ESelection &rSel, + sal_Int32 &nPara, sal_uInt16 &nPos) + // returns paragraph number and position of the selections left part +{ + // compare start and end of selection and use the one that comes first + if ( rSel.nStartPara < rSel.nEndPara + || (rSel.nStartPara == rSel.nEndPara && rSel.nStartPos < rSel.nEndPos) ) + { nPara = rSel.nStartPara; + nPos = rSel.nStartPos; + } + else + { nPara = rSel.nEndPara; + nPos = rSel.nEndPos; + } +} + +bool SmEditWindow::IsInlineEditEnabled() +{ + SmViewShell *pView = GetView(); + return pView && pView->IsInlineEditEnabled(); +} + + +SmEditWindow::SmEditWindow( SmCmdBoxWindow &rMyCmdBoxWin ) : + Window (&rMyCmdBoxWin, WB_BORDER), + DropTargetHelper ( this ), + rCmdBox (rMyCmdBoxWin), + aModifyIdle ("SmEditWindow ModifyIdle"), + aCursorMoveIdle ("SmEditWindow CursorMoveIdle") +{ + set_id("math_edit"); + SetHelpId(HID_SMA_COMMAND_WIN_EDIT); + SetMapMode(MapMode(MapUnit::MapPixel)); + + // Even RTL languages don't use RTL for math + EnableRTL( false ); + + // compare DataChanged + SetBackground( GetSettings().GetStyleSettings().GetWindowColor() ); + + aModifyIdle.SetInvokeHandler(LINK(this, SmEditWindow, ModifyTimerHdl)); + aModifyIdle.SetPriority(TaskPriority::LOWEST); + + if (!IsInlineEditEnabled()) + { + aCursorMoveIdle.SetInvokeHandler(LINK(this, SmEditWindow, CursorMoveTimerHdl)); + aCursorMoveIdle.SetPriority(TaskPriority::LOWEST); + } + + // if not called explicitly the this edit window within the + // command window will just show an empty gray panel. + Show(); +} + + +SmEditWindow::~SmEditWindow() +{ + disposeOnce(); +} + +void SmEditWindow::dispose() +{ + aModifyIdle.Stop(); + + StartCursorMove(); + + // clean up of classes used for accessibility + // must be done before EditView (and thus EditEngine) is no longer + // available for those classes. + if (mxAccessible.is()) + { + mxAccessible->ClearWin(); // make Accessible nonfunctional + mxAccessible.clear(); + } + + if (pEditView) + { + EditEngine *pEditEngine = pEditView->GetEditEngine(); + if (pEditEngine) + { + pEditEngine->SetStatusEventHdl( Link<EditStatus&,void>() ); + pEditEngine->RemoveView( pEditView.get() ); + } + pEditView.reset(); + } + + pHScrollBar.disposeAndClear(); + pVScrollBar.disposeAndClear(); + pScrollBox.disposeAndClear(); + + DropTargetHelper::dispose(); + vcl::Window::dispose(); +} + +void SmEditWindow::StartCursorMove() +{ + if (!IsInlineEditEnabled()) + aCursorMoveIdle.Stop(); +} + +void SmEditWindow::InvalidateSlots() +{ + SfxBindings& rBind = GetView()->GetViewFrame()->GetBindings(); + rBind.Invalidate(SID_COPY); + rBind.Invalidate(SID_CUT); + rBind.Invalidate(SID_DELETE); +} + +SmViewShell * SmEditWindow::GetView() +{ + return rCmdBox.GetView(); +} + + +SmDocShell * SmEditWindow::GetDoc() +{ + SmViewShell *pView = rCmdBox.GetView(); + return pView ? pView->GetDoc() : nullptr; +} + +EditView * SmEditWindow::GetEditView() +{ + return pEditView.get(); +} + +EditEngine * SmEditWindow::GetEditEngine() +{ + EditEngine *pEditEng = nullptr; + if (pEditView) + pEditEng = pEditView->GetEditEngine(); + else + { + SmDocShell *pDoc = GetDoc(); + if (pDoc) + pEditEng = &pDoc->GetEditEngine(); + } + return pEditEng; +} + +void SmEditWindow::ApplySettings(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + rRenderContext.SetBackground(rStyleSettings.GetWindowColor()); +} + +void SmEditWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if (!((rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)))) + return; + + EditEngine *pEditEngine = GetEditEngine(); + SmDocShell *pDoc = GetDoc(); + + if (pEditEngine && pDoc) + { + //! + //! see also SmDocShell::GetEditEngine() ! + //! + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + pDoc->UpdateEditEngineDefaultFonts(rStyleSettings.GetFieldTextColor()); + pEditEngine->SetBackgroundColor(rStyleSettings.GetFieldColor()); + pEditEngine->SetDefTab(sal_uInt16(GetTextWidth("XXXX"))); + + // forces new settings to be used + // unfortunately this resets the whole edit engine + // thus we need to save at least the text + OUString aTxt( pEditEngine->GetText() ); + pEditEngine->Clear(); //incorrect font size + pEditEngine->SetText( aTxt ); + + AdjustScrollBars(); + Resize(); + } + + Invalidate(); +} + +IMPL_LINK_NOARG( SmEditWindow, ModifyTimerHdl, Timer *, void ) +{ + UpdateStatus(false); + aModifyIdle.Stop(); +} + +IMPL_LINK_NOARG(SmEditWindow, CursorMoveTimerHdl, Timer *, void) + // every once in a while check cursor position (selection) of edit + // window and if it has changed (try to) set the formula-cursor + // according to that. +{ + if (IsInlineEditEnabled()) + return; + + ESelection aNewSelection(GetSelection()); + + if (aNewSelection != aOldSelection) + { + SmViewShell *pView = rCmdBox.GetView(); + if (pView) + { + // get row and column to look for + sal_Int32 nRow; + sal_uInt16 nCol; + SmGetLeftSelectionPart(aNewSelection, nRow, nCol); + nRow++; + nCol++; + pView->GetGraphicWindow().SetCursorPos(static_cast<sal_uInt16>(nRow), nCol); + aOldSelection = aNewSelection; + } + } + aCursorMoveIdle.Stop(); +} + +void SmEditWindow::Resize() +{ + if (!pEditView) + CreateEditView(); + + if (pEditView) + { + pEditView->SetOutputArea(AdjustScrollBars()); + pEditView->ShowCursor(); + + OSL_ENSURE( pEditView->GetEditEngine(), "EditEngine missing" ); + const long nMaxVisAreaStart = pEditView->GetEditEngine()->GetTextHeight() - + pEditView->GetOutputArea().GetHeight(); + if (pEditView->GetVisArea().Top() > nMaxVisAreaStart) + { + tools::Rectangle aVisArea(pEditView->GetVisArea() ); + aVisArea.SetTop( std::max<long>(nMaxVisAreaStart, 0) ); + aVisArea.SetSize(pEditView->GetOutputArea().GetSize()); + pEditView->SetVisArea(aVisArea); + pEditView->ShowCursor(); + } + InitScrollBars(); + } + Invalidate(); +} + +void SmEditWindow::MouseButtonUp(const MouseEvent &rEvt) +{ + if (pEditView) + pEditView->MouseButtonUp(rEvt); + else + Window::MouseButtonUp (rEvt); + + if (!IsInlineEditEnabled()) + CursorMoveTimerHdl(&aCursorMoveIdle); + InvalidateSlots(); +} + +void SmEditWindow::MouseButtonDown(const MouseEvent &rEvt) +{ + if (pEditView) + pEditView->MouseButtonDown(rEvt); + else + Window::MouseButtonDown (rEvt); + + GrabFocus(); +} + +void SmEditWindow::Command(const CommandEvent& rCEvt) +{ + //pass alt press/release to parent impl + if (rCEvt.GetCommand() == CommandEventId::ModKeyChange) + { + Window::Command(rCEvt); + return; + } + + bool bForwardEvt = true; + if (rCEvt.GetCommand() == CommandEventId::ContextMenu) + { + GetParent()->ToTop(); + + Point aPoint = rCEvt.GetMousePosPixel(); + SmViewShell* pViewSh = rCmdBox.GetView(); + if (pViewSh) + pViewSh->GetViewFrame()->GetDispatcher()->ExecutePopup("edit", this, &aPoint); + bForwardEvt = false; + } + else if (rCEvt.GetCommand() == CommandEventId::Wheel) + bForwardEvt = !HandleWheelCommands( rCEvt ); + + if (bForwardEvt) + { + if (pEditView) + pEditView->Command( rCEvt ); + else + Window::Command (rCEvt); + } +} + +bool SmEditWindow::HandleWheelCommands( const CommandEvent &rCEvt ) +{ + bool bCommandHandled = false; // true if the CommandEvent needs not + // to be passed on (because it has fully + // been taken care of). + + const CommandWheelData* pWData = rCEvt.GetWheelData(); + if (pWData) + { + if (CommandWheelMode::ZOOM == pWData->GetMode()) + bCommandHandled = true; // no zooming in Command window + else + bCommandHandled = HandleScrollCommand( rCEvt, pHScrollBar.get(), pVScrollBar.get()); + } + + return bCommandHandled; +} + +void SmEditWindow::KeyInput(const KeyEvent& rKEvt) +{ + if (rKEvt.GetKeyCode().GetCode() == KEY_ESCAPE) + { + bool bCallBase = true; + SfxViewShell* pViewShell = GetView(); + if ( dynamic_cast<const SmViewShell *>(pViewShell) ) + { + // Terminate possible InPlace mode + bCallBase = !pViewShell->Escape(); + } + if ( bCallBase ) + Window::KeyInput( rKEvt ); + } + else + { + StartCursorMove(); + + bool autoClose = false; + if (!pEditView) + CreateEditView(); + ESelection aSelection = pEditView->GetSelection(); + // as we don't support RTL in Math, we need to swap values from selection when they were done + // in RTL form + aSelection.Adjust(); + OUString selected = pEditView->GetEditEngine()->GetText(aSelection); + + // Check is auto close brackets/braces is disabled + SmModule *pMod = SM_MOD(); + if (pMod && !pMod->GetConfig()->IsAutoCloseBrackets()) + autoClose = false; + else if (selected.trim() == "<?>") + autoClose = true; + else if (selected.isEmpty() && !aSelection.HasRange()) + { + selected = pEditView->GetEditEngine()->GetText(aSelection.nEndPara); + if (!selected.isEmpty()) + { + sal_Int32 index = selected.indexOf("\n", aSelection.nEndPos); + if (index != -1) + { + selected = selected.copy(index, sal_Int32(aSelection.nEndPos-index)); + if (selected.trim().isEmpty()) + autoClose = true; + } + else + { + sal_Int32 length = selected.getLength(); + if (aSelection.nEndPos == length) + autoClose = true; + else + { + selected = selected.copy(aSelection.nEndPos); + if (selected.trim().isEmpty()) + autoClose = true; + } + } + } + else + autoClose = true; + } + + if ( !pEditView->PostKeyEvent(rKEvt) ) + { + SmViewShell *pView = GetView(); + if ( pView && !pView->KeyInput(rKEvt) ) + { + // F1 (help) leads to the destruction of this + Flush(); + if ( aModifyIdle.IsActive() ) + aModifyIdle.Stop(); + Window::KeyInput(rKEvt); + } + else + { + // SFX has maybe called a slot of the view and thus (because of a hack in SFX) + // set the focus to the view + SfxViewShell* pVShell = GetView(); + if ( dynamic_cast<const SmViewShell *>(pVShell) && + static_cast<SmViewShell*>(pVShell)->GetGraphicWindow().HasFocus() ) + { + GrabFocus(); + } + } + } + else + { + // have doc-shell modified only for formula input/change and not + // cursor travelling and such things... + SmDocShell *pDocShell = GetDoc(); + EditEngine *pEditEngine = GetEditEngine(); + if (pDocShell && pEditEngine) + pDocShell->SetModified(pEditEngine->IsModified()); + aModifyIdle.Start(); + } + + // get the current char of the key event + sal_Unicode cCharCode = rKEvt.GetCharCode(); + OUString sClose; + + if (cCharCode == '{') + sClose = " }"; + else if (cCharCode == '[') + sClose = " ]"; + else if (cCharCode == '(') + sClose = " )"; + + // auto close the current character only when needed + if (!sClose.isEmpty() && autoClose) + { + pEditView->InsertText(sClose); + // position it at center of brackets + aSelection.nStartPos += 2; + aSelection.nEndPos = aSelection.nStartPos; + pEditView->SetSelection(aSelection); + } + + InvalidateSlots(); + } +} + +void SmEditWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + if (!pEditView) + CreateEditView(); + pEditView->Paint(rRect, &rRenderContext); +} + +void SmEditWindow::CreateEditView() +{ + EditEngine *pEditEngine = GetEditEngine(); + + //! pEditEngine and pEditView may be 0. + //! For example when the program is used by the document-converter + if (pEditView || !pEditEngine) + return; + + pEditView.reset(new EditView(pEditEngine, this)); + pEditEngine->InsertView( pEditView.get() ); + + if (!pVScrollBar) + pVScrollBar = VclPtr<ScrollBar>::Create(this, WinBits(WB_VSCROLL)); + if (!pHScrollBar) + pHScrollBar = VclPtr<ScrollBar>::Create(this, WinBits(WB_HSCROLL)); + if (!pScrollBox) + pScrollBox = VclPtr<ScrollBarBox>::Create(this); + pVScrollBar->SetScrollHdl(LINK(this, SmEditWindow, ScrollHdl)); + pHScrollBar->SetScrollHdl(LINK(this, SmEditWindow, ScrollHdl)); + pVScrollBar->EnableDrag(); + pHScrollBar->EnableDrag(); + + pEditView->SetOutputArea(AdjustScrollBars()); + + ESelection eSelection; + + pEditView->SetSelection(eSelection); + PaintImmediately(); + pEditView->ShowCursor(); + + pEditEngine->SetStatusEventHdl( LINK(this, SmEditWindow, EditStatusHdl) ); + SetPointer(pEditView->GetPointer()); + + SetScrollBarRanges(); +} + + +IMPL_LINK_NOARG( SmEditWindow, EditStatusHdl, EditStatus&, void ) +{ + if (pEditView) + Resize(); +} + +IMPL_LINK( SmEditWindow, ScrollHdl, ScrollBar *, /*pScrollBar*/, void ) +{ + OSL_ENSURE(pEditView, "EditView missing"); + if (pEditView) + { + pEditView->SetVisArea(tools::Rectangle(Point(pHScrollBar->GetThumbPos(), + pVScrollBar->GetThumbPos()), + pEditView->GetVisArea().GetSize())); + pEditView->Invalidate(); + } +} + +tools::Rectangle SmEditWindow::AdjustScrollBars() +{ + const Size aOut( GetOutputSizePixel() ); + tools::Rectangle aRect( Point(), aOut ); + + if (pVScrollBar && pHScrollBar && pScrollBox) + { + const long nTmp = GetSettings().GetStyleSettings().GetScrollBarSize(); + Point aPt( aRect.TopRight() ); aPt.AdjustX( -(nTmp -1) ); + pVScrollBar->SetPosSizePixel( aPt, Size(nTmp, aOut.Height() - nTmp)); + + aPt = aRect.BottomLeft(); aPt.AdjustY( -(nTmp - 1) ); + pHScrollBar->SetPosSizePixel( aPt, Size(aOut.Width() - nTmp, nTmp)); + + aPt.setX( pHScrollBar->GetSizePixel().Width() ); + aPt.setY( pVScrollBar->GetSizePixel().Height() ); + pScrollBox->SetPosSizePixel(aPt, Size(nTmp, nTmp )); + + aRect.SetRight( aPt.X() - 2 ); + aRect.SetBottom( aPt.Y() - 2 ); + } + return aRect; +} + +void SmEditWindow::SetScrollBarRanges() +{ + // Extra method, not InitScrollBars, since it's also being used for EditEngine events + EditEngine *pEditEngine = GetEditEngine(); + if (pVScrollBar && pHScrollBar && pEditEngine && pEditView) + { + long nTmp = pEditEngine->GetTextHeight(); + pVScrollBar->SetRange(Range(0, nTmp)); + pVScrollBar->SetThumbPos(pEditView->GetVisArea().Top()); + + nTmp = pEditEngine->GetPaperSize().Width(); + pHScrollBar->SetRange(Range(0,nTmp)); + pHScrollBar->SetThumbPos(pEditView->GetVisArea().Left()); + } +} + +void SmEditWindow::InitScrollBars() +{ + if (!(pVScrollBar && pHScrollBar && pScrollBox && pEditView)) + return; + + const Size aOut( pEditView->GetOutputArea().GetSize() ); + pVScrollBar->SetVisibleSize(aOut.Height()); + pVScrollBar->SetPageSize(aOut.Height() * 8 / 10); + pVScrollBar->SetLineSize(aOut.Height() * 2 / 10); + + pHScrollBar->SetVisibleSize(aOut.Width()); + pHScrollBar->SetPageSize(aOut.Width() * 8 / 10); + pHScrollBar->SetLineSize(SCROLL_LINE ); + + SetScrollBarRanges(); + + pVScrollBar->Show(); + pHScrollBar->Show(); + pScrollBox->Show(); +} + + +OUString SmEditWindow::GetText() const +{ + OUString aText; + EditEngine *pEditEngine = const_cast< SmEditWindow* >(this)->GetEditEngine(); + OSL_ENSURE( pEditEngine, "EditEngine missing" ); + if (pEditEngine) + aText = pEditEngine->GetText(); + return aText; +} + + +void SmEditWindow::SetText(const OUString& rText) +{ + EditEngine *pEditEngine = GetEditEngine(); + OSL_ENSURE( pEditEngine, "EditEngine missing" ); + if (!pEditEngine || pEditEngine->IsModified()) + return; + + if (!pEditView) + CreateEditView(); + + ESelection eSelection = pEditView->GetSelection(); + + pEditEngine->SetText(rText); + pEditEngine->ClearModifyFlag(); + + // Restarting the timer here, prevents calling the handlers for other (currently inactive) + // math tasks + aModifyIdle.Start(); + + pEditView->SetSelection(eSelection); +} + + +void SmEditWindow::GetFocus() +{ + Window::GetFocus(); + + if (mxAccessible.is()) + { + // Note: will implicitly send the AccessibleStateType::FOCUSED event + ::accessibility::AccessibleTextHelper *pHelper = mxAccessible->GetTextHelper(); + if (pHelper) + pHelper->SetFocus(); + } + + if (!pEditView) + CreateEditView(); + EditEngine *pEditEngine = GetEditEngine(); + if (pEditEngine) + pEditEngine->SetStatusEventHdl( LINK(this, SmEditWindow, EditStatusHdl) ); + + //Let SmViewShell know we got focus + if(GetView() && IsInlineEditEnabled()) + GetView()->SetInsertIntoEditWindow(true); +} + + +void SmEditWindow::LoseFocus() +{ + EditEngine *pEditEngine = GetEditEngine(); + if (pEditEngine) + pEditEngine->SetStatusEventHdl( Link<EditStatus&,void>() ); + + Window::LoseFocus(); + + if (mxAccessible.is()) + { + // Note: will implicitly send the AccessibleStateType::FOCUSED event + ::accessibility::AccessibleTextHelper *pHelper = mxAccessible->GetTextHelper(); + if (pHelper) + pHelper->SetFocus(false); + } +} + + +bool SmEditWindow::IsAllSelected() const +{ + bool bRes = false; + EditEngine *pEditEngine = const_cast<SmEditWindow *>(this)->GetEditEngine(); + OSL_ENSURE( pEditView, "NULL pointer" ); + OSL_ENSURE( pEditEngine, "NULL pointer" ); + if (pEditEngine && pEditView) + { + ESelection eSelection( pEditView->GetSelection() ); + sal_Int32 nParaCnt = pEditEngine->GetParagraphCount(); + if (!(nParaCnt - 1)) + { + sal_Int32 nTextLen = pEditEngine->GetText().getLength(); + bRes = !eSelection.nStartPos && (eSelection.nEndPos == nTextLen - 1); + } + else + { + bRes = !eSelection.nStartPara && (eSelection.nEndPara == nParaCnt - 1); + } + } + return bRes; +} + +void SmEditWindow::SelectAll() +{ + OSL_ENSURE( pEditView, "NULL pointer" ); + if (pEditView) + { + // ALL as last two parameters refers to the end of the text + pEditView->SetSelection( ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) ); + } +} + +void SmEditWindow::MarkError(const Point &rPos) +{ + OSL_ENSURE( pEditView, "EditView missing" ); + if (pEditView) + { + const sal_uInt16 nCol = sal::static_int_cast< sal_uInt16 >(rPos.X()); + const sal_uInt16 nRow = sal::static_int_cast< sal_uInt16 >(rPos.Y() - 1); + + pEditView->SetSelection(ESelection(nRow, nCol - 1, nRow, nCol)); + GrabFocus(); + } +} + +// Makes selection to next <?> symbol +void SmEditWindow::SelNextMark() +{ + EditEngine *pEditEngine = GetEditEngine(); + OSL_ENSURE( pEditView, "NULL pointer" ); + OSL_ENSURE( pEditEngine, "NULL pointer" ); + if (!pEditEngine || !pEditView) + return; + + ESelection eSelection = pEditView->GetSelection(); + sal_Int32 nPos = eSelection.nEndPos; + sal_Int32 nCounts = pEditEngine->GetParagraphCount(); + + while (eSelection.nEndPara < nCounts) + { + OUString aText = pEditEngine->GetText(eSelection.nEndPara); + nPos = aText.indexOf("<?>", nPos); + if (nPos != -1) + { + pEditView->SetSelection(ESelection( + eSelection.nEndPara, nPos, eSelection.nEndPara, nPos + 3)); + break; + } + + nPos = 0; + eSelection.nEndPara++; + } +} + +void SmEditWindow::SelPrevMark() +{ + EditEngine *pEditEngine = GetEditEngine(); + OSL_ENSURE( pEditEngine, "NULL pointer" ); + OSL_ENSURE( pEditView, "NULL pointer" ); + if (!(pEditEngine && pEditView)) + return; + + ESelection eSelection = pEditView->GetSelection(); + sal_Int32 nPara = eSelection.nStartPara; + sal_Int32 nMax = eSelection.nStartPos; + OUString aText(pEditEngine->GetText(nPara)); + const OUString aMark("<?>"); + sal_Int32 nPos; + + while ( (nPos = aText.lastIndexOf(aMark, nMax)) < 0 ) + { + if (--nPara < 0) + return; + aText = pEditEngine->GetText(nPara); + nMax = aText.getLength(); + } + pEditView->SetSelection(ESelection(nPara, nPos, nPara, nPos + 3)); +} + +bool SmEditWindow::HasMark(const OUString& rText) + // returns true iff 'rText' contains a mark +{ + return rText.indexOf("<?>") != -1; +} + +void SmEditWindow::MouseMove(const MouseEvent &rEvt) +{ + if (pEditView) + pEditView->MouseMove(rEvt); +} + +sal_Int8 SmEditWindow::AcceptDrop( const AcceptDropEvent& /*rEvt*/ ) +{ + return DND_ACTION_NONE; +} + +sal_Int8 SmEditWindow::ExecuteDrop( const ExecuteDropEvent& /*rEvt*/ ) +{ + return DND_ACTION_NONE; +} + +ESelection SmEditWindow::GetSelection() const +{ + // pointer may be 0 when reloading a document and the old view + // was already destroyed + //(OSL_ENSURE( pEditView, "NULL pointer" ); + ESelection eSel; + if (pEditView) + eSel = pEditView->GetSelection(); + return eSel; +} + +void SmEditWindow::SetSelection(const ESelection &rSel) +{ + OSL_ENSURE( pEditView, "NULL pointer" ); + if (pEditView) + pEditView->SetSelection(rSel); + InvalidateSlots(); +} + +bool SmEditWindow::IsEmpty() const +{ + EditEngine *pEditEngine = const_cast<SmEditWindow *>(this)->GetEditEngine(); + bool bEmpty = ( pEditEngine && pEditEngine->GetTextLen() == 0 ); + return bEmpty; +} + +bool SmEditWindow::IsSelected() const +{ + return pEditView && pEditView->HasSelection(); +} + + +void SmEditWindow::UpdateStatus( bool bSetDocModified ) +{ + SmModule *pMod = SM_MOD(); + if (pMod && pMod->GetConfig()->IsAutoRedraw()) + Flush(); + if ( bSetDocModified ) + GetDoc()->SetModified(); +} + +void SmEditWindow::Cut() +{ + OSL_ENSURE( pEditView, "EditView missing" ); + if (pEditView) + { + pEditView->Cut(); + UpdateStatus(true); + } +} + +void SmEditWindow::Copy() +{ + OSL_ENSURE( pEditView, "EditView missing" ); + if (pEditView) + pEditView->Copy(); +} + +void SmEditWindow::Paste() +{ + OSL_ENSURE( pEditView, "EditView missing" ); + if (pEditView) + { + pEditView->Paste(); + UpdateStatus(true); + } +} + +void SmEditWindow::Delete() +{ + OSL_ENSURE( pEditView, "EditView missing" ); + if (pEditView) + { + pEditView->DeleteSelected(); + UpdateStatus(true); + } +} + +void SmEditWindow::InsertText(const OUString& rText) +{ + OSL_ENSURE( pEditView, "EditView missing" ); + if (!pEditView) + return; + + // Note: Insertion of a space in front of commands is done here and + // in SmEditWindow::InsertCommand. + ESelection aSelection = pEditView->GetSelection(); + OUString aCurrentFormula = pEditView->GetEditEngine()->GetText(); + sal_Int32 nStartIndex = 0; + + // get the start position (when we get a multi line formula) + for (sal_Int32 nParaPos = 0; nParaPos < aSelection.nStartPara; nParaPos++) + nStartIndex = aCurrentFormula.indexOf("\n", nStartIndex) + 1; + + nStartIndex += aSelection.nStartPos; + + // TODO: unify this function with the InsertCommand: The do the same thing for different + // callers + OUString string(rText); + + OUString selected(pEditView->GetSelected()); + // if we have text selected, use it in the first placeholder + if (!selected.isEmpty()) + string = string.replaceFirst("<?>", selected); + + // put a space before a new command if not in the beginning of a line + if (aSelection.nStartPos > 0 && aCurrentFormula[nStartIndex - 1] != ' ') + string = " " + string; + + /* + fdo#65588 - Elements Dock: Scrollbar moves into input window + This change "solves" the visual problem. But I don't think so + this is the best solution. + */ + pVScrollBar->Hide(); + pHScrollBar->Hide(); + pEditView->InsertText(string); + AdjustScrollBars(); + pVScrollBar->Show(); + pHScrollBar->Show(); + + // Remember start of the selection and move the cursor there afterwards. + aSelection.nEndPara = aSelection.nStartPara; + if (HasMark(string)) + { + aSelection.nEndPos = aSelection.nStartPos; + pEditView->SetSelection(aSelection); + SelNextMark(); + } + else + { // set selection after inserted text + aSelection.nEndPos = aSelection.nStartPos + string.getLength(); + aSelection.nStartPos = aSelection.nEndPos; + pEditView->SetSelection(aSelection); + } + + aModifyIdle.Start(); + StartCursorMove(); + GrabFocus(); +} + +void SmEditWindow::Flush() +{ + EditEngine *pEditEngine = GetEditEngine(); + if (pEditEngine && pEditEngine->IsModified()) + { + pEditEngine->ClearModifyFlag(); + SmViewShell *pViewSh = rCmdBox.GetView(); + if (pViewSh) + { + std::unique_ptr<SfxStringItem> pTextToFlush = std::make_unique<SfxStringItem>(SID_TEXT, GetText()); + pViewSh->GetViewFrame()->GetDispatcher()->ExecuteList( + SID_TEXT, SfxCallMode::RECORD, + { pTextToFlush.get() }); + } + } + if (aCursorMoveIdle.IsActive()) + { + aCursorMoveIdle.Stop(); + CursorMoveTimerHdl(&aCursorMoveIdle); + } +} + +void SmEditWindow::DeleteEditView() +{ + if (pEditView) + { + std::unique_ptr<EditEngine> xEditEngine(pEditView->GetEditEngine()); + if (xEditEngine) + { + xEditEngine->SetStatusEventHdl( Link<EditStatus&,void>() ); + xEditEngine->RemoveView( pEditView.get() ); + } + pEditView.reset(); + } +} + +uno::Reference< XAccessible > SmEditWindow::CreateAccessible() +{ + if (!mxAccessible.is()) + { + mxAccessible.set(new SmEditAccessible( this )); + mxAccessible->Init(); + } + return uno::Reference< XAccessible >(mxAccessible.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/eqnolefilehdr.cxx b/starmath/source/eqnolefilehdr.cxx new file mode 100644 index 000000000..f211f1aaa --- /dev/null +++ b/starmath/source/eqnolefilehdr.cxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "eqnolefilehdr.hxx" +#include <sot/storage.hxx> + + +bool GetMathTypeVersion( SotStorage* pStor, sal_uInt8 &nVersion ) +{ + sal_uInt8 nVer = 0; + bool bSuccess = false; + + + // code snippet copied from MathType::Parse + + tools::SvRef<SotStorageStream> xSrc = pStor->OpenSotStream( + "Equation Native", + StreamMode::STD_READ); + if ( (!xSrc.is()) || (ERRCODE_NONE != xSrc->GetError())) + return bSuccess; + SotStorageStream *pS = xSrc.get(); + pS->SetEndian( SvStreamEndian::LITTLE ); + + EQNOLEFILEHDR aHdr; + aHdr.Read(pS); + pS->ReadUChar( nVer ); + + if (!pS->GetError()) + { + nVersion = nVer; + bSuccess = true; + } + return bSuccess; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/eqnolefilehdr.hxx b/starmath/source/eqnolefilehdr.hxx new file mode 100644 index 000000000..29f93cd00 --- /dev/null +++ b/starmath/source/eqnolefilehdr.hxx @@ -0,0 +1,77 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_EQNOLEFILEHDR_HXX +#define INCLUDED_STARMATH_SOURCE_EQNOLEFILEHDR_HXX + +#include <sal/types.h> +#include <tools/stream.hxx> + +class SotStorage; + +#define EQNOLEFILEHDR_SIZE 28 + +class EQNOLEFILEHDR +{ +public: + EQNOLEFILEHDR() : nCBHdr(0),nVersion(0), + nCf(0),nCBObject(0),nReserved1(0),nReserved2(0), + nReserved3(0), nReserved4(0) {} + explicit EQNOLEFILEHDR(sal_uInt32 nLenMTEF) : nCBHdr(0x1c),nVersion(0x20000), + nCf(0xc1c6),nCBObject(nLenMTEF),nReserved1(0),nReserved2(0x0014F690), + nReserved3(0x0014EBB4), nReserved4(0) {} + + sal_uInt16 nCBHdr; // length of header, sizeof(EQNOLEFILEHDR) = 28 + sal_uInt32 nVersion; // hiword = 2, loword = 0 + sal_uInt16 nCf; // clipboard format ("MathType EF") + sal_uInt32 nCBObject; // length of MTEF data following this header + sal_uInt32 nReserved1; // not used + sal_uInt32 nReserved2; // not used + sal_uInt32 nReserved3; // not used + sal_uInt32 nReserved4; // not used + + void Read(SvStream* pS) + { + pS->ReadUInt16( nCBHdr ); + pS->ReadUInt32( nVersion ); + pS->ReadUInt16( nCf ); + pS->ReadUInt32( nCBObject ); + pS->ReadUInt32( nReserved1 ); + pS->ReadUInt32( nReserved2 ); + pS->ReadUInt32( nReserved3 ); + pS->ReadUInt32( nReserved4 ); + } + void Write(SvStream* pS) + { + pS->WriteUInt16( nCBHdr ); + pS->WriteUInt32( nVersion ); + pS->WriteUInt16( nCf ); + pS->WriteUInt32( nCBObject ); + pS->WriteUInt32( nReserved1 ); + pS->WriteUInt32( nReserved2 ); + pS->WriteUInt32( nReserved3 ); + pS->WriteUInt32( nReserved4 ); + } +}; + +bool GetMathTypeVersion( SotStorage* pStor, sal_uInt8 &nVersion ); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/format.cxx b/starmath/source/format.cxx new file mode 100644 index 000000000..a4bf960fd --- /dev/null +++ b/starmath/source/format.cxx @@ -0,0 +1,153 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <format.hxx> + + +SmFormat::SmFormat() +: aBaseSize(0, SmPtsTo100th_mm(12)) +{ + eHorAlign = SmHorAlign::Center; + nGreekCharStyle = 0; + bIsTextmode = bScaleNormalBrackets = false; + + vSize[SIZ_TEXT] = 100; + vSize[SIZ_INDEX] = 60; + vSize[SIZ_FUNCTION] = + vSize[SIZ_OPERATOR] = 100; + vSize[SIZ_LIMITS] = 60; + + vDist[DIS_HORIZONTAL] = 10; + vDist[DIS_VERTICAL] = 5; + vDist[DIS_ROOT] = 0; + vDist[DIS_SUPERSCRIPT] = + vDist[DIS_SUBSCRIPT] = 20; + vDist[DIS_NUMERATOR] = + vDist[DIS_DENOMINATOR] = 0; + vDist[DIS_FRACTION] = 10; + vDist[DIS_STROKEWIDTH] = 5; + vDist[DIS_UPPERLIMIT] = + vDist[DIS_LOWERLIMIT] = 0; + vDist[DIS_BRACKETSIZE] = + vDist[DIS_BRACKETSPACE] = 5; + vDist[DIS_MATRIXROW] = 3; + vDist[DIS_MATRIXCOL] = 30; + vDist[DIS_ORNAMENTSIZE] = + vDist[DIS_ORNAMENTSPACE] = 0; + vDist[DIS_OPERATORSIZE] = 50; + vDist[DIS_OPERATORSPACE] = 20; + vDist[DIS_LEFTSPACE] = + vDist[DIS_RIGHTSPACE] = 100; + vDist[DIS_TOPSPACE] = + vDist[DIS_BOTTOMSPACE] = + vDist[DIS_NORMALBRACKETSIZE] = 0; + + vFont[FNT_VARIABLE] = + vFont[FNT_FUNCTION] = + vFont[FNT_NUMBER] = + vFont[FNT_TEXT] = + vFont[FNT_SERIF] = SmFace(FNTNAME_TIMES, aBaseSize); + vFont[FNT_SANS] = SmFace(FNTNAME_HELV, aBaseSize); + vFont[FNT_FIXED] = SmFace(FNTNAME_COUR, aBaseSize); + vFont[FNT_MATH] = SmFace(FNTNAME_MATH, aBaseSize); + + vFont[FNT_MATH].SetCharSet( RTL_TEXTENCODING_UNICODE ); + + vFont[FNT_VARIABLE].SetItalic(ITALIC_NORMAL); + vFont[FNT_FUNCTION].SetItalic(ITALIC_NONE); + vFont[FNT_NUMBER] .SetItalic(ITALIC_NONE); + vFont[FNT_TEXT] .SetItalic(ITALIC_NONE); + vFont[FNT_SERIF] .SetItalic(ITALIC_NONE); + vFont[FNT_SANS] .SetItalic(ITALIC_NONE); + vFont[FNT_FIXED] .SetItalic(ITALIC_NONE); + + for ( sal_uInt16 i = FNT_BEGIN; i <= FNT_END; i++ ) + { + SmFace &rFace = vFont[i]; + rFace.SetTransparent( true ); + rFace.SetAlignment( ALIGN_BASELINE ); + rFace.SetColor( COL_AUTO ); + bDefaultFont[i] = false; + } +} + + +void SmFormat::SetFont(sal_uInt16 nIdent, const SmFace &rFont, bool bDefault ) +{ + vFont[nIdent] = rFont; + vFont[nIdent].SetTransparent( true ); + vFont[nIdent].SetAlignment( ALIGN_BASELINE ); + + bDefaultFont[nIdent] = bDefault; +} + +SmFormat & SmFormat::operator = (const SmFormat &rFormat) +{ + SetBaseSize(rFormat.GetBaseSize()); + SetHorAlign(rFormat.GetHorAlign()); + SetTextmode(rFormat.IsTextmode()); + SetGreekCharStyle(rFormat.GetGreekCharStyle()); + SetScaleNormalBrackets(rFormat.IsScaleNormalBrackets()); + + sal_uInt16 i; + for (i = FNT_BEGIN; i <= FNT_END; i++) + { + SetFont(i, rFormat.GetFont(i)); + SetDefaultFont(i, rFormat.IsDefaultFont(i)); + } + for (i = SIZ_BEGIN; i <= SIZ_END; i++) + SetRelSize(i, rFormat.GetRelSize(i)); + for (i = DIS_BEGIN; i <= DIS_END; i++) + SetDistance(i, rFormat.GetDistance(i)); + + return *this; +} + + +bool SmFormat::operator == (const SmFormat &rFormat) const +{ + bool bRes = aBaseSize == rFormat.aBaseSize && + eHorAlign == rFormat.eHorAlign && + nGreekCharStyle == rFormat.nGreekCharStyle && + bIsTextmode == rFormat.bIsTextmode && + bScaleNormalBrackets == rFormat.bScaleNormalBrackets; + + sal_uInt16 i; + for (i = 0; i <= SIZ_END && bRes; ++i) + { + if (vSize[i] != rFormat.vSize[i]) + bRes = false; + } + for (i = 0; i <= DIS_END && bRes; ++i) + { + if (vDist[i] != rFormat.vDist[i]) + bRes = false; + } + for (i = 0; i <= FNT_END && bRes; ++i) + { + if (vFont[i] != rFormat.vFont[i] || + bDefaultFont[i] != rFormat.bDefaultFont[i]) + bRes = false; + } + + return bRes; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathmlattr.cxx b/starmath/source/mathmlattr.cxx new file mode 100644 index 000000000..5e3f09df9 --- /dev/null +++ b/starmath/source/mathmlattr.cxx @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "mathmlattr.hxx" + +#include <unordered_map> + +static sal_Int32 ParseMathMLUnsignedNumber(const OUString &rStr, Fraction& rUN) +{ + auto nLen = rStr.getLength(); + sal_Int32 nDecimalPoint = -1; + sal_Int32 nIdx; + for (nIdx = 0; nIdx < nLen; nIdx++) + { + auto cD = rStr[nIdx]; + if (cD == u'.') + { + if (nDecimalPoint >= 0) + return -1; + nDecimalPoint = nIdx; + continue; + } + if (cD < u'0' || u'9' < cD) + break; + } + if (nIdx == 0 || (nIdx == 1 && nDecimalPoint == 0)) + return -1; + + rUN = Fraction(rStr.copy(0, nIdx).toDouble()); + + return nIdx; +} + +static sal_Int32 ParseMathMLNumber(const OUString &rStr, Fraction& rN) +{ + if (rStr.isEmpty()) + return -1; + bool bNegative = (rStr[0] == '-'); + sal_Int32 nOffset = bNegative ? 1 : 0; + auto nIdx = ParseMathMLUnsignedNumber(rStr.copy(nOffset), rN); + if (nIdx <= 0 || !rN.IsValid()) + return -1; + if (bNegative) + rN *= -1; + return nOffset + nIdx; +} + +sal_Int32 ParseMathMLAttributeLengthValue(const OUString &rStr, MathMLAttributeLengthValue& rV) +{ + auto nIdx = ParseMathMLNumber(rStr, rV.aNumber); + if (nIdx <= 0) + return -1; + OUString sRest = rStr.copy(nIdx); + if (sRest.isEmpty()) + { + rV.eUnit = MathMLLengthUnit::None; + return nIdx; + } + if (sRest.startsWith("em")) + { + rV.eUnit = MathMLLengthUnit::Em; + return nIdx + 2; + } + if (sRest.startsWith("ex")) + { + rV.eUnit = MathMLLengthUnit::Ex; + return nIdx + 2; + } + if (sRest.startsWith("px")) + { + rV.eUnit = MathMLLengthUnit::Px; + return nIdx + 2; + } + if (sRest.startsWith("in")) + { + rV.eUnit = MathMLLengthUnit::In; + return nIdx + 2; + } + if (sRest.startsWith("cm")) + { + rV.eUnit = MathMLLengthUnit::Cm; + return nIdx + 2; + } + if (sRest.startsWith("mm")) + { + rV.eUnit = MathMLLengthUnit::Mm; + return nIdx + 2; + } + if (sRest.startsWith("pt")) + { + rV.eUnit = MathMLLengthUnit::Pt; + return nIdx + 2; + } + if (sRest.startsWith("pc")) + { + rV.eUnit = MathMLLengthUnit::Pc; + return nIdx + 2; + } + if (sRest[0] == u'%') + { + rV.eUnit = MathMLLengthUnit::Percent; + return nIdx + 2; + } + return nIdx; +} + +bool GetMathMLMathvariantValue(const OUString &rStr, MathMLMathvariantValue& rV) +{ + static const std::unordered_map<OUString, MathMLMathvariantValue> aMap{ + {"normal", MathMLMathvariantValue::Normal}, + {"bold", MathMLMathvariantValue::Bold}, + {"italic", MathMLMathvariantValue::Italic}, + {"bold-italic", MathMLMathvariantValue::BoldItalic}, + {"double-struck", MathMLMathvariantValue::DoubleStruck}, + {"bold-fraktur", MathMLMathvariantValue::BoldFraktur}, + {"script", MathMLMathvariantValue::Script}, + {"bold-script", MathMLMathvariantValue::BoldScript}, + {"fraktur", MathMLMathvariantValue::Fraktur}, + {"sans-serif", MathMLMathvariantValue::SansSerif}, + {"bold-sans-serif", MathMLMathvariantValue::BoldSansSerif}, + {"sans-serif-italic", MathMLMathvariantValue::SansSerifItalic}, + {"sans-serif-bold-italic", MathMLMathvariantValue::SansSerifBoldItalic}, + {"monospace", MathMLMathvariantValue::Monospace}, + {"initial", MathMLMathvariantValue::Initial}, + {"tailed", MathMLMathvariantValue::Tailed}, + {"looped", MathMLMathvariantValue::Looped}, + {"stretched", MathMLMathvariantValue::Stretched} + }; + + auto it = aMap.find(rStr); + if (it != aMap.end()) + { + rV = it->second; + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathmlattr.hxx b/starmath/source/mathmlattr.hxx new file mode 100644 index 000000000..232e080e6 --- /dev/null +++ b/starmath/source/mathmlattr.hxx @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#ifndef INCLUDED_STARMATH_SOURCE_MATHMLATTR_HXX +#define INCLUDED_STARMATH_SOURCE_MATHMLATTR_HXX + +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <tools/fract.hxx> + +// MathML 3: 2.1.5.1 Syntax notation used in the MathML specification +// <https://www.w3.org/TR/MathML/chapter2.html#id.2.1.5.1> +// MathML 2: 2.4.4.2 Attributes with units +// <https://www.w3.org/TR/MathML2/chapter2.html#fund.attval> +// MathML 3: 2.1.5.2 Length Valued Attributes +// <https://www.w3.org/TR/MathML/chapter2.html#fund.units> + +enum class MathMLLengthUnit { + None, + Em, + Ex, + Px, + In, + Cm, + Mm, + Pt, + Pc, + Percent +}; + +struct MathMLAttributeLengthValue +{ + Fraction aNumber; + MathMLLengthUnit eUnit; + MathMLAttributeLengthValue() + : eUnit(MathMLLengthUnit::None) + { + } +}; + +sal_Int32 ParseMathMLAttributeLengthValue(const OUString &rStr, MathMLAttributeLengthValue& rV); + + +// MathML 3: 3.2.2 Mathematics style attributes common to token elements +// <https://www.w3.org/TR/MathML3/chapter3.html#presm.commatt> + +enum class MathMLMathvariantValue { + Normal, + Bold, + Italic, + BoldItalic, + DoubleStruck, + BoldFraktur, + Script, + BoldScript, + Fraktur, + SansSerif, + BoldSansSerif, + SansSerifItalic, + SansSerifBoldItalic, + Monospace, + Initial, + Tailed, + Looped, + Stretched +}; + +bool GetMathMLMathvariantValue(const OUString &rStr, MathMLMathvariantValue& rV); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathmlexport.cxx b/starmath/source/mathmlexport.cxx new file mode 100644 index 000000000..c619f22f4 --- /dev/null +++ b/starmath/source/mathmlexport.cxx @@ -0,0 +1,1526 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +/* + Warning: The SvXMLElementExport helper class creates the beginning and + closing tags of xml elements in its constructor and destructor, so there's + hidden stuff going on, on occasion the ordering of these classes declarations + may be significant +*/ + +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/uno/Any.h> + +#include <rtl/math.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <osl/diagnose.h> +#include <unotools/saveopt.hxx> +#include <sot/storage.hxx> +#include <svl/itemset.hxx> +#include <svl/stritem.hxx> +#include <comphelper/fileformat.h> +#include <comphelper/processfactory.hxx> +#include <unotools/streamwrap.hxx> +#include <sax/tools/converter.hxx> +#include <xmloff/xmlnmspe.hxx> +#include <xmloff/xmltoken.hxx> +#include <xmloff/nmspmap.hxx> +#include <xmloff/attrlist.hxx> +#include <comphelper/genericpropertyset.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> + +#include <memory> +#include <stack> + +#include "mathmlexport.hxx" +#include <strings.hrc> +#include <smmod.hxx> +#include <unomodel.hxx> +#include <document.hxx> +#include <utility.hxx> +#include "cfgitem.hxx" + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +namespace { + +bool IsInPrivateUseArea( sal_Unicode cChar ) { return 0xE000 <= cChar && cChar <= 0xF8FF; } + +sal_Unicode ConvertMathToMathML( sal_Unicode cChar ) +{ + sal_Unicode cRes = cChar; + if (IsInPrivateUseArea( cChar )) + { + SAL_WARN("starmath", "Error: private use area characters should no longer be in use!" ); + cRes = u'@'; // just some character that should easily be notice as odd in the context + } + return cRes; +} + +} + +bool SmXMLExportWrapper::Export(SfxMedium &rMedium) +{ + bool bRet=true; + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + + //Get model + uno::Reference< lang::XComponent > xModelComp = xModel; + + bool bEmbedded = false; + SmModel *pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel); + + SmDocShell *pDocShell = pModel ? + static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr; + if ( pDocShell && + SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode() ) + bEmbedded = true; + + uno::Reference<task::XStatusIndicator> xStatusIndicator; + if (!bEmbedded) + { + if (pDocShell /*&& pDocShell->GetMedium()*/) + { + OSL_ENSURE( pDocShell->GetMedium() == &rMedium, + "different SfxMedium found" ); + + SfxItemSet* pSet = rMedium.GetItemSet(); + if (pSet) + { + const SfxUnoAnyItem* pItem = static_cast<const SfxUnoAnyItem*>( + pSet->GetItem(SID_PROGRESS_STATUSBAR_CONTROL) ); + if (pItem) + pItem->GetValue() >>= xStatusIndicator; + } + } + + // set progress range and start status indicator + if (xStatusIndicator.is()) + { + sal_Int32 nProgressRange = bFlat ? 1 : 3; + xStatusIndicator->start(SmResId(STR_STATSTR_WRITING), + nProgressRange); + } + } + + + // create XPropertySet with three properties for status indicator + comphelper::PropertyMapEntry aInfoMap[] = + { + { OUString("UsePrettyPrinting"), 0, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::MAYBEVOID, 0}, + { OUString("BaseURI"), 0, + ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamRelPath"), 0, + ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamName"), 0, + ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + uno::Reference< beans::XPropertySet > xInfoSet( + comphelper::GenericPropertySet_CreateInstance( + new comphelper::PropertySetInfo( aInfoMap ) ) ); + + SvtSaveOptions aSaveOpt; + bool bUsePrettyPrinting( bFlat || aSaveOpt.IsPrettyPrinting() ); + xInfoSet->setPropertyValue( "UsePrettyPrinting", Any(bUsePrettyPrinting) ); + + // Set base URI + OUString sPropName( "BaseURI" ); + xInfoSet->setPropertyValue( sPropName, makeAny( rMedium.GetBaseURL( true ) ) ); + + sal_Int32 nSteps=0; + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + if (!bFlat) //Storage (Package) of Stream + { + uno::Reference < embed::XStorage > xStg = rMedium.GetOutputStorage(); + bool bOASIS = ( SotStorage::GetVersion( xStg ) > SOFFICE_FILEFORMAT_60 ); + + // TODO/LATER: handle the case of embedded links gracefully + if ( bEmbedded ) //&& !pStg->IsRoot() ) + { + OUString aName; + if ( rMedium.GetItemSet() ) + { + const SfxStringItem* pDocHierarchItem = static_cast<const SfxStringItem*>( + rMedium.GetItemSet()->GetItem(SID_DOC_HIERARCHICALNAME) ); + if ( pDocHierarchItem ) + aName = pDocHierarchItem->GetValue(); + } + + if ( !aName.isEmpty() ) + { + sPropName = "StreamRelPath"; + xInfoSet->setPropertyValue( sPropName, makeAny( aName ) ); + } + } + + if ( !bEmbedded ) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + bRet = WriteThroughComponent( + xStg, xModelComp, "meta.xml", xContext, xInfoSet, + (bOASIS ? "com.sun.star.comp.Math.XMLOasisMetaExporter" + : "com.sun.star.comp.Math.XMLMetaExporter")); + } + if ( bRet ) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + bRet = WriteThroughComponent( + xStg, xModelComp, "content.xml", xContext, xInfoSet, + "com.sun.star.comp.Math.XMLContentExporter"); + } + + if ( bRet ) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + bRet = WriteThroughComponent( + xStg, xModelComp, "settings.xml", xContext, xInfoSet, + (bOASIS ? "com.sun.star.comp.Math.XMLOasisSettingsExporter" + : "com.sun.star.comp.Math.XMLSettingsExporter") ); + } + } + else + { + SvStream *pStream = rMedium.GetOutStream(); + uno::Reference<io::XOutputStream> xOut( + new utl::OOutputStreamWrapper(*pStream) ); + + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + bRet = WriteThroughComponent( + xOut, xModelComp, xContext, xInfoSet, + "com.sun.star.comp.Math.XMLContentExporter"); + } + + if (xStatusIndicator.is()) + xStatusIndicator->end(); + + return bRet; +} + + +/// export through an XML exporter component (output stream version) +bool SmXMLExportWrapper::WriteThroughComponent( + const Reference<io::XOutputStream>& xOutputStream, + const Reference<XComponent>& xComponent, + Reference<uno::XComponentContext> const & rxContext, + Reference<beans::XPropertySet> const & rPropSet, + const char* pComponentName ) +{ + OSL_ENSURE(xOutputStream.is(), "I really need an output stream!"); + OSL_ENSURE(xComponent.is(), "Need component!"); + OSL_ENSURE(nullptr != pComponentName, "Need component name!"); + + // get component + Reference< xml::sax::XWriter > xSaxWriter = xml::sax::Writer::create(rxContext ); + + // connect XML writer to output stream + xSaxWriter->setOutputStream( xOutputStream ); + + // prepare arguments (prepend doc handler to given arguments) + Sequence<Any> aArgs( 2 ); + aArgs[0] <<= xSaxWriter; + aArgs[1] <<= rPropSet; + + // get filter component + Reference< document::XExporter > xExporter( + rxContext->getServiceManager()->createInstanceWithArgumentsAndContext(OUString::createFromAscii(pComponentName), aArgs, rxContext), + UNO_QUERY); + OSL_ENSURE( xExporter.is(), + "can't instantiate export filter component" ); + if ( !xExporter.is() ) + return false; + + + // connect model and filter + xExporter->setSourceDocument( xComponent ); + + // filter! + Reference < XFilter > xFilter( xExporter, UNO_QUERY ); + uno::Sequence< PropertyValue > aProps(0); + xFilter->filter( aProps ); + + auto pFilter = comphelper::getUnoTunnelImplementation<SmXMLExport>(xFilter); + return pFilter == nullptr || pFilter->GetSuccess(); +} + + +/// export through an XML exporter component (storage version) +bool SmXMLExportWrapper::WriteThroughComponent( + const Reference < embed::XStorage >& xStorage, + const Reference<XComponent>& xComponent, + const char* pStreamName, + Reference<uno::XComponentContext> const & rxContext, + Reference<beans::XPropertySet> const & rPropSet, + const char* pComponentName + ) +{ + OSL_ENSURE(xStorage.is(), "Need storage!"); + OSL_ENSURE(nullptr != pStreamName, "Need stream name!"); + + // open stream + Reference < io::XStream > xStream; + OUString sStreamName = OUString::createFromAscii(pStreamName); + try + { + xStream = xStorage->openStreamElement( sStreamName, + embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE ); + } + catch ( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("starmath", "Can't create output stream in package" ); + return false; + } + + uno::Reference < beans::XPropertySet > xSet( xStream, uno::UNO_QUERY ); + xSet->setPropertyValue( "MediaType", Any(OUString( "text/xml" )) ); + + // all streams must be encrypted in encrypted document + xSet->setPropertyValue( "UseCommonStoragePasswordEncryption", Any(true) ); + + // set Base URL + if ( rPropSet.is() ) + { + rPropSet->setPropertyValue( "StreamName", makeAny( sStreamName ) ); + } + + // write the stuff + bool bRet = WriteThroughComponent( xStream->getOutputStream(), xComponent, rxContext, + rPropSet, pComponentName ); + + return bRet; +} + +SmXMLExport::SmXMLExport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + OUString const & implementationName, SvXMLExportFlags nExportFlags) + : SvXMLExport(util::MeasureUnit::INCH, rContext, implementationName, XML_MATH, + nExportFlags) + , pTree(nullptr) + , bSuccess(false) +{ +} + +sal_Int64 SAL_CALL SmXMLExport::getSomething( + const uno::Sequence< sal_Int8 >& rId ) +{ + if ( isUnoTunnelId<SmXMLExport>(rId) ) + return sal::static_int_cast< sal_Int64 >(reinterpret_cast< sal_uIntPtr >(this)); + + return SvXMLExport::getSomething( rId ); +} + +namespace +{ + class theSmXMLExportUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSmXMLExportUnoTunnelId> {}; +} + +const uno::Sequence< sal_Int8 > & SmXMLExport::getUnoTunnelId() throw() +{ + return theSmXMLExportUnoTunnelId::get().getSeq(); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLExporter", SvXMLExportFlags::OASIS|SvXMLExportFlags::ALL)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLMetaExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLMetaExporter", SvXMLExportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLOasisMetaExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisMetaExporter", SvXMLExportFlags::OASIS|SvXMLExportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLSettingsExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLSettingsExporter", SvXMLExportFlags::SETTINGS)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLOasisSettingsExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisSettingsExporter", SvXMLExportFlags::OASIS|SvXMLExportFlags::SETTINGS)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLContentExporter_get_implementation(css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLContentExporter", SvXMLExportFlags::OASIS|SvXMLExportFlags::CONTENT)); +} + +ErrCode SmXMLExport::exportDoc(enum XMLTokenEnum eClass) +{ + if ( !(getExportFlags() & SvXMLExportFlags::CONTENT) ) + { + SvXMLExport::exportDoc( eClass ); + } + else + { + uno::Reference <frame::XModel> xModel = GetModel(); + SmModel *pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel); + + if (pModel) + { + SmDocShell *pDocShell = + static_cast<SmDocShell*>(pModel->GetObjectShell()); + pTree = pDocShell->GetFormulaTree(); + aText = pDocShell->GetText(); + } + + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + /*Add xmlns line*/ + SvXMLAttributeList &rList = GetAttrList(); + + // make use of a default namespace + 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) + GetNamespaceMap_().Add( OUString(), GetXMLToken(XML_N_MATH), XML_NAMESPACE_MATH ); + + rList.AddAttribute(GetNamespaceMap().GetAttrNameByKey(XML_NAMESPACE_MATH), + GetNamespaceMap().GetNameByKey( XML_NAMESPACE_MATH)); + + //I think we need something like ImplExportEntities(); + ExportContent_(); + GetDocHandler()->endDocument(); + } + + bSuccess=true; + return ERRCODE_NONE; +} + +void SmXMLExport::ExportContent_() +{ + uno::Reference <frame::XModel> xModel = GetModel(); + SmModel *pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel); + SmDocShell *pDocShell = pModel ? + static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr; + OSL_ENSURE( pDocShell, "doc shell missing" ); + + if (pDocShell && !pDocShell->GetFormat().IsTextmode()) + { + // If the Math equation is not in text mode, we attach a display="block" + // attribute on the <math> root. We don't do anything if it is in + // text mode, the default display="inline" value will be used. + AddAttribute(XML_NAMESPACE_MATH, XML_DISPLAY, XML_BLOCK); + } + SvXMLElementExport aEquation(*this, XML_NAMESPACE_MATH, XML_MATH, true, true); + std::unique_ptr<SvXMLElementExport> pSemantics; + + if (!aText.isEmpty()) + { + pSemantics.reset( new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_SEMANTICS, true, true) ); + } + + ExportNodes(pTree, 0); + + if (aText.isEmpty()) + return; + + // Convert symbol names + if (pDocShell) + { + SmParser &rParser = pDocShell->GetParser(); + bool bVal = rParser.IsExportSymbolNames(); + rParser.SetExportSymbolNames( true ); + auto pTmpTree = rParser.Parse( aText ); + aText = rParser.GetText(); + pTmpTree.reset(); + rParser.SetExportSymbolNames( bVal ); + } + + AddAttribute(XML_NAMESPACE_MATH, XML_ENCODING, + OUString("StarMath 5.0")); + SvXMLElementExport aAnnotation(*this, XML_NAMESPACE_MATH, + XML_ANNOTATION, true, false); + GetDocHandler()->characters( aText ); +} + +void SmXMLExport::GetViewSettings( Sequence < PropertyValue >& aProps) +{ + uno::Reference <frame::XModel> xModel = GetModel(); + if ( !xModel.is() ) + return; + + SmModel *pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel); + + if ( !pModel ) + return; + + SmDocShell *pDocShell = + static_cast<SmDocShell*>(pModel->GetObjectShell()); + if ( !pDocShell ) + return; + + aProps.realloc( 4 ); + PropertyValue *pValue = aProps.getArray(); + sal_Int32 nIndex = 0; + + tools::Rectangle aRect( pDocShell->GetVisArea() ); + + pValue[nIndex].Name = "ViewAreaTop"; + pValue[nIndex++].Value <<= aRect.Top(); + + pValue[nIndex].Name = "ViewAreaLeft"; + pValue[nIndex++].Value <<= aRect.Left(); + + pValue[nIndex].Name = "ViewAreaWidth"; + pValue[nIndex++].Value <<= aRect.GetWidth(); + + pValue[nIndex].Name = "ViewAreaHeight"; + pValue[nIndex++].Value <<= aRect.GetHeight(); +} + +void SmXMLExport::GetConfigurationSettings( Sequence < PropertyValue > & rProps) +{ + Reference < XPropertySet > xProps ( GetModel(), UNO_QUERY ); + if ( !xProps.is() ) + return; + + Reference< XPropertySetInfo > xPropertySetInfo = xProps->getPropertySetInfo(); + if (!xPropertySetInfo.is()) + return; + + Sequence< Property > aProps = xPropertySetInfo->getProperties(); + const sal_Int32 nCount = aProps.getLength(); + if (!nCount) + return; + + rProps.realloc(nCount); + SmMathConfig* pConfig = SM_MOD()->GetConfig(); + const bool bUsedSymbolsOnly = pConfig && pConfig->IsSaveOnlyUsedSymbols(); + + std::transform(aProps.begin(), aProps.end(), rProps.begin(), + [bUsedSymbolsOnly, &xProps](Property& prop) { + PropertyValue aRet; + if (prop.Name != "Formula" && prop.Name != "BasicLibraries" + && prop.Name != "DialogLibraries" + && prop.Name != "RuntimeUID") + { + aRet.Name = prop.Name; + OUString aActualName(prop.Name); + // handle 'save used symbols only' + if (bUsedSymbolsOnly && prop.Name == "Symbols") + aActualName = "UserDefinedSymbolsInUse"; + aRet.Value = xProps->getPropertyValue(aActualName); + } + return aRet; + }); +} + +void SmXMLExport::ExportLine(const SmNode *pNode, int nLevel) +{ + ExportExpression(pNode, nLevel); +} + +void SmXMLExport::ExportBinaryHorizontal(const SmNode *pNode, int nLevel) +{ + TG nGroup = pNode->GetToken().nGroup; + + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); + + // Unfold the binary tree structure as long as the nodes are SmBinHorNode + // with the same nGroup. This will reduce the number of nested <mrow> + // elements e.g. we only need three <mrow> levels to export + + // "a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l = + // a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l" + + // See https://www.libreoffice.org/bugzilla/show_bug.cgi?id=66081 + ::std::stack< const SmNode* > s; + s.push(pNode); + while (!s.empty()) + { + const SmNode *node = s.top(); + s.pop(); + if (node->GetType() != SmNodeType::BinHor || node->GetToken().nGroup != nGroup) + { + ExportNodes(node, nLevel+1); + continue; + } + const SmBinHorNode* binNode = static_cast<const SmBinHorNode*>(node); + s.push(binNode->RightOperand()); + s.push(binNode->Symbol()); + s.push(binNode->LeftOperand()); + } +} + +void SmXMLExport::ExportUnaryHorizontal(const SmNode *pNode, int nLevel) +{ + ExportExpression(pNode, nLevel); +} + +void SmXMLExport::ExportExpression(const SmNode *pNode, int nLevel, + bool bNoMrowContainer /*=false*/) +{ + std::unique_ptr<SvXMLElementExport> pRow; + size_t nSize = pNode->GetNumSubNodes(); + + // #i115443: nodes of type expression always need to be grouped with mrow statement + if (!bNoMrowContainer && + (nSize > 1 || pNode->GetType() == SmNodeType::Expression)) + pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true)); + + for (size_t i = 0; i < nSize; ++i) + { + if (const SmNode *pTemp = pNode->GetSubNode(i)) + ExportNodes(pTemp, nLevel+1); + } +} + +void SmXMLExport::ExportBinaryVertical(const SmNode *pNode, int nLevel) +{ + assert(pNode->GetNumSubNodes() == 3); + const SmNode *pNum = pNode->GetSubNode(0); + const SmNode *pDenom = pNode->GetSubNode(2); + if (pNum->GetType() == SmNodeType::Align && pNum->GetToken().eType != TALIGNC) + { + // A left or right alignment is specified on the numerator: + // attach the corresponding numalign attribute. + AddAttribute(XML_NAMESPACE_MATH, XML_NUMALIGN, + pNum->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT); + } + if (pDenom->GetType() == SmNodeType::Align && pDenom->GetToken().eType != TALIGNC) + { + // A left or right alignment is specified on the denominator: + // attach the corresponding denomalign attribute. + AddAttribute(XML_NAMESPACE_MATH, XML_DENOMALIGN, + pDenom->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT); + } + SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true); + ExportNodes(pNum, nLevel); + ExportNodes(pDenom, nLevel); +} + +void SmXMLExport::ExportBinaryDiagonal(const SmNode *pNode, int nLevel) +{ + assert(pNode->GetNumSubNodes() == 3); + + if (pNode->GetToken().eType == TWIDESLASH) + { + // wideslash + // export the node as <mfrac bevelled="true"> + AddAttribute(XML_NAMESPACE_MATH, XML_BEVELLED, XML_TRUE); + SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, + true, true); + ExportNodes(pNode->GetSubNode(0), nLevel); + ExportNodes(pNode->GetSubNode(1), nLevel); + } + else + { + // widebslash + // We can not use <mfrac> to a backslash, so just use <mo>\</mo> + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); + + ExportNodes(pNode->GetSubNode(0), nLevel); + + { // Scoping for <mo> creation + SvXMLElementExport aMo(*this, XML_NAMESPACE_MATH, XML_MO, + true, true); + sal_Unicode const nArse[2] = {MS_BACKSLASH,0x00}; + GetDocHandler()->characters(nArse); + } + + ExportNodes(pNode->GetSubNode(1), nLevel); + } +} + +void SmXMLExport::ExportTable(const SmNode *pNode, int nLevel) +{ + std::unique_ptr<SvXMLElementExport> pTable; + + size_t nSize = pNode->GetNumSubNodes(); + + //If the list ends in newline then the last entry has + //no subnodes, the newline is superfluous so we just drop + //the last node, inclusion would create a bad MathML + //table + if (nSize >= 1) + { + const SmNode *pLine = pNode->GetSubNode(nSize-1); + if (pLine->GetType() == SmNodeType::Line && pLine->GetNumSubNodes() == 1 && + pLine->GetSubNode(0) != nullptr && + pLine->GetSubNode(0)->GetToken().eType == TNEWLINE) + --nSize; + } + + // try to avoid creating a mtable element when the formula consists only + // of a single output line + if (nLevel || (nSize >1)) + pTable.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true)); + + for (size_t i = 0; i < nSize; ++i) + { + if (const SmNode *pTemp = pNode->GetSubNode(i)) + { + std::unique_ptr<SvXMLElementExport> pRow; + std::unique_ptr<SvXMLElementExport> pCell; + if (pTable) + { + pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTR, true, true)); + SmTokenType eAlign = TALIGNC; + if (pTemp->GetType() == SmNodeType::Align) + { + // For Binom() and Stack() constructions, the SmNodeType::Align nodes + // are direct children. + // binom{alignl ...}{alignr ...} and + // stack{alignl ... ## alignr ... ## ...} + eAlign = pTemp->GetToken().eType; + } + else if (pTemp->GetType() == SmNodeType::Line && + pTemp->GetNumSubNodes() == 1 && + pTemp->GetSubNode(0) && + pTemp->GetSubNode(0)->GetType() == SmNodeType::Align) + { + // For the Table() construction, the SmNodeType::Align node is a child + // of an SmNodeType::Line node. + // alignl ... newline alignr ... newline ... + eAlign = pTemp->GetSubNode(0)->GetToken().eType; + } + if (eAlign != TALIGNC) + { + // If a left or right alignment is specified on this line, + // attach the corresponding columnalign attribute. + AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN, + eAlign == TALIGNL ? XML_LEFT : XML_RIGHT); + } + pCell.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTD, true, true)); + } + ExportNodes(pTemp, nLevel+1); + } + } +} + +void SmXMLExport::ExportMath(const SmNode *pNode) +{ + const SmTextNode *pTemp = static_cast<const SmTextNode *>(pNode); + std::unique_ptr<SvXMLElementExport> pMath; + + if (pNode->GetType() == SmNodeType::Math || pNode->GetType() == SmNodeType::GlyphSpecial) + { + // Export SmNodeType::Math and SmNodeType::GlyphSpecial symbols as <mo> elements + pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MO, true, false)); + } + else if (pNode->GetType() == SmNodeType::Special) + { + bool bIsItalic = IsItalic(pNode->GetFont()); + if (!bIsItalic) + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL); + pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false)); + } + else + { + // Export SmNodeType::MathIdent and SmNodeType::Place symbols as <mi> elements: + // - These math symbols should not be drawn slanted. Hence we should + // attach a mathvariant="normal" attribute to single-char <mi> elements + // that are not mathematical alphanumeric symbol. For simplicity and to + // work around browser limitations, we always attach such an attribute. + // - The MathML specification suggests to use empty <mi> elements as + // placeholders but they won't be visible in most MathML rendering + // engines so let's use an empty square for SmNodeType::Place instead. + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL); + pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false)); + } + sal_Unicode nArse[2]; + nArse[0] = pTemp->GetText()[0]; + sal_Unicode cTmp = ConvertMathToMathML( nArse[0] ); + if (cTmp != 0) + nArse[0] = cTmp; + OSL_ENSURE(nArse[0] != 0xffff,"Non existent symbol"); + nArse[1] = 0; + GetDocHandler()->characters(nArse); +} + +void SmXMLExport::ExportText(const SmNode *pNode) +{ + std::unique_ptr<SvXMLElementExport> pText; + const SmTextNode *pTemp = static_cast<const SmTextNode *>(pNode); + switch (pNode->GetToken().eType) + { + default: + case TIDENT: + { + //Note that we change the fontstyle to italic for strings that + //are italic and longer than a single character. + bool bIsItalic = IsItalic( pTemp->GetFont() ); + if ((pTemp->GetText().getLength() > 1) && bIsItalic) + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_ITALIC); + else if ((pTemp->GetText().getLength() == 1) && !bIsItalic) + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL); + pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false)); + break; + } + case TNUMBER: + pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MN, true, false)); + break; + case TTEXT: + pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTEXT, true, false)); + break; + } + GetDocHandler()->characters(pTemp->GetText()); +} + +void SmXMLExport::ExportBlank(const SmNode *pNode) +{ + const SmBlankNode *pTemp = static_cast<const SmBlankNode *>(pNode); + //!! exports an <mspace> element. Note that for example "~_~" is allowed in + //!! Math (so it has no sense at all) but must not result in an empty + //!! <msub> tag in MathML !! + + if (pTemp->GetBlankNum() != 0) + { + // Attach a width attribute. We choose the (somewhat arbitrary) values + // ".5em" for a small gap '`' and "2em" for a large gap '~'. + // (see SmBlankNode::IncreaseBy for how pTemp->mnNum is set). + OUStringBuffer sStrBuf; + ::sax::Converter::convertDouble(sStrBuf, pTemp->GetBlankNum() * .5); + sStrBuf.append("em"); + AddAttribute(XML_NAMESPACE_MATH, XML_WIDTH, sStrBuf.getStr()); + } + + SvXMLElementExport aTextExport(*this, XML_NAMESPACE_MATH, XML_MSPACE, + true, false); + + GetDocHandler()->characters( OUString() ); +} + +void SmXMLExport::ExportSubSupScript(const SmNode *pNode, int nLevel) +{ + const SmNode *pSub = nullptr; + const SmNode *pSup = nullptr; + const SmNode *pCSub = nullptr; + const SmNode *pCSup = nullptr; + const SmNode *pLSub = nullptr; + const SmNode *pLSup = nullptr; + std::unique_ptr<SvXMLElementExport> pThing2; + + //if we have prescripts at all then we must use the tensor notation + + //This is one of those excellent locations where scope is vital to + //arrange the construction and destruction of the element helper + //classes correctly + pLSub = pNode->GetSubNode(LSUB+1); + pLSup = pNode->GetSubNode(LSUP+1); + if (pLSub || pLSup) + { + SvXMLElementExport aMultiScripts(*this, XML_NAMESPACE_MATH, + XML_MMULTISCRIPTS, true, true); + + + if (nullptr != (pCSub = pNode->GetSubNode(CSUB+1)) + && nullptr != (pCSup = pNode->GetSubNode(CSUP+1))) + { + pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_MUNDEROVER, true, true)); + } + else if (nullptr != (pCSub = pNode->GetSubNode(CSUB+1))) + { + pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_MUNDER, true, true)); + } + else if (nullptr != (pCSup = pNode->GetSubNode(CSUP+1))) + { + pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_MOVER, true, true)); + } + + ExportNodes(pNode->GetSubNode(0), nLevel+1); //Main Term + + if (pCSub) + ExportNodes(pCSub, nLevel+1); + if (pCSup) + ExportNodes(pCSup, nLevel+1); + pThing2.reset(); + + pSub = pNode->GetSubNode(RSUB+1); + pSup = pNode->GetSubNode(RSUP+1); + if (pSub || pSup) + { + if (pSub) + ExportNodes(pSub, nLevel+1); + else + { + SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true); + } + if (pSup) + ExportNodes(pSup, nLevel+1); + else + { + SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true); + } + } + + //Separator element between suffix and prefix sub/sup pairs + { + SvXMLElementExport aPrescripts(*this, XML_NAMESPACE_MATH, + XML_MPRESCRIPTS, true, true); + } + + if (pLSub) + ExportNodes(pLSub, nLevel+1); + else + { + SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, + true, true); + + } + if (pLSup) + ExportNodes(pLSup, nLevel+1); + else + { + SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, + true, true); + + } + } + else + { + std::unique_ptr<SvXMLElementExport> pThing; + if (nullptr != (pSub = pNode->GetSubNode(RSUB+1)) && + nullptr != (pSup = pNode->GetSubNode(RSUP+1))) + { + pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_MSUBSUP, true, true)); + } + else if (nullptr != (pSub = pNode->GetSubNode(RSUB+1))) + { + pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUB, + true, true)); + } + else if (nullptr != (pSup = pNode->GetSubNode(RSUP+1))) + { + pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUP, + true, true)); + } + + if (nullptr != (pCSub = pNode->GetSubNode(CSUB+1)) + && nullptr != (pCSup=pNode->GetSubNode(CSUP+1))) + { + pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_MUNDEROVER, true, true)); + } + else if (nullptr != (pCSub = pNode->GetSubNode(CSUB+1))) + { + pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_MUNDER, true, true)); + } + else if (nullptr != (pCSup = pNode->GetSubNode(CSUP+1))) + { + pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_MOVER, true, true)); + } + ExportNodes(pNode->GetSubNode(0), nLevel+1); //Main Term + + if (pCSub) + ExportNodes(pCSub, nLevel+1); + if (pCSup) + ExportNodes(pCSup, nLevel+1); + pThing2.reset(); + + if (pSub) + ExportNodes(pSub, nLevel+1); + if (pSup) + ExportNodes(pSup, nLevel+1); + pThing.reset(); + } +} + +void SmXMLExport::ExportBrace(const SmNode *pNode, int nLevel) +{ + const SmNode *pTemp; + const SmNode *pLeft=pNode->GetSubNode(0); + const SmNode *pRight=pNode->GetSubNode(2); + + // This used to generate <mfenced> or <mrow>+<mo> elements according to + // the stretchiness of fences. The MathML recommendation defines an + // <mrow>+<mo> construction that is equivalent to the <mfenced> element: + // http://www.w3.org/TR/MathML3/chapter3.html#presm.mfenced + // To simplify our code and avoid issues with mfenced implementations in + // MathML rendering engines, we now always generate <mrow>+<mo> elements. + // See #fdo 66282. + + // <mrow> + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, + true, true); + + // <mo fence="true"> opening-fence </mo> + if (pLeft && (pLeft->GetToken().eType != TNONE)) + { + AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE); + if (pNode->GetScaleMode() == SmScaleMode::Height) + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + else + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE); + ExportNodes(pLeft, nLevel+1); + } + + if (nullptr != (pTemp = pNode->GetSubNode(1))) + { + // <mrow> + SvXMLElementExport aRowExport(*this, XML_NAMESPACE_MATH, XML_MROW, + true, true); + ExportNodes(pTemp, nLevel+1); + // </mrow> + } + + // <mo fence="true"> closing-fence </mo> + if (pRight && (pRight->GetToken().eType != TNONE)) + { + AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE); + if (pNode->GetScaleMode() == SmScaleMode::Height) + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + else + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE); + ExportNodes(pRight, nLevel+1); + } + + // </mrow> +} + +void SmXMLExport::ExportRoot(const SmNode *pNode, int nLevel) +{ + if (pNode->GetSubNode(0)) + { + SvXMLElementExport aRoot(*this, XML_NAMESPACE_MATH, XML_MROOT, true, + true); + ExportNodes(pNode->GetSubNode(2), nLevel+1); + ExportNodes(pNode->GetSubNode(0), nLevel+1); + } + else + { + SvXMLElementExport aSqrt(*this, XML_NAMESPACE_MATH, XML_MSQRT, true, + true); + ExportNodes(pNode->GetSubNode(2), nLevel+1); + } +} + +void SmXMLExport::ExportOperator(const SmNode *pNode, int nLevel) +{ + /*we need to either use content or font and size attributes + *here*/ + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, + true, true); + ExportNodes(pNode->GetSubNode(0), nLevel+1); + ExportNodes(pNode->GetSubNode(1), nLevel+1); +} + +void SmXMLExport::ExportAttributes(const SmNode *pNode, int nLevel) +{ + std::unique_ptr<SvXMLElementExport> pElement; + + if (pNode->GetToken().eType == TUNDERLINE) + { + AddAttribute(XML_NAMESPACE_MATH, XML_ACCENTUNDER, + XML_TRUE); + pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, + true, true)); + } + else if (pNode->GetToken().eType == TOVERSTRIKE) + { + // export as <menclose notation="horizontalstrike"> + AddAttribute(XML_NAMESPACE_MATH, XML_NOTATION, XML_HORIZONTALSTRIKE); + pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, + XML_MENCLOSE, true, true)); + } + else + { + AddAttribute(XML_NAMESPACE_MATH, XML_ACCENT, + XML_TRUE); + pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, + true, true)); + } + + ExportNodes(pNode->GetSubNode(1), nLevel+1); + switch (pNode->GetToken().eType) + { + case TOVERLINE: + { + //proper entity support required + SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, + true, true); + sal_Unicode const nArse[2] = {0xAF,0x00}; + GetDocHandler()->characters(nArse); + } + break; + case TUNDERLINE: + { + //proper entity support required + SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, + true, true); + sal_Unicode const nArse[2] = {0x0332,0x00}; + GetDocHandler()->characters(nArse); + } + break; + case TOVERSTRIKE: + break; + case TWIDETILDE: + case TWIDEHAT: + case TWIDEVEC: + case TWIDEHARPOON: + { + // make these wide accents stretchy + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + ExportNodes(pNode->GetSubNode(0), nLevel+1); + } + break; + default: + ExportNodes(pNode->GetSubNode(0), nLevel+1); + break; + } +} + +static bool lcl_HasEffectOnMathvariant( const SmTokenType eType ) +{ + return eType == TBOLD || eType == TNBOLD || + eType == TITALIC || eType == TNITALIC || + eType == TSANS || eType == TSERIF || eType == TFIXED; +} + +void SmXMLExport::ExportFont(const SmNode *pNode, int nLevel) +{ + + // gather the mathvariant attribute relevant data from all + // successively following SmFontNodes... + + int nBold = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true; + int nItalic = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true; + int nSansSerifFixed = -1; + SmTokenType eNodeType = TUNKNOWN; + for (;;) + { + eNodeType = pNode->GetToken().eType; + if (!lcl_HasEffectOnMathvariant(eNodeType)) + break; + switch (eNodeType) + { + case TBOLD : nBold = 1; break; + case TNBOLD : nBold = 0; break; + case TITALIC : nItalic = 1; break; + case TNITALIC : nItalic = 0; break; + case TSANS : nSansSerifFixed = 0; break; + case TSERIF : nSansSerifFixed = 1; break; + case TFIXED : nSansSerifFixed = 2; break; + default: + SAL_WARN("starmath", "unexpected case"); + } + // According to the parser every node that is to be evaluated here + // has a single non-zero subnode at index 1!! Thus we only need to check + // that single node for follow-up nodes that have an effect on the attribute. + if (pNode->GetNumSubNodes() > 1 && pNode->GetSubNode(1) && + lcl_HasEffectOnMathvariant( pNode->GetSubNode(1)->GetToken().eType)) + { + pNode = pNode->GetSubNode(1); + } + else + break; + } + + switch (pNode->GetToken().eType) + { + case TPHANTOM: + // No attribute needed. An <mphantom> element will be used below. + break; + case TBLACK: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_BLACK); + break; + case TWHITE: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_WHITE); + break; + case TRED: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_RED); + break; + case TGREEN: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_GREEN); + break; + case TBLUE: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_BLUE); + break; + case TCYAN: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_AQUA); + break; + case TMAGENTA: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_FUCHSIA); + break; + case TYELLOW: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_YELLOW); + break; + case TSILVER: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_SILVER); + break; + case TGRAY: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_GRAY); + break; + case TMAROON: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_MAROON); + break; + case TOLIVE: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_OLIVE); + break; + case TLIME: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_LIME); + break; + case TAQUA: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_AQUA); + break; + case TTEAL: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_TEAL); + break; + case TNAVY: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_NAVY); + break; + case TFUCHSIA: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_FUCHSIA); + break; + case TPURPLE: + AddAttribute(XML_NAMESPACE_MATH, XML_COLOR, XML_PURPLE); + break; + case TSIZE: + { + const SmFontNode *pFontNode = static_cast<const SmFontNode *>(pNode); + const Fraction &aFrac = pFontNode->GetSizeParameter(); + + OUStringBuffer sStrBuf; + switch(pFontNode->GetSizeType()) + { + case FontSizeType::MULTIPLY: + ::sax::Converter::convertDouble(sStrBuf, + static_cast<double>(aFrac*Fraction(100.00))); + sStrBuf.append('%'); + break; + case FontSizeType::DIVIDE: + ::sax::Converter::convertDouble(sStrBuf, + static_cast<double>(Fraction(100.00)/aFrac)); + sStrBuf.append('%'); + break; + case FontSizeType::ABSOLUT: + ::sax::Converter::convertDouble(sStrBuf, + static_cast<double>(aFrac)); + sStrBuf.append( + GetXMLToken(XML_UNIT_PT)); + break; + default: + { + //The problem here is that the wheels fall off because + //font size is stored in 100th's of a mm not pts, and + //rounding errors take their toll on the original + //value specified in points. + + //Must fix StarMath to retain the original pt values + Fraction aTemp = Sm100th_mmToPts(pFontNode->GetFont().GetFontSize().Height()); + + if (pFontNode->GetSizeType() == FontSizeType::MINUS) + aTemp-=aFrac; + else + aTemp+=aFrac; + + double mytest = static_cast<double>(aTemp); + + mytest = ::rtl::math::round(mytest,1); + ::sax::Converter::convertDouble(sStrBuf,mytest); + sStrBuf.append(GetXMLToken(XML_UNIT_PT)); + } + break; + } + + OUString sStr(sStrBuf.makeStringAndClear()); + AddAttribute(XML_NAMESPACE_MATH, XML_MATHSIZE, sStr); + } + break; + case TBOLD: + case TITALIC: + case TNBOLD: + case TNITALIC: + case TFIXED: + case TSANS: + case TSERIF: + { + // nBold: -1 = yet undefined; 0 = false; 1 = true; + // nItalic: -1 = yet undefined; 0 = false; 1 = true; + // nSansSerifFixed: -1 = undefined; 0 = sans; 1 = serif; 2 = fixed; + const char *pText = "normal"; + if (nSansSerifFixed == -1 || nSansSerifFixed == 1) + { + pText = "normal"; + if (nBold == 1 && nItalic != 1) + pText = "bold"; + else if (nBold != 1 && nItalic == 1) + pText = "italic"; + else if (nBold == 1 && nItalic == 1) + pText = "bold-italic"; + } + else if (nSansSerifFixed == 0) + { + pText = "sans-serif"; + if (nBold == 1 && nItalic != 1) + pText = "bold-sans-serif"; + else if (nBold != 1 && nItalic == 1) + pText = "sans-serif-italic"; + else if (nBold == 1 && nItalic == 1) + pText = "sans-serif-bold-italic"; + } + else if (nSansSerifFixed == 2) + pText = "monospace"; // no modifiers allowed for monospace ... + else + { + SAL_WARN("starmath", "unexpected case"); + } + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, OUString::createFromAscii( pText )); + } + break; + default: + break; + + } + { + // Wrap everything in an <mphantom> or <mstyle> element. These elements + // are mrow-like, so ExportExpression doesn't need to add an explicit + // <mrow> element. See #fdo 66283. + SvXMLElementExport aElement(*this, XML_NAMESPACE_MATH, + pNode->GetToken().eType == TPHANTOM ? XML_MPHANTOM : XML_MSTYLE, + true, true); + ExportExpression(pNode, nLevel, true); + } +} + + +void SmXMLExport::ExportVerticalBrace(const SmVerticalBraceNode *pNode, int nLevel) +{ + // "[body] overbrace [script]" + + // Position body, overbrace and script vertically. First place the overbrace + // OVER the body and then the script OVER this expression. + + // [script] + // --[overbrace]-- + // XXXXXX[body]XXXXXXX + + // Similarly for the underbrace construction. + + XMLTokenEnum which; + + switch (pNode->GetToken().eType) + { + case TOVERBRACE: + default: + which = XML_MOVER; + break; + case TUNDERBRACE: + which = XML_MUNDER; + break; + } + + SvXMLElementExport aOver1(*this, XML_NAMESPACE_MATH,which, true, true); + {//Scoping + // using accents will draw the over-/underbraces too close to the base + // see http://www.w3.org/TR/MathML2/chapter3.html#id.3.4.5.2 + // also XML_ACCENT is illegal with XML_MUNDER. Thus no XML_ACCENT attribute here! + SvXMLElementExport aOver2(*this, XML_NAMESPACE_MATH,which, true, true); + ExportNodes(pNode->Body(), nLevel); + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + ExportNodes(pNode->Brace(), nLevel); + } + ExportNodes(pNode->Script(), nLevel); +} + +void SmXMLExport::ExportMatrix(const SmNode *pNode, int nLevel) +{ + SvXMLElementExport aTable(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true); + const SmMatrixNode *pMatrix = static_cast<const SmMatrixNode *>(pNode); + size_t i = 0; + for (sal_uInt16 y = 0; y < pMatrix->GetNumRows(); y++) + { + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MTR, true, true); + for (sal_uInt16 x = 0; x < pMatrix->GetNumCols(); x++) + { + if (const SmNode *pTemp = pNode->GetSubNode(i++)) + { + if (pTemp->GetType() == SmNodeType::Align && + pTemp->GetToken().eType != TALIGNC) + { + // A left or right alignment is specified on this cell, + // attach the corresponding columnalign attribute. + AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN, + pTemp->GetToken().eType == TALIGNL ? + XML_LEFT : XML_RIGHT); + } + SvXMLElementExport aCell(*this, XML_NAMESPACE_MATH, XML_MTD, true, true); + ExportNodes(pTemp, nLevel+1); + } + } + } +} + +void SmXMLExport::ExportNodes(const SmNode *pNode, int nLevel) +{ + if (!pNode) + return; + switch(pNode->GetType()) + { + case SmNodeType::Table: + ExportTable(pNode, nLevel); + break; + case SmNodeType::Align: + case SmNodeType::Bracebody: + case SmNodeType::Expression: + ExportExpression(pNode, nLevel); + break; + case SmNodeType::Line: + ExportLine(pNode, nLevel); + break; + case SmNodeType::Text: + ExportText(pNode); + break; + case SmNodeType::GlyphSpecial: + case SmNodeType::Math: + { + sal_Unicode cTmp = 0; + const SmTextNode *pTemp = static_cast< const SmTextNode * >(pNode); + if (!pTemp->GetText().isEmpty()) + cTmp = ConvertMathToMathML( pTemp->GetText()[0] ); + if (cTmp == 0) + { + // no conversion to MathML implemented -> export it as text + // thus at least it will not vanish into nothing + ExportText(pNode); + } + else + { + switch (pNode->GetToken().eType) + { + case TINTD: + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + break; + default: + break; + } + //To fully handle generic MathML we need to implement the full + //operator dictionary, we will generate MathML with explicit + //stretchiness for now. + sal_Int16 nLength = GetAttrList().getLength(); + bool bAddStretch=true; + for ( sal_Int16 i = 0; i < nLength; i++ ) + { + OUString sLocalName; + sal_uInt16 nPrefix = GetNamespaceMap().GetKeyByAttrName( + GetAttrList().getNameByIndex(i), &sLocalName ); + + if ( ( XML_NAMESPACE_MATH == nPrefix ) && + IsXMLToken(sLocalName, XML_STRETCHY) ) + { + bAddStretch = false; + break; + } + } + if (bAddStretch) + { + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE); + } + ExportMath(pNode); + } + } + break; + case SmNodeType::Special: //SmNodeType::Special requires some sort of Entity preservation in the XML engine. + case SmNodeType::MathIdent: + case SmNodeType::Place: + ExportMath(pNode); + break; + case SmNodeType::BinHor: + ExportBinaryHorizontal(pNode, nLevel); + break; + case SmNodeType::UnHor: + ExportUnaryHorizontal(pNode, nLevel); + break; + case SmNodeType::Brace: + ExportBrace(pNode, nLevel); + break; + case SmNodeType::BinVer: + ExportBinaryVertical(pNode, nLevel); + break; + case SmNodeType::BinDiagonal: + ExportBinaryDiagonal(pNode, nLevel); + break; + case SmNodeType::SubSup: + ExportSubSupScript(pNode, nLevel); + break; + case SmNodeType::Root: + ExportRoot(pNode, nLevel); + break; + case SmNodeType::Oper: + ExportOperator(pNode, nLevel); + break; + case SmNodeType::Attribut: + ExportAttributes(pNode, nLevel); + break; + case SmNodeType::Font: + ExportFont(pNode, nLevel); + break; + case SmNodeType::VerticalBrace: + ExportVerticalBrace(static_cast<const SmVerticalBraceNode *>(pNode), nLevel); + break; + case SmNodeType::Matrix: + ExportMatrix(pNode, nLevel); + break; + case SmNodeType::Blank: + ExportBlank(pNode); + break; + default: + SAL_WARN("starmath", "Warning: failed to export a node?"); + break; + + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathmlexport.hxx b/starmath/source/mathmlexport.hxx new file mode 100644 index 000000000..376365842 --- /dev/null +++ b/starmath/source/mathmlexport.hxx @@ -0,0 +1,116 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_MATHMLEXPORT_HXX +#define INCLUDED_STARMATH_SOURCE_MATHMLEXPORT_HXX + +#include <xmloff/xmlexp.hxx> +#include <xmloff/xmltoken.hxx> + +class SfxMedium; +class SmNode; +class SmVerticalBraceNode; +namespace com::sun::star { + namespace io { + class XOutputStream; } + namespace beans { + class XPropertySet; } +} + + +class SmXMLExportWrapper +{ + css::uno::Reference<css::frame::XModel> xModel; + bool bFlat; //set true for export to flat .mml, set false for + //export to a .sxm (or whatever) package +public: + explicit SmXMLExportWrapper(css::uno::Reference<css::frame::XModel> const &rRef) + : xModel(rRef), bFlat(true) {} + + bool Export(SfxMedium &rMedium); + void SetFlat(bool bIn) {bFlat = bIn;} + + static bool WriteThroughComponent( + const css::uno::Reference< css::io::XOutputStream >& xOutputStream, + const css::uno::Reference< css::lang::XComponent >& xComponent, + css::uno::Reference< css::uno::XComponentContext > const & rxContext, + css::uno::Reference< css::beans::XPropertySet > const & rPropSet, + const char* pComponentName ); + + static bool WriteThroughComponent( + const css::uno::Reference< css::embed::XStorage >& xStor, + const css::uno::Reference< css::lang::XComponent >& xComponent, + const char* pStreamName, + css::uno::Reference< css::uno::XComponentContext > const & rxContext, + css::uno::Reference< css::beans::XPropertySet > const & rPropSet, + const char* pComponentName ); +}; + + +class SmXMLExport final : public SvXMLExport +{ + const SmNode * pTree; + OUString aText; + bool bSuccess; + + void ExportNodes(const SmNode *pNode, int nLevel); + void ExportTable(const SmNode *pNode, int nLevel); + void ExportLine(const SmNode *pNode, int nLevel); + void ExportExpression(const SmNode *pNode, int nLevel, + bool bNoMrowContainer = false); + void ExportText(const SmNode *pNode); + void ExportMath(const SmNode *pNode); + void ExportBinaryHorizontal(const SmNode *pNode, int nLevel); + void ExportUnaryHorizontal(const SmNode *pNode, int nLevel); + void ExportBrace(const SmNode *pNode, int nLevel); + void ExportBinaryVertical(const SmNode *pNode, int nLevel); + void ExportBinaryDiagonal(const SmNode *pNode, int nLevel); + void ExportSubSupScript(const SmNode *pNode, int nLevel); + void ExportRoot(const SmNode *pNode, int nLevel); + void ExportOperator(const SmNode *pNode, int nLevel); + void ExportAttributes(const SmNode *pNode, int nLevel); + void ExportFont(const SmNode *pNode, int nLevel); + void ExportVerticalBrace(const SmVerticalBraceNode *pNode, int nLevel); + void ExportMatrix(const SmNode *pNode, int nLevel); + void ExportBlank(const SmNode *pNode); + +public: + SmXMLExport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + OUString const & implementationName, SvXMLExportFlags nExportFlags); + + // XUnoTunnel + sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) override; + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId() throw(); + + void ExportAutoStyles_() override {} + void ExportMasterStyles_() override {} + void ExportContent_() override; + ErrCode exportDoc(enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID) override; + + virtual void GetViewSettings(css::uno::Sequence<css::beans::PropertyValue>& aProps) override; + virtual void GetConfigurationSettings(css::uno::Sequence<css::beans::PropertyValue>& aProps) override; + + bool GetSuccess() const {return bSuccess;} +}; + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathmlimport.cxx b/starmath/source/mathmlimport.cxx new file mode 100644 index 000000000..6ae00afbb --- /dev/null +++ b/starmath/source/mathmlimport.cxx @@ -0,0 +1,2744 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +/*todo: Change characters and tcharacters to accumulate the characters together +into one string, xml parser hands them to us line by line rather than all in +one go*/ + +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/FastParser.hpp> +#include <com/sun/star/xml/sax/XFastParser.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/task/XStatusIndicator.hpp> + +#include <comphelper/fileformat.h> +#include <comphelper/genericpropertyset.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <rtl/character.hxx> +#include <sal/log.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/sfxmodelfactory.hxx> +#include <osl/diagnose.h> +#include <sot/storage.hxx> +#include <svtools/sfxecode.hxx> +#include <svl/itemset.hxx> +#include <svl/stritem.hxx> +#include <unotools/streamwrap.hxx> +#include <sax/tools/converter.hxx> +#include <xmloff/DocumentSettingsContext.hxx> +#include <xmloff/xmlnmspe.hxx> +#include <xmloff/xmltoken.hxx> +#include <xmloff/nmspmap.hxx> +#include <xmloff/xmluconv.hxx> +#include <xmloff/xmlmetai.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <tools/diagnose_ex.h> + +#include <memory> + +#include "mathmlattr.hxx" +#include "mathmlimport.hxx" +#include <document.hxx> +#include <smdll.hxx> +#include <unomodel.hxx> +#include <utility.hxx> + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +namespace { + +std::unique_ptr<SmNode> popOrZero(SmNodeStack& rStack) +{ + if (rStack.empty()) + return nullptr; + auto pTmp = std::move(rStack.front()); + rStack.pop_front(); + return pTmp; +} + +} + +ErrCode SmXMLImportWrapper::Import(SfxMedium &rMedium) +{ + ErrCode nError = ERRCODE_SFX_DOLOADFAILED; + + uno::Reference<uno::XComponentContext> xContext( comphelper::getProcessComponentContext() ); + + //Make a model component from our SmModel + uno::Reference< lang::XComponent > xModelComp = xModel; + OSL_ENSURE( xModelComp.is(), "XMLReader::Read: got no model" ); + + // try to get an XStatusIndicator from the Medium + uno::Reference<task::XStatusIndicator> xStatusIndicator; + + bool bEmbedded = false; + SmModel *pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel); + + SmDocShell *pDocShell = pModel ? + static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr; + if (pDocShell) + { + OSL_ENSURE( pDocShell->GetMedium() == &rMedium, + "different SfxMedium found" ); + + SfxItemSet* pSet = rMedium.GetItemSet(); + if (pSet) + { + const SfxUnoAnyItem* pItem = static_cast<const SfxUnoAnyItem*>( + pSet->GetItem(SID_PROGRESS_STATUSBAR_CONTROL) ); + if (pItem) + pItem->GetValue() >>= xStatusIndicator; + } + + if ( SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode() ) + bEmbedded = true; + } + + comphelper::PropertyMapEntry aInfoMap[] = + { + { OUString("PrivateData"), 0, + cppu::UnoType<XInterface>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("BaseURI"), 0, + ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamRelPath"), 0, + ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamName"), 0, + ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + uno::Reference< beans::XPropertySet > xInfoSet( + comphelper::GenericPropertySet_CreateInstance( + new comphelper::PropertySetInfo( aInfoMap ) ) ); + + // Set base URI + OUString const baseURI(rMedium.GetBaseURL()); + // needed for relative URLs; but it's OK to import e.g. MathML from the + // clipboard without one + SAL_INFO_IF(baseURI.isEmpty(), "starmath", "SmXMLImportWrapper: no base URL"); + xInfoSet->setPropertyValue("BaseURI", makeAny(baseURI)); + + sal_Int32 nSteps=3; + if ( !(rMedium.IsStorage())) + nSteps = 1; + + sal_Int32 nProgressRange(nSteps); + if (xStatusIndicator.is()) + { + xStatusIndicator->start(SvxResId(RID_SVXSTR_DOC_LOAD), nProgressRange); + } + + nSteps=0; + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + if ( rMedium.IsStorage()) + { + // TODO/LATER: handle the case of embedded links gracefully + if ( bEmbedded ) // && !rMedium.GetStorage()->IsRoot() ) + { + OUString aName( "dummyObjName" ); + if ( rMedium.GetItemSet() ) + { + const SfxStringItem* pDocHierarchItem = static_cast<const SfxStringItem*>( + rMedium.GetItemSet()->GetItem(SID_DOC_HIERARCHICALNAME) ); + if ( pDocHierarchItem ) + aName = pDocHierarchItem->GetValue(); + } + + if ( !aName.isEmpty() ) + { + xInfoSet->setPropertyValue("StreamRelPath", makeAny(aName)); + } + } + + bool bOASIS = ( SotStorage::GetVersion( rMedium.GetStorage() ) > SOFFICE_FILEFORMAT_60 ); + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + auto nWarn = ReadThroughComponent( + rMedium.GetStorage(), xModelComp, "meta.xml", + xContext, xInfoSet, + (bOASIS ? "com.sun.star.comp.Math.XMLOasisMetaImporter" + : "com.sun.star.comp.Math.XMLMetaImporter") ); + + if ( nWarn != ERRCODE_IO_BROKENPACKAGE ) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + nWarn = ReadThroughComponent( + rMedium.GetStorage(), xModelComp, "settings.xml", + xContext, xInfoSet, + (bOASIS ? "com.sun.star.comp.Math.XMLOasisSettingsImporter" + : "com.sun.star.comp.Math.XMLSettingsImporter" ) ); + + if ( nWarn != ERRCODE_IO_BROKENPACKAGE ) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + nError = ReadThroughComponent( + rMedium.GetStorage(), xModelComp, "content.xml", + xContext, xInfoSet, "com.sun.star.comp.Math.XMLImporter" ); + } + else + nError = ERRCODE_IO_BROKENPACKAGE; + } + else + nError = ERRCODE_IO_BROKENPACKAGE; + } + else + { + Reference<io::XInputStream> xInputStream = + new utl::OInputStreamWrapper(rMedium.GetInStream()); + + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + nError = ReadThroughComponent( xInputStream, xModelComp, + xContext, xInfoSet, "com.sun.star.comp.Math.XMLImporter", false ); + } + + if (xStatusIndicator.is()) + xStatusIndicator->end(); + return nError; +} + + +/// read a component (file + filter version) +ErrCode SmXMLImportWrapper::ReadThroughComponent( + const Reference<io::XInputStream>& xInputStream, + const Reference<XComponent>& xModelComponent, + Reference<uno::XComponentContext> const & rxContext, + Reference<beans::XPropertySet> const & rPropSet, + const char* pFilterName, + bool bEncrypted ) +{ + ErrCode nError = ERRCODE_SFX_DOLOADFAILED; + OSL_ENSURE(xInputStream.is(), "input stream missing"); + OSL_ENSURE(xModelComponent.is(), "document missing"); + OSL_ENSURE(rxContext.is(), "factory missing"); + OSL_ENSURE(nullptr != pFilterName,"I need a service name for the component!"); + + // prepare ParserInputSrouce + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; + + // get parser + Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(rxContext); + + Sequence<Any> aArgs( 1 ); + aArgs[0] <<= rPropSet; + + // get filter + Reference< xml::sax::XFastDocumentHandler > xFilter( + rxContext->getServiceManager()->createInstanceWithArgumentsAndContext( + OUString::createFromAscii(pFilterName), aArgs, rxContext), + UNO_QUERY ); + OSL_ENSURE( xFilter.is(), "Can't instantiate filter component." ); + if ( !xFilter.is() ) + return nError; + + // connect parser and filter + xParser->setFastDocumentHandler( xFilter ); + + // connect model and filter + Reference < XImporter > xImporter( xFilter, UNO_QUERY ); + xImporter->setTargetDocument( xModelComponent ); + + uno::Reference< xml::sax::XFastParser > xFastParser = dynamic_cast< + xml::sax::XFastParser* >( xFilter.get() ); + + // finally, parser the stream + try + { + if( xFastParser.is() ) + xFastParser->parseStream( aParserInput ); + else + xParser->parseStream( aParserInput ); + + auto pFilter = comphelper::getUnoTunnelImplementation<SmXMLImport>(xFilter); + if ( pFilter && pFilter->GetSuccess() ) + nError = ERRCODE_NONE; + } + catch (const xml::sax::SAXParseException& r) + { + // sax parser sends wrapped exceptions, + // try to find the original one + xml::sax::SAXException aSaxEx = *static_cast<const xml::sax::SAXException*>(&r); + bool bTryChild = true; + + while( bTryChild ) + { + xml::sax::SAXException aTmp; + if ( aSaxEx.WrappedException >>= aTmp ) + aSaxEx = aTmp; + else + bTryChild = false; + } + + packages::zip::ZipIOException aBrokenPackage; + if ( aSaxEx.WrappedException >>= aBrokenPackage ) + return ERRCODE_IO_BROKENPACKAGE; + + if ( bEncrypted ) + nError = ERRCODE_SFX_WRONGPASSWORD; + } + catch (const xml::sax::SAXException& r) + { + packages::zip::ZipIOException aBrokenPackage; + if ( r.WrappedException >>= aBrokenPackage ) + return ERRCODE_IO_BROKENPACKAGE; + + if ( bEncrypted ) + nError = ERRCODE_SFX_WRONGPASSWORD; + } + catch (const packages::zip::ZipIOException&) + { + nError = ERRCODE_IO_BROKENPACKAGE; + } + catch (const io::IOException&) + { + } + catch (const std::range_error&) + { + } + + return nError; +} + + +ErrCode SmXMLImportWrapper::ReadThroughComponent( + const uno::Reference< embed::XStorage >& xStorage, + const Reference<XComponent>& xModelComponent, + const char* pStreamName, + Reference<uno::XComponentContext> const & rxContext, + Reference<beans::XPropertySet> const & rPropSet, + const char* pFilterName ) +{ + OSL_ENSURE(xStorage.is(), "Need storage!"); + OSL_ENSURE(nullptr != pStreamName, "Please, please, give me a name!"); + + // open stream (and set parser input) + OUString sStreamName = OUString::createFromAscii(pStreamName); + + // get input stream + try + { + uno::Reference < io::XStream > xEventsStream = xStorage->openStreamElement( sStreamName, embed::ElementModes::READ ); + + // determine if stream is encrypted or not + uno::Reference < beans::XPropertySet > xProps( xEventsStream, uno::UNO_QUERY ); + Any aAny = xProps->getPropertyValue( "Encrypted" ); + bool bEncrypted = false; + if ( aAny.getValueType() == cppu::UnoType<bool>::get() ) + aAny >>= bEncrypted; + + // set Base URL + if ( rPropSet.is() ) + { + rPropSet->setPropertyValue( "StreamName", makeAny( sStreamName ) ); + } + + + Reference < io::XInputStream > xStream = xEventsStream->getInputStream(); + return ReadThroughComponent( xStream, xModelComponent, rxContext, rPropSet, pFilterName, bEncrypted ); + } + catch ( packages::WrongPasswordException& ) + { + return ERRCODE_SFX_WRONGPASSWORD; + } + catch( packages::zip::ZipIOException& ) + { + return ERRCODE_IO_BROKENPACKAGE; + } + catch ( uno::Exception& ) + { + } + + return ERRCODE_SFX_DOLOADFAILED; +} + + +SmXMLImport::SmXMLImport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + OUString const & implementationName, SvXMLImportFlags nImportFlags) +: SvXMLImport(rContext, implementationName, nImportFlags), + bSuccess(false), + nParseDepth(0) +{ +} + +namespace +{ + class theSmXMLImportUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSmXMLImportUnoTunnelId> {}; +} + +const uno::Sequence< sal_Int8 > & SmXMLImport::getUnoTunnelId() throw() +{ + return theSmXMLImportUnoTunnelId::get().getSeq(); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_XMLImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire( + new SmXMLImport(pCtx, "com.sun.star.comp.Math.XMLImporter", SvXMLImportFlags::ALL)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_XMLOasisMetaImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new SmXMLImport(pCtx, "com.sun.star.comp.Math.XMLOasisMetaImporter", + SvXMLImportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_XMLOasisSettingsImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new SmXMLImport(pCtx, "com.sun.star.comp.Math.XMLOasisSettingsImporter", + SvXMLImportFlags::SETTINGS)); +} + +sal_Int64 SAL_CALL SmXMLImport::getSomething( + const uno::Sequence< sal_Int8 >&rId ) +{ + if ( isUnoTunnelId<SmXMLImport>(rId) ) + return sal::static_int_cast< sal_Int64 >(reinterpret_cast< sal_uIntPtr >(this)); + + return SvXMLImport::getSomething( rId ); +} + +void SmXMLImport::endDocument() +{ + //Set the resulted tree into the SmDocShell where it belongs + std::unique_ptr<SmNode> pTree = popOrZero(aNodeStack); + if (pTree && pTree->GetType() == SmNodeType::Table) + { + uno::Reference <frame::XModel> xModel = GetModel(); + SmModel *pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel); + + if (pModel) + { + SmDocShell *pDocShell = + static_cast<SmDocShell*>(pModel->GetObjectShell()); + auto pTreeTmp = pTree.get(); + pDocShell->SetFormulaTree(static_cast<SmTableNode *>(pTree.release())); + if (aText.isEmpty()) //If we picked up no annotation text + { + OUStringBuffer aStrBuf; + // Get text from imported formula + pTreeTmp->CreateTextFromNode(aStrBuf); + aStrBuf.stripEnd(' '); + aText = aStrBuf.makeStringAndClear(); + } + + // Convert symbol names + SmParser &rParser = pDocShell->GetParser(); + bool bVal = rParser.IsImportSymbolNames(); + rParser.SetImportSymbolNames( true ); + auto pTmpTree = rParser.Parse( aText ); + aText = rParser.GetText(); + pTmpTree.reset(); + rParser.SetImportSymbolNames( bVal ); + + pDocShell->SetText( aText ); + } + OSL_ENSURE(pModel,"So there *was* a UNO problem after all"); + + bSuccess = true; + } + + SvXMLImport::endDocument(); +} + +namespace { + +class SmXMLImportContext: public SvXMLImportContext +{ +public: + SmXMLImportContext( SmXMLImport &rImport) + : SvXMLImportContext(rImport) + { + GetSmImport().IncParseDepth(); + } + + virtual ~SmXMLImportContext() override + { + GetSmImport().DecParseDepth(); + } + + SmXMLImport& GetSmImport() + { + return static_cast<SmXMLImport&>(GetImport()); + } + + virtual void TCharacters(const OUString & /*rChars*/); + virtual void SAL_CALL characters(const OUString &rChars) override; + virtual uno::Reference< xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) override; + virtual void SAL_CALL startFastElement(sal_Int32 /*nElement*/, const css::uno::Reference<css::xml::sax::XFastAttributeList>& /*rAttrList*/) override + { + if (GetSmImport().TooDeep()) + throw std::range_error("too deep"); + } +}; + +} + +void SmXMLImportContext::TCharacters(const OUString & /*rChars*/) +{ +} + +void SmXMLImportContext::characters(const OUString &rChars) +{ + /* + Whitespace occurring within the content of token elements is "trimmed" + from the ends (i.e. all whitespace at the beginning and end of the + content is removed), and "collapsed" internally (i.e. each sequence of + 1 or more whitespace characters is replaced with one blank character). + */ + //collapsing not done yet! + const OUString &rChars2 = rChars.trim(); + if (!rChars2.isEmpty()) + TCharacters(rChars2/*.collapse()*/); +} + +uno::Reference< xml::sax::XFastContextHandler > SAL_CALL SmXMLImportContext::createFastChildContext( + sal_Int32 /*nElement*/, const uno::Reference< xml::sax::XFastAttributeList >& /*xAttrList*/ ) +{ + return nullptr; +} + +namespace { + +struct SmXMLContext_Helper +{ + sal_Int8 nIsBold; + sal_Int8 nIsItalic; + double nFontSize; + OUString sFontFamily; + OUString sColor; + + SmXMLImportContext & rContext; + + explicit SmXMLContext_Helper(SmXMLImportContext &rImport) + : nIsBold( -1 ) + , nIsItalic( -1 ) + , nFontSize( 0.0 ) + , rContext( rImport ) + {} + + bool IsFontNodeNeeded() const; + void RetrieveAttrs(const uno::Reference< xml::sax::XFastAttributeList > &xAttrList ); + void ApplyAttrs(); +}; + +} + +bool SmXMLContext_Helper::IsFontNodeNeeded() const +{ + return nIsBold != -1 || + nIsItalic != -1 || + nFontSize != 0.0 || + !sFontFamily.isEmpty() || + !sColor.isEmpty(); +} + +void SmXMLContext_Helper::RetrieveAttrs(const uno::Reference< + xml::sax::XFastAttributeList > & xAttrList ) +{ + bool bMvFound = false; + for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList )) + { + OUString sValue = aIter.toString(); + // sometimes they have namespace, sometimes not? + switch(aIter.getToken() & TOKEN_MASK) + { + case XML_FONTWEIGHT: + nIsBold = sal_Int8(sValue == GetXMLToken(XML_BOLD)); + break; + case XML_FONTSTYLE: + nIsItalic = sal_Int8(sValue == GetXMLToken(XML_ITALIC)); + break; + case XML_FONTSIZE: + case XML_MATHSIZE: + ::sax::Converter::convertDouble(nFontSize, sValue); + rContext.GetSmImport().GetMM100UnitConverter(). + SetXMLMeasureUnit(util::MeasureUnit::POINT); + if (-1 == sValue.indexOf(GetXMLToken(XML_UNIT_PT))) + { + if (-1 == sValue.indexOf('%')) + nFontSize=0.0; + else + { + rContext.GetSmImport().GetMM100UnitConverter(). + SetXMLMeasureUnit(util::MeasureUnit::PERCENT); + } + } + break; + case XML_FONTFAMILY: + sFontFamily = sValue; + break; + case XML_COLOR: + sColor = sValue; + break; + case XML_MATHCOLOR: + sColor = sValue; + break; + case XML_MATHVARIANT: + bMvFound = true; + break; + default: + SAL_WARN("starmath", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << "=" << aIter.toString()); + break; + } + } + + if (bMvFound) + { + // Ignore deprecated attributes fontfamily, fontweight, and fontstyle + // in favor of mathvariant, as specified in + // <https://www.w3.org/TR/MathML3/chapter3.html#presm.deprecatt>. + sFontFamily.clear(); + nIsBold = -1; + nIsItalic = -1; + } +} + +void SmXMLContext_Helper::ApplyAttrs() +{ + SmNodeStack &rNodeStack = rContext.GetSmImport().GetNodeStack(); + + if (!IsFontNodeNeeded()) + return; + + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.nLevel = 5; + + if (nIsBold != -1) + { + if (nIsBold) + aToken.eType = TBOLD; + else + aToken.eType = TNBOLD; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } + if (nIsItalic != -1) + { + if (nIsItalic) + aToken.eType = TITALIC; + else + aToken.eType = TNITALIC; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr,popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } + if (nFontSize != 0.0) + { + aToken.eType = TSIZE; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + + if (util::MeasureUnit::PERCENT == rContext.GetSmImport() + .GetMM100UnitConverter().GetXMLMeasureUnit()) + { + if (nFontSize < 100.00) + pFontNode->SetSizeParameter(Fraction(100.00/nFontSize), + FontSizeType::DIVIDE); + else + pFontNode->SetSizeParameter(Fraction(nFontSize/100.00), + FontSizeType::MULTIPLY); + } + else + pFontNode->SetSizeParameter(Fraction(nFontSize),FontSizeType::ABSOLUT); + + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } + if (!sFontFamily.isEmpty()) + { + if (sFontFamily.equalsIgnoreAsciiCase(GetXMLToken(XML_FIXED))) + aToken.eType = TFIXED; + else if (sFontFamily.equalsIgnoreAsciiCase("sans")) + aToken.eType = TSANS; + else if (sFontFamily.equalsIgnoreAsciiCase("serif")) + aToken.eType = TSERIF; + else //Just give up, we need to extend our font mechanism to be + //more general + return; + + aToken.aText = sFontFamily; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } + if (sColor.isEmpty()) + return; + + //Again we can only handle a small set of colours in + //StarMath for now. + const SvXMLTokenMap& rTokenMap = + rContext.GetSmImport().GetColorTokenMap(); + sal_uInt16 tok = rTokenMap.Get(XML_NAMESPACE_MATH, sColor); + if (tok != XML_TOK_UNKNOWN) + { + aToken.eType = static_cast<SmTokenType>(tok); + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } +} + +namespace { + +class SmXMLTokenAttrHelper +{ + SmXMLImportContext& mrContext; + MathMLMathvariantValue meMv; + bool mbMvFound; + +public: + SmXMLTokenAttrHelper(SmXMLImportContext& rContext) + : mrContext(rContext) + , meMv(MathMLMathvariantValue::Normal) + , mbMvFound(false) + {} + + void RetrieveAttrs(const uno::Reference<xml::sax::XFastAttributeList>& xAttrList); + void ApplyAttrs(MathMLMathvariantValue eDefaultMv); +}; + +} + +void SmXMLTokenAttrHelper::RetrieveAttrs(const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList )) + { + OUString sValue = aIter.toString(); + switch(aIter.getToken()) + { + case XML_MATHVARIANT: + if (!GetMathMLMathvariantValue(sValue, meMv)) + SAL_WARN("starmath", "failed to recognize mathvariant: " << sValue); + mbMvFound = true; + break; + default: + SAL_WARN("starmath", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << "=" << aIter.toString()); + break; + } + } +} + +void SmXMLTokenAttrHelper::ApplyAttrs(MathMLMathvariantValue eDefaultMv) +{ + assert( eDefaultMv == MathMLMathvariantValue::Normal || + eDefaultMv == MathMLMathvariantValue::Italic ); + + std::vector<SmTokenType> vVariant; + MathMLMathvariantValue eMv = mbMvFound ? meMv : eDefaultMv; + switch(eMv) + { + case MathMLMathvariantValue::Normal: + vVariant.push_back(TNITALIC); + break; + case MathMLMathvariantValue::Bold: + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::Italic: + // nothing to do + break; + case MathMLMathvariantValue::BoldItalic: + vVariant.push_back(TITALIC); + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::DoubleStruck: + // TODO + break; + case MathMLMathvariantValue::BoldFraktur: + // TODO: Fraktur + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::Script: + // TODO + break; + case MathMLMathvariantValue::BoldScript: + // TODO: Script + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::Fraktur: + // TODO + break; + case MathMLMathvariantValue::SansSerif: + vVariant.push_back(TSANS); + break; + case MathMLMathvariantValue::BoldSansSerif: + vVariant.push_back(TSANS); + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::SansSerifItalic: + vVariant.push_back(TITALIC); + vVariant.push_back(TSANS); + break; + case MathMLMathvariantValue::SansSerifBoldItalic: + vVariant.push_back(TITALIC); + vVariant.push_back(TBOLD); + vVariant.push_back(TSANS); + break; + case MathMLMathvariantValue::Monospace: + vVariant.push_back(TFIXED); + break; + case MathMLMathvariantValue::Initial: + case MathMLMathvariantValue::Tailed: + case MathMLMathvariantValue::Looped: + case MathMLMathvariantValue::Stretched: + // TODO + break; + } + if (vVariant.empty()) + return; + SmNodeStack &rNodeStack = mrContext.GetSmImport().GetNodeStack(); + for (auto eType : vVariant) + { + SmToken aToken; + aToken.eType = eType; + aToken.cMathChar = '\0'; + aToken.nLevel = 5; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } +} + +namespace { + +class SmXMLDocContext_Impl : public SmXMLImportContext +{ +public: + SmXMLDocContext_Impl( SmXMLImport &rImport) + : SmXMLImportContext(rImport) {} + + virtual uno::Reference< xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) override; + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + + +/*avert the gaze from the originator*/ +class SmXMLRowContext_Impl : public SmXMLDocContext_Impl +{ +protected: + size_t nElementCount; + +public: + SmXMLRowContext_Impl(SmXMLImport &rImport) + : SmXMLDocContext_Impl(rImport) + , nElementCount(GetSmImport().GetNodeStack().size()) + { + } + + virtual uno::Reference< xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) override; + + uno::Reference< xml::sax::XFastContextHandler > StrictCreateChildContext(sal_Int32 nElement); + + virtual void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +class SmXMLEncloseContext_Impl : public SmXMLRowContext_Impl +{ +public: + // TODO/LATER: convert <menclose notation="horizontalstrike"> into + // "overstrike{}" and extend the Math syntax to support more notations + SmXMLEncloseContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLEncloseContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <menclose> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + if (GetSmImport().GetNodeStack().size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement( nElement ); +} + +namespace { + +class SmXMLFracContext_Impl : public SmXMLRowContext_Impl +{ +public: + // TODO/LATER: convert <mfrac bevelled="true"> into "wideslash{}{}" + SmXMLFracContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + + +class SmXMLSqrtContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLSqrtContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + + +class SmXMLRootContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLRootContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + + +class SmXMLStyleContext_Impl : public SmXMLRowContext_Impl +{ +protected: + SmXMLContext_Helper aStyleHelper; + +public: + /*Right now the style tag is completely ignored*/ + SmXMLStyleContext_Impl(SmXMLImport &rImport) : SmXMLRowContext_Impl(rImport), + aStyleHelper(*this) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; + void SAL_CALL startFastElement(sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList > &xAttrList ) override; +}; + +} + +void SmXMLStyleContext_Impl::startFastElement( sal_Int32 /*nElement*/, const uno::Reference< + xml::sax::XFastAttributeList > & xAttrList ) +{ + aStyleHelper.RetrieveAttrs(xAttrList); +} + + +void SmXMLStyleContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <mstyle> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + if (rNodeStack.size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); + aStyleHelper.ApplyAttrs(); +} + +namespace { + +class SmXMLPaddedContext_Impl : public SmXMLRowContext_Impl +{ +public: + /*Right now the style tag is completely ignored*/ + SmXMLPaddedContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLPaddedContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <mpadded> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + if (GetSmImport().GetNodeStack().size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); +} + +namespace { + +class SmXMLPhantomContext_Impl : public SmXMLRowContext_Impl +{ +public: + /*Right now the style tag is completely ignored*/ + SmXMLPhantomContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLPhantomContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <mphantom> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + if (GetSmImport().GetNodeStack().size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); + + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.nLevel = 5; + aToken.eType = TPHANTOM; + + std::unique_ptr<SmFontNode> pPhantom(new SmFontNode(aToken)); + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + pPhantom->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pPhantom)); +} + +namespace { + +class SmXMLFencedContext_Impl : public SmXMLRowContext_Impl +{ +protected: + sal_Unicode cBegin; + sal_Unicode cEnd; + +public: + SmXMLFencedContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport), + cBegin('('), cEnd(')') {} + + void SAL_CALL startFastElement(sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) override; + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLFencedContext_Impl::startFastElement(sal_Int32 /*nElement*/, const uno::Reference< + xml::sax::XFastAttributeList > & xAttrList ) +{ + for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList )) + { + OUString sValue = aIter.toString(); + switch(aIter.getToken()) + { + //temp, starmath cannot handle multichar brackets (I think) + case XML_OPEN: + cBegin = sValue[0]; + break; + case XML_CLOSE: + cEnd = sValue[0]; + break; + default: + SAL_WARN("starmath", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << "=" << aIter.toString()); + /*Go to superclass*/ + break; + } + } +} + + +void SmXMLFencedContext_Impl::endFastElement(sal_Int32 /*nElement*/) +{ + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.aText = ","; + aToken.nLevel = 5; + + aToken.eType = TLPARENT; + aToken.cMathChar = cBegin; + std::unique_ptr<SmStructureNode> pSNode(new SmBraceNode(aToken)); + std::unique_ptr<SmNode> pLeft(new SmMathSymbolNode(aToken)); + + aToken.cMathChar = cEnd; + aToken.eType = TRPARENT; + std::unique_ptr<SmNode> pRight(new SmMathSymbolNode(aToken)); + + SmNodeArray aRelationArray; + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + + aToken.cMathChar = '\0'; + aToken.eType = TIDENT; + + auto i = rNodeStack.size() - nElementCount; + if (rNodeStack.size() - nElementCount > 1) + i += rNodeStack.size() - 1 - nElementCount; + aRelationArray.resize(i); + while (rNodeStack.size() > nElementCount) + { + auto pNode = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + aRelationArray[--i] = pNode.release(); + if (i > 1 && rNodeStack.size() > 1) + aRelationArray[--i] = new SmGlyphSpecialNode(aToken); + } + + SmToken aDummy; + std::unique_ptr<SmStructureNode> pBody(new SmExpressionNode(aDummy)); + pBody->SetSubNodes(std::move(aRelationArray)); + + + pSNode->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pSNode->SetScaleMode(SmScaleMode::Height); + GetSmImport().GetNodeStack().push_front(std::move(pSNode)); +} + +namespace { + +class SmXMLErrorContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLErrorContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLErrorContext_Impl::endFastElement(sal_Int32 /*nElement*/) +{ + /*Right now the error tag is completely ignored, what + can I do with it in starmath, ?, maybe we need a + report window ourselves, do a test for validity of + the xml input, use mirrors, and then generate + the markup inside the merror with a big red colour + of something. For now just throw them all away. + */ + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + while (rNodeStack.size() > nElementCount) + { + rNodeStack.pop_front(); + } +} + +namespace { + +class SmXMLNumberContext_Impl : public SmXMLImportContext +{ +protected: + SmToken aToken; + +public: + SmXMLNumberContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport) + { + aToken.cMathChar = '\0'; + aToken.nLevel = 5; + aToken.eType = TNUMBER; + } + + virtual void TCharacters(const OUString &rChars) override; + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLNumberContext_Impl::TCharacters(const OUString &rChars) +{ + aToken.aText = rChars; +} + +void SmXMLNumberContext_Impl::endFastElement(sal_Int32 ) +{ + GetSmImport().GetNodeStack().push_front(std::make_unique<SmTextNode>(aToken,FNT_NUMBER)); +} + +namespace { + +class SmXMLAnnotationContext_Impl : public SmXMLImportContext +{ + bool bIsStarMath; + +public: + SmXMLAnnotationContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport), bIsStarMath(false) {} + + void SAL_CALL characters(const OUString &rChars) override; + + void SAL_CALL startFastElement(sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList > & xAttrList ) override; +}; + +} + +void SmXMLAnnotationContext_Impl::startFastElement(sal_Int32 /*nElement*/, const uno::Reference< + xml::sax::XFastAttributeList > & xAttrList ) +{ + for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList )) + { + OUString sValue = aIter.toString(); + // sometimes they have namespace, sometimes not? + switch(aIter.getToken() & TOKEN_MASK) + { + case XML_ENCODING: + bIsStarMath= sValue == "StarMath 5.0"; + break; + default: + SAL_WARN("starmath", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << "=" << aIter.toString()); + break; + } + } +} + +void SmXMLAnnotationContext_Impl::characters(const OUString &rChars) +{ + if (bIsStarMath) + GetSmImport().SetText( GetSmImport().GetText() + rChars ); +} + +namespace { + +class SmXMLTextContext_Impl : public SmXMLImportContext +{ +protected: + SmToken aToken; + +public: + SmXMLTextContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport) + { + aToken.cMathChar = '\0'; + aToken.nLevel = 5; + aToken.eType = TTEXT; + } + + virtual void TCharacters(const OUString &rChars) override; + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLTextContext_Impl::TCharacters(const OUString &rChars) +{ + aToken.aText = rChars; +} + +void SmXMLTextContext_Impl::endFastElement(sal_Int32 ) +{ + GetSmImport().GetNodeStack().push_front(std::make_unique<SmTextNode>(aToken,FNT_TEXT)); +} + +namespace { + +class SmXMLStringContext_Impl : public SmXMLImportContext +{ +protected: + SmToken aToken; + +public: + SmXMLStringContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport) + { + aToken.cMathChar = '\0'; + aToken.nLevel = 5; + aToken.eType = TTEXT; + } + + virtual void TCharacters(const OUString &rChars) override; + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLStringContext_Impl::TCharacters(const OUString &rChars) +{ + /* + The content of <ms> elements should be rendered with visible "escaping" of + certain characters in the content, including at least "double quote" + itself, and preferably whitespace other than individual blanks. The intent + is for the viewer to see that the expression is a string literal, and to + see exactly which characters form its content. For example, <ms>double + quote is "</ms> might be rendered as "double quote is \"". + + Obviously this isn't fully done here. + */ + aToken.aText = "\"" + rChars + "\""; +} + +void SmXMLStringContext_Impl::endFastElement(sal_Int32 ) +{ + GetSmImport().GetNodeStack().push_front(std::make_unique<SmTextNode>(aToken,FNT_FIXED)); +} + +namespace { + +class SmXMLIdentifierContext_Impl : public SmXMLImportContext +{ + SmXMLTokenAttrHelper maTokenAttrHelper; + SmXMLContext_Helper aStyleHelper; + SmToken aToken; + +public: + SmXMLIdentifierContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport) + , maTokenAttrHelper(*this) + , aStyleHelper(*this) + { + aToken.cMathChar = '\0'; + aToken.nLevel = 5; + aToken.eType = TIDENT; + } + + void TCharacters(const OUString &rChars) override; + void SAL_CALL startFastElement(sal_Int32 /*nElement*/, const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) override + { + maTokenAttrHelper.RetrieveAttrs(xAttrList); + aStyleHelper.RetrieveAttrs(xAttrList); + }; + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLIdentifierContext_Impl::endFastElement(sal_Int32 ) +{ + std::unique_ptr<SmTextNode> pNode; + //we will handle identifier italic/normal here instead of with a standalone + //font node + if (((aStyleHelper.nIsItalic == -1) && (aToken.aText.getLength() > 1)) + || ((aStyleHelper.nIsItalic == 0) && (aToken.aText.getLength() == 1))) + { + pNode.reset(new SmTextNode(aToken,FNT_FUNCTION)); + pNode->GetFont().SetItalic(ITALIC_NONE); + aStyleHelper.nIsItalic = -1; + } + else + pNode.reset(new SmTextNode(aToken,FNT_VARIABLE)); + if (aStyleHelper.nIsItalic != -1) + { + if (aStyleHelper.nIsItalic) + pNode->GetFont().SetItalic(ITALIC_NORMAL); + else + pNode->GetFont().SetItalic(ITALIC_NONE); + aStyleHelper.nIsItalic = -1; + } + GetSmImport().GetNodeStack().push_front(std::move(pNode)); + aStyleHelper.ApplyAttrs(); + + maTokenAttrHelper.ApplyAttrs( (aToken.aText.getLength() == 1) + ? MathMLMathvariantValue::Italic + : MathMLMathvariantValue::Normal ); +} + +void SmXMLIdentifierContext_Impl::TCharacters(const OUString &rChars) +{ + aToken.aText = rChars; +} + +namespace { + +class SmXMLOperatorContext_Impl : public SmXMLImportContext +{ + SmXMLTokenAttrHelper maTokenAttrHelper; + bool bIsStretchy; + SmToken aToken; + +public: + SmXMLOperatorContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport) + , maTokenAttrHelper(*this) + , bIsStretchy(false) + { + aToken.eType = TSPECIAL; + aToken.nLevel = 5; + } + + void TCharacters(const OUString &rChars) override; + void SAL_CALL startFastElement(sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList > &xAttrList ) override; + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLOperatorContext_Impl::TCharacters(const OUString &rChars) +{ + aToken.cMathChar = rChars[0]; +} + +void SmXMLOperatorContext_Impl::endFastElement(sal_Int32 ) +{ + std::unique_ptr<SmMathSymbolNode> pNode(new SmMathSymbolNode(aToken)); + //For stretchy scaling the scaling must be retrieved from this node + //and applied to the expression itself so as to get the expression + //to scale the operator to the height of the expression itself + if (bIsStretchy) + pNode->SetScaleMode(SmScaleMode::Height); + GetSmImport().GetNodeStack().push_front(std::move(pNode)); + + // TODO: apply to non-alphabetic characters too + if (rtl::isAsciiAlpha(aToken.cMathChar)) + maTokenAttrHelper.ApplyAttrs(MathMLMathvariantValue::Normal); +} + + +void SmXMLOperatorContext_Impl::startFastElement(sal_Int32 /*nElement*/, const uno::Reference< + xml::sax::XFastAttributeList > & xAttrList ) +{ + maTokenAttrHelper.RetrieveAttrs(xAttrList); + + for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList )) + { + OUString sValue = aIter.toString(); + switch(aIter.getToken()) + { + case XML_STRETCHY: + bIsStretchy = sValue == GetXMLToken(XML_TRUE); + break; + default: + SAL_WARN("starmath", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << "=" << aIter.toString()); + break; + } + } +} + +namespace { + +class SmXMLSpaceContext_Impl : public SmXMLImportContext +{ +public: + SmXMLSpaceContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport) {} + + void SAL_CALL startFastElement(sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) override; +}; + +bool lcl_CountBlanks(const MathMLAttributeLengthValue &rLV, + sal_Int32 *pWide, sal_Int32 *pNarrow) +{ + assert(pWide); + assert(pNarrow); + if (rLV.aNumber.GetNumerator() == 0) + { + *pWide = *pNarrow = 0; + return true; + } + // TODO: honor other units than em + if (rLV.eUnit != MathMLLengthUnit::Em) + return false; + if (rLV.aNumber.GetNumerator() < 0) + return false; + const Fraction aTwo(2, 1); + auto aWide = rLV.aNumber / aTwo; + auto nWide = static_cast<sal_Int32>(static_cast<long>(aWide)); + if (nWide < 0) + return false; + const Fraction aPointFive(1, 2); + auto aNarrow = (rLV.aNumber - Fraction(nWide, 1) * aTwo) / aPointFive; + auto nNarrow = static_cast<sal_Int32>(static_cast<long>(aNarrow)); + if (nNarrow < 0) + return false; + *pWide = nWide; + *pNarrow = nNarrow; + return true; +} + +} + +void SmXMLSpaceContext_Impl::startFastElement(sal_Int32 /*nElement*/, + const uno::Reference<xml::sax::XFastAttributeList > & xAttrList ) +{ + // There is no syntax in Math to specify blank nodes of arbitrary size yet. + MathMLAttributeLengthValue aLV; + sal_Int32 nWide = 0, nNarrow = 0; + + for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList )) + { + OUString sValue = aIter.toString(); + switch (aIter.getToken()) + { + case XML_WIDTH: + if ( ParseMathMLAttributeLengthValue(sValue.trim(), aLV) <= 0 || + !lcl_CountBlanks(aLV, &nWide, &nNarrow) ) + SAL_WARN("starmath", "ignore mspace's width: " << sValue); + break; + default: + SAL_WARN("starmath", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << "=" << aIter.toString()); + break; + } + } + SmToken aToken; + aToken.eType = TBLANK; + aToken.cMathChar = '\0'; + aToken.nGroup = TG::Blank; + aToken.nLevel = 5; + std::unique_ptr<SmBlankNode> pBlank(new SmBlankNode(aToken)); + if (nWide > 0) + pBlank->IncreaseBy(aToken, nWide); + if (nNarrow > 0) + { + aToken.eType = TSBLANK; + pBlank->IncreaseBy(aToken, nNarrow); + } + GetSmImport().GetNodeStack().push_front(std::move(pBlank)); +} + +namespace { + +class SmXMLSubContext_Impl : public SmXMLRowContext_Impl +{ +protected: + void GenericEndElement(SmTokenType eType,SmSubSup aSubSup); + +public: + SmXMLSubContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 ) override + { + GenericEndElement(TRSUB,RSUB); + } +}; + +} + +void SmXMLSubContext_Impl::GenericEndElement(SmTokenType eType, SmSubSup eSubSup) +{ + /*The <msub> element requires exactly 2 arguments.*/ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 2; + OSL_ENSURE( bNodeCheck, "Sub has not two arguments" ); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.eType = eType; + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(aToken)); + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + + // initialize subnodes array + SmNodeArray aSubNodes; + aSubNodes.resize(1 + SUBSUP_NUM_ENTRIES); + for (size_t i = 1; i < aSubNodes.size(); i++) + aSubNodes[i] = nullptr; + + aSubNodes[eSubSup+1] = popOrZero(rNodeStack).release(); + aSubNodes[0] = popOrZero(rNodeStack).release(); + pNode->SetSubNodes(std::move(aSubNodes)); + rNodeStack.push_front(std::move(pNode)); +} + +namespace { + +class SmXMLSupContext_Impl : public SmXMLSubContext_Impl +{ +public: + SmXMLSupContext_Impl(SmXMLImport &rImport) + : SmXMLSubContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 ) override + { + GenericEndElement(TRSUP,RSUP); + } +}; + + +class SmXMLSubSupContext_Impl : public SmXMLRowContext_Impl +{ +protected: + void GenericEndElement(SmTokenType eType, SmSubSup aSub,SmSubSup aSup); + +public: + SmXMLSubSupContext_Impl(SmXMLImport &rImport) + : SmXMLRowContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 ) override + { + GenericEndElement(TRSUB,RSUB,RSUP); + } +}; + +} + +void SmXMLSubSupContext_Impl::GenericEndElement(SmTokenType eType, + SmSubSup aSub,SmSubSup aSup) +{ + /*The <msub> element requires exactly 3 arguments.*/ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 3; + OSL_ENSURE( bNodeCheck, "SubSup has not three arguments" ); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.eType = eType; + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(aToken)); + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + + // initialize subnodes array + SmNodeArray aSubNodes; + aSubNodes.resize(1 + SUBSUP_NUM_ENTRIES); + for (size_t i = 1; i < aSubNodes.size(); i++) + aSubNodes[i] = nullptr; + + aSubNodes[aSup+1] = popOrZero(rNodeStack).release(); + aSubNodes[aSub+1] = popOrZero(rNodeStack).release(); + aSubNodes[0] = popOrZero(rNodeStack).release(); + pNode->SetSubNodes(std::move(aSubNodes)); + rNodeStack.push_front(std::move(pNode)); +} + +namespace { + +class SmXMLUnderContext_Impl : public SmXMLSubContext_Impl +{ +protected: + sal_Int16 nAttrCount; + +public: + SmXMLUnderContext_Impl(SmXMLImport &rImport) + : SmXMLSubContext_Impl(rImport) + , nAttrCount( 0 ) + {} + + void SAL_CALL startFastElement(sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList > &xAttrList ) override; + void SAL_CALL endFastElement(sal_Int32 nElement) override; + void HandleAccent(); +}; + +} + +void SmXMLUnderContext_Impl::startFastElement(sal_Int32 /*nElement*/, const uno::Reference< + xml::sax::XFastAttributeList > & xAttrList ) +{ + sax_fastparser::FastAttributeList& rAttribList = + sax_fastparser::castToFastAttributeList( xAttrList ); + nAttrCount = rAttribList.getFastAttributeTokens().size(); +} + +void SmXMLUnderContext_Impl::HandleAccent() +{ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 2; + OSL_ENSURE( bNodeCheck, "Sub has not two arguments" ); + if (!bNodeCheck) + return; + + /*Just one special case for the underline thing*/ + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + std::unique_ptr<SmNode> pTest = popOrZero(rNodeStack); + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.eType = TUNDERLINE; + + std::unique_ptr<SmNode> pFirst; + std::unique_ptr<SmStructureNode> pNode(new SmAttributNode(aToken)); + if ((pTest->GetToken().cMathChar & 0x0FFF) == 0x0332) + { + pFirst.reset(new SmRectangleNode(aToken)); + } + else + pFirst = std::move(pTest); + + std::unique_ptr<SmNode> pSecond = popOrZero(rNodeStack); + pNode->SetSubNodes(std::move(pFirst), std::move(pSecond)); + pNode->SetScaleMode(SmScaleMode::Width); + rNodeStack.push_front(std::move(pNode)); +} + + +void SmXMLUnderContext_Impl::endFastElement(sal_Int32 ) +{ + if (!nAttrCount) + GenericEndElement(TCSUB,CSUB); + else + HandleAccent(); +} + +namespace { + +class SmXMLOverContext_Impl : public SmXMLSubContext_Impl +{ +protected: + sal_Int16 nAttrCount; + +public: + SmXMLOverContext_Impl(SmXMLImport &rImport) + : SmXMLSubContext_Impl(rImport), nAttrCount(0) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; + void SAL_CALL startFastElement(sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList > &xAttrList ) override; + void HandleAccent(); +}; + +} + +void SmXMLOverContext_Impl::startFastElement(sal_Int32 /*nElement*/, const uno::Reference< + xml::sax::XFastAttributeList > & xAttrList ) +{ + sax_fastparser::FastAttributeList& rAttribList = + sax_fastparser::castToFastAttributeList( xAttrList ); + nAttrCount = rAttribList.getFastAttributeTokens().size(); +} + + +void SmXMLOverContext_Impl::endFastElement(sal_Int32 ) +{ + if (!nAttrCount) + GenericEndElement(TCSUP,CSUP); + else + HandleAccent(); +} + + +void SmXMLOverContext_Impl::HandleAccent() +{ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 2; + OSL_ENSURE (bNodeCheck, "Sub has not two arguments"); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.eType = TACUTE; + + std::unique_ptr<SmAttributNode> pNode(new SmAttributNode(aToken)); + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + + std::unique_ptr<SmNode> pFirst = popOrZero(rNodeStack); + std::unique_ptr<SmNode> pSecond = popOrZero(rNodeStack); + pNode->SetSubNodes(std::move(pFirst), std::move(pSecond)); + pNode->SetScaleMode(SmScaleMode::Width); + rNodeStack.push_front(std::move(pNode)); + +} + +namespace { + +class SmXMLUnderOverContext_Impl : public SmXMLSubSupContext_Impl +{ +public: + SmXMLUnderOverContext_Impl(SmXMLImport &rImport) + : SmXMLSubSupContext_Impl(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 ) override + { + GenericEndElement(TCSUB,CSUB,CSUP); + } +}; + + +class SmXMLMultiScriptsContext_Impl : public SmXMLSubSupContext_Impl +{ + bool bHasPrescripts; + + void ProcessSubSupPairs(bool bIsPrescript); + +public: + SmXMLMultiScriptsContext_Impl(SmXMLImport &rImport) : + SmXMLSubSupContext_Impl(rImport), + bHasPrescripts(false) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; + virtual uno::Reference< xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) override; +}; + + +class SmXMLNoneContext_Impl : public SmXMLImportContext +{ +public: + SmXMLNoneContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport) {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +} + +void SmXMLNoneContext_Impl::endFastElement(sal_Int32 ) +{ + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.aText.clear(); + aToken.nLevel = 5; + aToken.eType = TIDENT; + GetSmImport().GetNodeStack().push_front( + std::make_unique<SmTextNode>(aToken,FNT_VARIABLE)); +} + +namespace { + +class SmXMLPrescriptsContext_Impl : public SmXMLImportContext +{ +public: + SmXMLPrescriptsContext_Impl(SmXMLImport &rImport) + : SmXMLImportContext(rImport) {} +}; + + +class SmXMLTableRowContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLTableRowContext_Impl(SmXMLImport &rImport) : + SmXMLRowContext_Impl(rImport) + {} + + virtual uno::Reference< xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) override; +}; + + +class SmXMLTableContext_Impl : public SmXMLTableRowContext_Impl +{ +public: + SmXMLTableContext_Impl(SmXMLImport &rImport) : + SmXMLTableRowContext_Impl(rImport) + {} + + void SAL_CALL endFastElement(sal_Int32 nElement) override; + virtual uno::Reference< xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) override; +}; + + +class SmXMLTableCellContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLTableCellContext_Impl(SmXMLImport &rImport) : + SmXMLRowContext_Impl(rImport) + {} +}; + + +class SmXMLAlignGroupContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLAlignGroupContext_Impl(SmXMLImport &rImport) : + SmXMLRowContext_Impl(rImport) + {} + + /*Don't do anything with alignment for now*/ + void SAL_CALL endFastElement(sal_Int32 ) override + { + } +}; + + +class SmXMLActionContext_Impl : public SmXMLRowContext_Impl +{ + size_t mnSelection; // 1-based + +public: + SmXMLActionContext_Impl(SmXMLImport &rImport) : + SmXMLRowContext_Impl(rImport) + , mnSelection(1) + {} + + void SAL_CALL startFastElement(sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList> &xAttrList) override; + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + + +// NB: virtually inherit so we can multiply inherit properly +// in SmXMLFlatDocContext_Impl +class SmXMLOfficeContext_Impl : public virtual SvXMLImportContext +{ +public: + SmXMLOfficeContext_Impl( SmXMLImport &rImport ) + : SvXMLImportContext(rImport) {} + + virtual void SAL_CALL characters( const OUString& /*aChars*/ ) override {} + + virtual void SAL_CALL startFastElement( sal_Int32 /*nElement*/, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& /*xAttrList*/ ) override {} + + virtual void SAL_CALL endFastElement( sal_Int32 /*nElement*/ ) override {} + + virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList ) override; +}; + +} + +uno::Reference< xml::sax::XFastContextHandler > SmXMLOfficeContext_Impl::createFastChildContext(sal_Int32 nElement, + const uno::Reference< xml::sax::XFastAttributeList > &/*xAttrList*/) +{ + if ( nElement == XML_ELEMENT(OFFICE, XML_META) ) + { + SAL_WARN("starmath", "XML_TOK_DOC_META: should not have come here, maybe document is invalid?"); + } + else if ( nElement == XML_ELEMENT(OFFICE, XML_SETTINGS) ) + { + return new XMLDocumentSettingsContext( GetImport() ); + } + return nullptr; +} + +namespace { + +// context for flat file xml format +class SmXMLFlatDocContext_Impl + : public SmXMLOfficeContext_Impl, public SvXMLMetaDocumentContext +{ +public: + SmXMLFlatDocContext_Impl( SmXMLImport& i_rImport, + const uno::Reference<document::XDocumentProperties>& i_xDocProps); + + virtual void SAL_CALL characters( const OUString& aChars ) override; + + virtual void SAL_CALL startFastElement( sal_Int32 nElement, + const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList ) override; + + virtual void SAL_CALL endFastElement( sal_Int32 nElement ) override; + + virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList ) override; +}; + +} + +SmXMLFlatDocContext_Impl::SmXMLFlatDocContext_Impl( SmXMLImport& i_rImport, + const uno::Reference<document::XDocumentProperties>& i_xDocProps) : + SvXMLImportContext(i_rImport), + SmXMLOfficeContext_Impl(i_rImport), + SvXMLMetaDocumentContext(i_rImport, i_xDocProps) +{ +} + +void SAL_CALL SmXMLFlatDocContext_Impl::startFastElement( sal_Int32 nElement, + const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) +{ + SvXMLMetaDocumentContext::startFastElement(nElement, xAttrList); +} + +void SAL_CALL SmXMLFlatDocContext_Impl::endFastElement( sal_Int32 nElement ) +{ + SvXMLMetaDocumentContext::endFastElement(nElement); +} + +void SAL_CALL SmXMLFlatDocContext_Impl::characters( const OUString& rChars ) +{ + SvXMLMetaDocumentContext::characters(rChars); +} + +uno::Reference< xml::sax::XFastContextHandler > SAL_CALL SmXMLFlatDocContext_Impl::createFastChildContext( + sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList ) +{ + // behave like meta base class iff we encounter office:meta + if ( nElement == XML_ELEMENT(OFFICE, XML_META) ) + { + return SvXMLMetaDocumentContext::createFastChildContext( + nElement, xAttrList ); + } + else + { + return SmXMLOfficeContext_Impl::createFastChildContext( + nElement, xAttrList ); + } +} + +static const SvXMLTokenMapEntry aColorTokenMap[] = +{ + { XML_NAMESPACE_MATH, XML_BLACK, TBLACK}, + { XML_NAMESPACE_MATH, XML_WHITE, TWHITE}, + { XML_NAMESPACE_MATH, XML_RED, TRED}, + { XML_NAMESPACE_MATH, XML_GREEN, TGREEN}, + { XML_NAMESPACE_MATH, XML_BLUE, TBLUE}, + { XML_NAMESPACE_MATH, XML_AQUA, TAQUA}, + { XML_NAMESPACE_MATH, XML_FUCHSIA, TFUCHSIA}, + { XML_NAMESPACE_MATH, XML_YELLOW, TYELLOW}, + { XML_NAMESPACE_MATH, XML_NAVY, TNAVY}, + { XML_NAMESPACE_MATH, XML_TEAL, TTEAL}, + { XML_NAMESPACE_MATH, XML_MAROON, TMAROON}, + { XML_NAMESPACE_MATH, XML_PURPLE, TPURPLE}, + { XML_NAMESPACE_MATH, XML_OLIVE, TOLIVE}, + { XML_NAMESPACE_MATH, XML_GRAY, TGRAY}, + { XML_NAMESPACE_MATH, XML_SILVER, TSILVER}, + { XML_NAMESPACE_MATH, XML_LIME, TLIME}, + XML_TOKEN_MAP_END +}; + + +const SvXMLTokenMap& SmXMLImport::GetColorTokenMap() +{ + if (!pColorTokenMap) + pColorTokenMap.reset(new SvXMLTokenMap(aColorTokenMap)); + return *pColorTokenMap; +} + +uno::Reference< xml::sax::XFastContextHandler > SmXMLDocContext_Impl::createFastChildContext( + sal_Int32 nElement, + const uno::Reference<xml::sax::XFastAttributeList>& /*xAttrList*/) +{ + uno::Reference< xml::sax::XFastContextHandler > xContext; + + switch(nElement) + { + //Consider semantics a dummy except for any starmath annotations + case XML_ELEMENT(MATH, XML_SEMANTICS): + xContext = new SmXMLRowContext_Impl(GetSmImport()); + break; + /*General Layout Schemata*/ + case XML_ELEMENT(MATH, XML_MROW): + xContext = new SmXMLRowContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MENCLOSE): + xContext = new SmXMLEncloseContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MFRAC): + xContext = new SmXMLFracContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSQRT): + xContext = new SmXMLSqrtContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MROOT): + xContext = new SmXMLRootContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSTYLE): + xContext = new SmXMLStyleContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MERROR): + xContext = new SmXMLErrorContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MPADDED): + xContext = new SmXMLPaddedContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MPHANTOM): + xContext = new SmXMLPhantomContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MFENCED): + xContext = new SmXMLFencedContext_Impl(GetSmImport()); + break; + /*Script and Limit Schemata*/ + case XML_ELEMENT(MATH, XML_MSUB): + xContext = new SmXMLSubContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSUP): + xContext = new SmXMLSupContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSUBSUP): + xContext = new SmXMLSubSupContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MUNDER): + xContext = new SmXMLUnderContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MOVER): + xContext = new SmXMLOverContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MUNDEROVER): + xContext = new SmXMLUnderOverContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MMULTISCRIPTS): + xContext = new SmXMLMultiScriptsContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MTABLE): + xContext = new SmXMLTableContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MACTION): + xContext = new SmXMLActionContext_Impl(GetSmImport()); + break; + default: + /*Basically there's an implicit mrow around certain bare + *elements, use a RowContext to see if this is one of + *those ones*/ + rtl::Reference<SmXMLRowContext_Impl> aTempContext(new SmXMLRowContext_Impl(GetSmImport())); + + xContext = aTempContext->StrictCreateChildContext(nElement); + break; + } + return xContext; +} + +void SmXMLDocContext_Impl::endFastElement(sal_Int32) +{ + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + + std::unique_ptr<SmNode> pContextNode = popOrZero(rNodeStack); + + SmToken aDummy; + std::unique_ptr<SmStructureNode> pSNode(new SmLineNode(aDummy)); + pSNode->SetSubNodes(std::move(pContextNode), nullptr); + rNodeStack.push_front(std::move(pSNode)); + + SmNodeArray LineArray; + auto n = rNodeStack.size(); + LineArray.resize(n); + for (size_t j = 0; j < n; j++) + { + auto pNode = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + LineArray[n - (j + 1)] = pNode.release(); + } + std::unique_ptr<SmStructureNode> pSNode2(new SmTableNode(aDummy)); + pSNode2->SetSubNodes(std::move(LineArray)); + rNodeStack.push_front(std::move(pSNode2)); +} + +void SmXMLFracContext_Impl::endFastElement(sal_Int32 ) +{ + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + const bool bNodeCheck = rNodeStack.size() - nElementCount == 2; + OSL_ENSURE( bNodeCheck, "Fraction (mfrac) tag is missing component" ); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.eType = TOVER; + std::unique_ptr<SmStructureNode> pSNode(new SmBinVerNode(aToken)); + std::unique_ptr<SmNode> pOper(new SmRectangleNode(aToken)); + std::unique_ptr<SmNode> pSecond = popOrZero(rNodeStack); + std::unique_ptr<SmNode> pFirst = popOrZero(rNodeStack); + pSNode->SetSubNodes(std::move(pFirst), std::move(pOper), std::move(pSecond)); + rNodeStack.push_front(std::move(pSNode)); +} + +void SmXMLRootContext_Impl::endFastElement(sal_Int32 ) +{ + /*The <mroot> element requires exactly 2 arguments.*/ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 2; + OSL_ENSURE( bNodeCheck, "Root tag is missing component"); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = MS_SQRT; //Temporary: alert, based on StarSymbol font + aToken.eType = TNROOT; + std::unique_ptr<SmStructureNode> pSNode(new SmRootNode(aToken)); + std::unique_ptr<SmNode> pOper(new SmRootSymbolNode(aToken)); + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + std::unique_ptr<SmNode> pIndex = popOrZero(rNodeStack); + std::unique_ptr<SmNode> pBase = popOrZero(rNodeStack); + pSNode->SetSubNodes(std::move(pIndex), std::move(pOper), std::move(pBase)); + rNodeStack.push_front(std::move(pSNode)); +} + +void SmXMLSqrtContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <msqrt> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + if (GetSmImport().GetNodeStack().size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); + + SmToken aToken; + aToken.cMathChar = MS_SQRT; //Temporary: alert, based on StarSymbol font + aToken.eType = TSQRT; + std::unique_ptr<SmStructureNode> pSNode(new SmRootNode(aToken)); + std::unique_ptr<SmNode> pOper(new SmRootSymbolNode(aToken)); + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + pSNode->SetSubNodes(nullptr, std::move(pOper), popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pSNode)); +} + +void SmXMLRowContext_Impl::endFastElement(sal_Int32 ) +{ + SmNodeArray aRelationArray; + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + + if (rNodeStack.size() > nElementCount) + { + auto nSize = rNodeStack.size() - nElementCount; + + aRelationArray.resize(nSize); + for (auto j=nSize;j > 0;j--) + { + auto pNode = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + aRelationArray[j-1] = pNode.release(); + } + + //If the first or last element is an operator with stretchyness + //set then we must create a brace node here from those elements, + //removing the stretchness from the operators and applying it to + //ourselves, and creating the appropriate dummy StarMath none bracket + //to balance the arrangement + if (((aRelationArray[0]->GetScaleMode() == SmScaleMode::Height) + && (aRelationArray[0]->GetType() == SmNodeType::Math)) + || ((aRelationArray[nSize-1]->GetScaleMode() == SmScaleMode::Height) + && (aRelationArray[nSize-1]->GetType() == SmNodeType::Math))) + { + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.nLevel = 5; + + int nLeft=0,nRight=0; + if ((aRelationArray[0]->GetScaleMode() == SmScaleMode::Height) + && (aRelationArray[0]->GetType() == SmNodeType::Math)) + { + aToken = aRelationArray[0]->GetToken(); + nLeft=1; + } + else + aToken.cMathChar = '\0'; + + aToken.eType = TLPARENT; + std::unique_ptr<SmNode> pLeft(new SmMathSymbolNode(aToken)); + + if ((aRelationArray[nSize-1]->GetScaleMode() == SmScaleMode::Height) + && (aRelationArray[nSize-1]->GetType() == SmNodeType::Math)) + { + aToken = aRelationArray[nSize-1]->GetToken(); + nRight=1; + } + else + aToken.cMathChar = '\0'; + + aToken.eType = TRPARENT; + std::unique_ptr<SmNode> pRight(new SmMathSymbolNode(aToken)); + + SmNodeArray aRelationArray2; + + //!! nSize-nLeft-nRight may be < 0 !! + int nRelArrSize = nSize-nLeft-nRight; + if (nRelArrSize > 0) + { + aRelationArray2.resize(nRelArrSize); + for (int i=0;i < nRelArrSize;i++) + { + aRelationArray2[i] = aRelationArray[i+nLeft]; + aRelationArray[i+nLeft] = nullptr; + } + } + + SmToken aDummy; + std::unique_ptr<SmStructureNode> pSNode(new SmBraceNode(aToken)); + std::unique_ptr<SmStructureNode> pBody(new SmExpressionNode(aDummy)); + pBody->SetSubNodes(std::move(aRelationArray2)); + + pSNode->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pSNode->SetScaleMode(SmScaleMode::Height); + rNodeStack.push_front(std::move(pSNode)); + + for (auto a : aRelationArray) + delete a; + + return; + } + } + else + { + // The elements msqrt, mstyle, merror, menclose, mpadded, mphantom, mtd, and math + // treat their content as a single inferred mrow in case their content is empty. + // Here an empty group {} is used to catch those cases and transform them without error + // to StarMath. + aRelationArray.resize(2); + SmToken aToken; + aToken.cMathChar = MS_LBRACE; + aToken.nLevel = 5; + aToken.eType = TLGROUP; + aToken.aText = "{"; + aRelationArray[0] = new SmLineNode(aToken); + + aToken.cMathChar = MS_RBRACE; + aToken.nLevel = 0; + aToken.eType = TRGROUP; + aToken.aText = "}"; + aRelationArray[1] = new SmLineNode(aToken); + } + + SmToken aDummy; + std::unique_ptr<SmStructureNode> pSNode(new SmExpressionNode(aDummy)); + pSNode->SetSubNodes(std::move(aRelationArray)); + rNodeStack.push_front(std::move(pSNode)); +} + +uno::Reference< xml::sax::XFastContextHandler > SmXMLRowContext_Impl::StrictCreateChildContext( + sal_Int32 nElement) +{ + uno::Reference< xml::sax::XFastContextHandler > pContext; + + switch(nElement) + { + /*Note that these should accept malignmark subelements, but do not*/ + case XML_ELEMENT(MATH, XML_MN): + pContext = new SmXMLNumberContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MI): + pContext = new SmXMLIdentifierContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MO): + pContext = new SmXMLOperatorContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MTEXT): + pContext = new SmXMLTextContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSPACE): + pContext = new SmXMLSpaceContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MS): + pContext = new SmXMLStringContext_Impl(GetSmImport()); + break; + + /*Note: The maligngroup should only be seen when the row + * (or descendants) are in a table*/ + case XML_ELEMENT(MATH, XML_MALIGNGROUP): + pContext = new SmXMLAlignGroupContext_Impl(GetSmImport()); + break; + + case XML_ELEMENT(MATH, XML_ANNOTATION): + pContext = new SmXMLAnnotationContext_Impl(GetSmImport()); + break; + + default: + break; + } + return pContext; +} + + +uno::Reference< xml::sax::XFastContextHandler > SmXMLRowContext_Impl::createFastChildContext( + sal_Int32 nElement, + const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + uno::Reference< xml::sax::XFastContextHandler > xContext = StrictCreateChildContext(nElement); + + if (!xContext) + { + //Hmm, unrecognized for this level, check to see if its + //an element that can have an implicit schema around it + xContext = SmXMLDocContext_Impl::createFastChildContext(nElement, xAttrList); + } + return xContext; +} + +uno::Reference< xml::sax::XFastContextHandler > SmXMLMultiScriptsContext_Impl::createFastChildContext( + sal_Int32 nElement, + const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + uno::Reference< xml::sax::XFastContextHandler > xContext; + + switch(nElement) + { + case XML_ELEMENT(MATH, XML_MPRESCRIPTS): + bHasPrescripts = true; + ProcessSubSupPairs(false); + xContext = new SmXMLPrescriptsContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_NONE): + xContext = new SmXMLNoneContext_Impl(GetSmImport()); + break; + default: + xContext = SmXMLRowContext_Impl::createFastChildContext(nElement,xAttrList); + break; + } + return xContext; +} + +void SmXMLMultiScriptsContext_Impl::ProcessSubSupPairs(bool bIsPrescript) +{ + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + + if (rNodeStack.size() <= nElementCount) + return; + + auto nCount = rNodeStack.size() - nElementCount - 1; + if (nCount == 0) + return; + + if (nCount % 2 == 0) + { + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.eType = bIsPrescript ? TLSUB : TRSUB; + + SmNodeStack aReverseStack; + for (size_t i = 0; i < nCount + 1; i++) + { + auto pNode = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + aReverseStack.push_front(std::move(pNode)); + } + + SmSubSup eSub = bIsPrescript ? LSUB : RSUB; + SmSubSup eSup = bIsPrescript ? LSUP : RSUP; + + for (size_t i = 0; i < nCount; i += 2) + { + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(aToken)); + + // initialize subnodes array + SmNodeArray aSubNodes(1 + SUBSUP_NUM_ENTRIES); + + /*On each loop the base and its sub sup pair becomes the + base for the next loop to which the next sub sup pair is + attached, i.e. wheels within wheels*/ + aSubNodes[0] = popOrZero(aReverseStack).release(); + + std::unique_ptr<SmNode> pScriptNode = popOrZero(aReverseStack); + + if (pScriptNode && ((pScriptNode->GetToken().eType != TIDENT) || + (!pScriptNode->GetToken().aText.isEmpty()))) + aSubNodes[eSub+1] = pScriptNode.release(); + pScriptNode = popOrZero(aReverseStack); + if (pScriptNode && ((pScriptNode->GetToken().eType != TIDENT) || + (!pScriptNode->GetToken().aText.isEmpty()))) + aSubNodes[eSup+1] = pScriptNode.release(); + + pNode->SetSubNodes(std::move(aSubNodes)); + aReverseStack.push_front(std::move(pNode)); + } + assert(!aReverseStack.empty()); + auto pNode = std::move(aReverseStack.front()); + aReverseStack.pop_front(); + rNodeStack.push_front(std::move(pNode)); + } + else + { + // Ignore odd number of elements. + for (size_t i = 0; i < nCount; i++) + { + rNodeStack.pop_front(); + } + } +} + + +void SmXMLTableContext_Impl::endFastElement(sal_Int32 ) +{ + SmNodeArray aExpressionArray; + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + SmNodeStack aReverseStack; + aExpressionArray.resize(rNodeStack.size()-nElementCount); + + size_t nRows = rNodeStack.size()-nElementCount; + size_t nCols = 0; + + for (size_t i = nRows; i > 0; --i) + { + SmNode* pArray = rNodeStack.front().release(); + rNodeStack.pop_front(); + if (pArray->GetNumSubNodes() == 0) + { + //This is a little tricky, it is possible that there was + //be elements that were not inside a <mtd> pair, in which + //case they will not be in a row, i.e. they will not have + //SubNodes, so we have to wait until here before we can + //resolve the situation. Implicit surrounding tags are + //surprisingly difficult to get right within this + //architecture + + SmNodeArray aRelationArray; + aRelationArray.resize(1); + aRelationArray[0] = pArray; + SmToken aDummy; + SmExpressionNode* pExprNode = new SmExpressionNode(aDummy); + pExprNode->SetSubNodes(std::move(aRelationArray)); + pArray = pExprNode; + } + + nCols = std::max(nCols, pArray->GetNumSubNodes()); + aReverseStack.push_front(std::unique_ptr<SmNode>(pArray)); + } + if (nCols > SAL_MAX_UINT16) + throw std::range_error("column limit"); + if (nRows > SAL_MAX_UINT16) + throw std::range_error("row limit"); + aExpressionArray.resize(nCols*nRows); + size_t j=0; + for (auto & elem : aReverseStack) + { + std::unique_ptr<SmStructureNode> xArray(static_cast<SmStructureNode*>(elem.release())); + for (size_t i = 0; i < xArray->GetNumSubNodes(); ++i) + aExpressionArray[j++] = xArray->GetSubNode(i); + xArray->ClearSubNodes(); + } + aReverseStack.clear(); + + SmToken aToken; + aToken.cMathChar = '\0'; + aToken.eType = TMATRIX; + std::unique_ptr<SmMatrixNode> pSNode(new SmMatrixNode(aToken)); + pSNode->SetSubNodes(std::move(aExpressionArray)); + pSNode->SetRowCol(nRows, nCols); + rNodeStack.push_front(std::move(pSNode)); +} + +uno::Reference< xml::sax::XFastContextHandler > SmXMLTableRowContext_Impl::createFastChildContext( + sal_Int32 nElement, + const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + uno::Reference< xml::sax::XFastContextHandler > xContext; + + switch(nElement) + { + case XML_ELEMENT(MATH, XML_MTD): + xContext = new SmXMLTableCellContext_Impl(GetSmImport()); + break; + default: + xContext = SmXMLRowContext_Impl::createFastChildContext(nElement,xAttrList); + break; + } + return xContext; +} + +uno::Reference< xml::sax::XFastContextHandler > SmXMLTableContext_Impl::createFastChildContext( + sal_Int32 nElement, + const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + uno::Reference< xml::sax::XFastContextHandler > xContext; + + switch(nElement) + { + case XML_ELEMENT(MATH, XML_MTR): + xContext = new SmXMLTableRowContext_Impl(GetSmImport()); + break; + default: + xContext = SmXMLTableRowContext_Impl::createFastChildContext(nElement, xAttrList); + break; + } + return xContext; +} + +void SmXMLMultiScriptsContext_Impl::endFastElement(sal_Int32 ) +{ + ProcessSubSupPairs(bHasPrescripts); +} + +void SmXMLActionContext_Impl::startFastElement(sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList> & xAttrList) +{ + for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList )) + { + OUString sValue = aIter.toString(); + switch(aIter.getToken()) + { + case XML_SELECTION: + { + sal_uInt32 n = sValue.toUInt32(); + if (n > 0) mnSelection = static_cast<size_t>(n); + } + break; + default: + SAL_WARN("starmath", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << "=" << aIter.toString()); + break; + } + } +} + +void SmXMLActionContext_Impl::endFastElement(sal_Int32 ) +{ + SmNodeStack &rNodeStack = GetSmImport().GetNodeStack(); + auto nSize = rNodeStack.size(); + if (nSize <= nElementCount) { + // not compliant to maction's specification, e.g., no subexpressions + return; + } + assert(mnSelection > 0); + if (nSize < nElementCount + mnSelection) { + // No selected subexpression exists, which is a MathML error; + // fallback to selecting the first + mnSelection = 1; + } + assert(nSize >= nElementCount + mnSelection); + for (auto i=nSize-(nElementCount+mnSelection); i > 0; i--) + { + rNodeStack.pop_front(); + } + auto pSelected = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + for (auto i=rNodeStack.size()-nElementCount; i > 0; i--) + { + rNodeStack.pop_front(); + } + rNodeStack.push_front(std::move(pSelected)); +} + +SvXMLImportContext *SmXMLImport::CreateFastContext(sal_Int32 nElement, + const uno::Reference <xml::sax::XFastAttributeList> & /*xAttrList*/) +{ + SvXMLImportContext *pContext = nullptr; + + switch (nElement) + { + case XML_ELEMENT( OFFICE, XML_DOCUMENT ): + case XML_ELEMENT( OFFICE, XML_DOCUMENT_META ): + { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + GetModel(), uno::UNO_QUERY_THROW); + pContext = ( (nElement & TOKEN_MASK) == XML_DOCUMENT_META ) + ? new SvXMLMetaDocumentContext( *this, + xDPS->getDocumentProperties() ) + // flat OpenDocument file format -- this has not been tested... + : new SmXMLFlatDocContext_Impl( *this, + xDPS->getDocumentProperties() ); + } + break; + default: + if (IsTokenInNamespace(nElement, XML_NAMESPACE_OFFICE)) + pContext = new SmXMLOfficeContext_Impl(*this); + else + pContext = new SmXMLDocContext_Impl(*this); + } + return pContext; +} + +SmXMLImport::~SmXMLImport() throw () +{ + cleanup(); +} + +void SmXMLImport::SetViewSettings(const Sequence<PropertyValue>& aViewProps) +{ + uno::Reference <frame::XModel> xModel = GetModel(); + if ( !xModel.is() ) + return; + + SmModel *pModel = comphelper::getUnoTunnelImplementation<SmModel>(xModel); + + if ( !pModel ) + return; + + SmDocShell *pDocShell = + static_cast<SmDocShell*>(pModel->GetObjectShell()); + if ( !pDocShell ) + return; + + tools::Rectangle aRect( pDocShell->GetVisArea() ); + + long nTmp = 0; + + for (const PropertyValue& rValue : aViewProps) + { + if (rValue.Name == "ViewAreaTop" ) + { + rValue.Value >>= nTmp; + aRect.SaturatingSetY(nTmp); + } + else if (rValue.Name == "ViewAreaLeft" ) + { + rValue.Value >>= nTmp; + aRect.SaturatingSetX(nTmp); + } + else if (rValue.Name == "ViewAreaWidth" ) + { + rValue.Value >>= nTmp; + Size aSize( aRect.GetSize() ); + aSize.setWidth( nTmp ); + aRect.SaturatingSetSize(aSize); + } + else if (rValue.Name == "ViewAreaHeight" ) + { + rValue.Value >>= nTmp; + Size aSize( aRect.GetSize() ); + aSize.setHeight( nTmp ); + aRect.SaturatingSetSize(aSize); + } + } + + pDocShell->SetVisArea ( aRect ); +} + +void SmXMLImport::SetConfigurationSettings(const Sequence<PropertyValue>& aConfProps) +{ + uno::Reference < XPropertySet > xProps ( GetModel(), UNO_QUERY ); + if ( !xProps.is() ) + return; + + Reference < XPropertySetInfo > xInfo ( xProps->getPropertySetInfo() ); + if (!xInfo.is() ) + return; + + const OUString sFormula ( "Formula" ); + const OUString sBasicLibraries ( "BasicLibraries" ); + const OUString sDialogLibraries ( "DialogLibraries" ); + for ( const PropertyValue& rValue : aConfProps ) + { + if (rValue.Name != sFormula && + rValue.Name != sBasicLibraries && + rValue.Name != sDialogLibraries) + { + try + { + if ( xInfo->hasPropertyByName( rValue.Name ) ) + xProps->setPropertyValue( rValue.Name, rValue.Value ); + } + catch (const beans::PropertyVetoException &) + { + // dealing with read-only properties here. Nothing to do... + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("starmath"); + } + } + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportMML(SvStream &rStream) +{ + SmGlobals::ensure(); + + SfxObjectShellLock xDocSh(new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT)); + xDocSh->DoInitNew(); + uno::Reference<frame::XModel> xModel(xDocSh->GetModel()); + + uno::Reference<beans::XPropertySet> xInfoSet; + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + uno::Reference<io::XInputStream> xStream(new utl::OSeekableInputStreamWrapper(rStream)); + + //SetLoading hack because the document properties will be re-initted + //by the xml filter and during the init, while it's considered uninitialized, + //setting a property will inform the document it's modified, which attempts + //to update the properties, which throws cause the properties are uninitialized + xDocSh->SetLoading(SfxLoadedFlags::NONE); + + ErrCode nRet = ERRCODE_SFX_DOLOADFAILED; + + try + { + nRet = SmXMLImportWrapper::ReadThroughComponent(xStream, xModel, xContext, xInfoSet, "com.sun.star.comp.Math.XMLImporter", false); + } + catch (...) + { + } + + xDocSh->SetLoading(SfxLoadedFlags::ALL); + + xDocSh->DoClose(); + + return nRet != ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathmlimport.hxx b/starmath/source/mathmlimport.hxx new file mode 100644 index 000000000..555ea0d2c --- /dev/null +++ b/starmath/source/mathmlimport.hxx @@ -0,0 +1,119 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_MATHMLIMPORT_HXX +#define INCLUDED_STARMATH_SOURCE_MATHMLIMPORT_HXX + +#include <xmloff/xmlimp.hxx> +#include <vcl/errcode.hxx> + +#include <deque> +#include <memory> + +class SmNode; +class SfxMedium; +namespace com::sun::star { + namespace beans { + class XPropertySet; } +} + + +typedef std::deque<std::unique_ptr<SmNode>> SmNodeStack; + +class SmXMLImportWrapper +{ + css::uno::Reference<css::frame::XModel> xModel; + +public: + explicit SmXMLImportWrapper(css::uno::Reference<css::frame::XModel> const &rRef) + : xModel(rRef) {} + + ErrCode Import(SfxMedium &rMedium); + + static ErrCode ReadThroughComponent( + const css::uno::Reference< css::io::XInputStream >& xInputStream, + const css::uno::Reference< css::lang::XComponent >& xModelComponent, + css::uno::Reference< css::uno::XComponentContext > const & rxContext, + css::uno::Reference< css::beans::XPropertySet > const & rPropSet, + const char* pFilterName, + bool bEncrypted ); + + static ErrCode ReadThroughComponent( + const css::uno::Reference< css::embed::XStorage >& xStorage, + const css::uno::Reference< css::lang::XComponent >& xModelComponent, + const char* pStreamName, + css::uno::Reference< css::uno::XComponentContext > const & rxContext, + css::uno::Reference< css::beans::XPropertySet > const & rPropSet, + const char* pFilterName ); +}; + + +class SmXMLImport : public SvXMLImport +{ + std::unique_ptr<SvXMLTokenMap> pColorTokenMap; + + SmNodeStack aNodeStack; + bool bSuccess; + int nParseDepth; + OUString aText; + +public: + SmXMLImport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + OUString const & implementationName, SvXMLImportFlags nImportFlags); + virtual ~SmXMLImport() throw () override; + + // XUnoTunnel + sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) override; + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId() throw(); + + void SAL_CALL endDocument() override; + + SvXMLImportContext *CreateFastContext( sal_Int32 nElement, + const css::uno::Reference< + css::xml::sax::XFastAttributeList >& xAttrList ) override; + + const SvXMLTokenMap &GetColorTokenMap(); + + SmNodeStack & GetNodeStack() { return aNodeStack; } + + bool GetSuccess() const { return bSuccess; } + [[nodiscard]] const OUString& GetText() const { return aText; } + void SetText(const OUString &rStr) { aText = rStr; } + + virtual void SetViewSettings(const css::uno::Sequence<css::beans::PropertyValue>& aViewProps) override; + virtual void SetConfigurationSettings(const css::uno::Sequence<css::beans::PropertyValue>& aViewProps) override; + + void IncParseDepth() { ++nParseDepth; } + bool TooDeep() const { return nParseDepth >= 2048; } + void DecParseDepth() { --nParseDepth; } +}; + + +enum SmXMLPresScriptEmptyElemTokenMap +{ + XML_TOK_MPRESCRIPTS, + XML_TOK_NONE +}; + + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathtype.cxx b/starmath/source/mathtype.cxx new file mode 100644 index 000000000..13297eb7f --- /dev/null +++ b/starmath/source/mathtype.cxx @@ -0,0 +1,3345 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "mathtype.hxx" + +#include <filter/msfilter/classids.hxx> +#include <osl/diagnose.h> +#include <sfx2/docfile.hxx> +#include <sot/storage.hxx> +#include <sal/log.hxx> + +#include "eqnolefilehdr.hxx" +#include <node.hxx> + +void MathType::Init() +{ + //These are the default MathType sizes + aSizeTable.push_back(12); + aSizeTable.push_back(8); + aSizeTable.push_back(6); + aSizeTable.push_back(24); + aSizeTable.push_back(10); + aSizeTable.push_back(12); + aSizeTable.push_back(12); + + /* + These are the default MathType italic/bold settings If mathtype is changed + from its defaults, there is nothing we can do, as this information is not + stored in the document + */ + MathTypeFont aFont; + for(sal_uInt8 i=1;i<=11;i++) + { + aFont.nTface = i+128; + switch (i) + { + default: + aFont.nStyle=0; + break; + case 3: + case 4: + aFont.nStyle=1; + break; + case 7: + aFont.nStyle=2; + break; + } + aUserStyles.insert(aFont); + } +} + + +/*ToDo replace with table rather than switch, returns + sal_True in the case that the char is just a char, and + sal_False if the character is an operator which must not be + placed inside the quote sequence designed to protect + against being parsed as a keyword + + General solution required to force starmath to handle + unicode math chars the way it handles its own math + chars rather than handle them as text as it will do + for the default case below, i.e. incorrect spacing + between math symbols and ordinary text e.g. 1=2 rather + than 1 = 2 + */ +bool MathType::LookupChar(sal_Unicode nChar,OUStringBuffer &rRet,sal_uInt8 nVersion, + sal_uInt8 nTypeFace) +{ + bool bRet=false; + const char *pC = nullptr; + switch(nChar) + { + case 0x0000: + pC = " none "; + break; + case 0x00ac: + pC = " neg "; + break; + case 0x00b1: + pC = " +- "; + break; + case '(': + pC = " \\( "; + break; + case ')': + pC = " \\) "; + break; + case '[': + pC = " \\[ "; + break; + case ']': + pC = " \\] "; + break; + case '.': + pC = " \".\" "; + break; + case 0xae: + if ((nVersion < 3) && (nTypeFace == 0x86)) + pC = " rightarrow "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x00fb: + if ((nVersion < 3) && (nTypeFace == 0x81)) + nChar = 0xDF; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'a': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3b1; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'b': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3b2; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'l': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3bb; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'n': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3bd; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'r': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3c1; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'D': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x394; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 0xa9: + if ((nVersion < 3) && (nTypeFace == 0x82)) + nChar = '\''; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 0x00f1: + if ((nVersion < 3) && (nTypeFace == 0x86)) + pC = " \\rangle "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x00a3: + if ((nVersion < 3) && (nTypeFace == 0x86)) + pC = " <= "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x00de: + if ((nVersion < 3) && (nTypeFace == 0x86)) + pC = " drarrow "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x0057: + if ((nVersion < 3) && (nTypeFace == 0x85)) + pC = " %OMEGA "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x007b: + pC = " lbrace "; + break; + case 0x007c: + pC = " \\lline "; + break; + case 0x007d: + pC = " rbrace "; + break; + case 0x007e: + pC = " \"~\" "; + break; + case 0x2224: + pC = " ndivides "; + break; + case 0x2225: + pC = " parallel "; + break; + case 0x00d7: + if (nVersion < 3) + pC = " cdot "; + else + pC = " times "; + break; + case 0x00f7: + pC = " div "; + break; + case 0x019b: + pC = " lambdabar "; + break; + case 0x2026: + pC = " dotslow "; + break; + case 0x2022: + pC = " cdot "; + break; + case 0x2102: + pC = " setC "; + break; + case 0x210f: + pC = " hbar "; + break; + case 0x2111: + pC = " Im "; + break; + case 0x2115: + pC = " setN "; + break; + case 0x2118: + pC = " wp "; + break; + case 0x211a: + pC = " setQ "; + break; + case 0x211c: + pC = " Re "; + break; + case 0x211d: + pC = " setR "; + break; + case 0x2124: + pC = " setZ "; + break; + case 0x2135: + pC = " aleph "; + break; + case 0x2190: + pC = " leftarrow "; + break; + case 0x2191: + pC = " uparrow "; + break; + case 0x2192: + pC = " rightarrow "; + break; + case 0x0362: + pC = " widevec "; + break; + case 0x2193: + pC = " downarrow "; + break; + case 0x21d0: + pC = " dlarrow "; + break; + case 0x21d2: + pC = " drarrow "; + break; + case 0x21d4: + pC = " dlrarrow "; + break; + case 0x2200: + pC = " forall "; + break; + case 0x2202: + pC = " partial "; + break; + case 0x2203: + pC = " exists "; + break; + case 0x2204: + pC = " notexists "; + break; + case 0x2205: + pC = " emptyset "; + break; + case 0x2207: + pC = " nabla "; + break; + case 0x2112: + pC = " laplace "; + break; + case 0x2208: // in + case 0x2209: // notin + rRet.append(" func ").append(OUStringChar(nChar)).append(" "); + break; + case 0x220d: // owns + rRet.append(u" func \u220b "); + break; + case 0x220f: + pC = " prod "; + break; + case 0x2210: + pC = " coprod "; + break; + case 0x2211: + pC = " sum "; + break; + case 0x2212: + pC = " - "; + break; + case 0x2213: + pC = " -+ "; + break; + case 0x2217: + pC = " * "; + break; + case 0x2218: + pC = " circ "; + break; + case 0x221d: + pC = " prop "; + break; + case 0x221e: + pC = " infinity "; + break; + case 0x2227: + pC = " and "; + break; + case 0x2228: + pC = " or "; + break; + case 0x2229: + pC = " intersection "; + break; + case 0x222a: + pC = " union "; + break; + case 0x222b: + pC = " int "; + break; + case 0x222c: + pC = " iint "; + break; + case 0x222d: + pC = " iiint "; + break; + case 0x222e: + pC = " lint "; + break; + case 0x222f: + pC = " llint "; + break; + case 0x2230: + pC = " lllint "; + break; + case 0x2245: + pC = " simeq "; + break; + case 0x2248: + pC = " approx "; + break; + case 0x2260: + pC = " <> "; + break; + case 0x2261: + pC = " equiv "; + break; + case 0x2264: + pC = " <= "; + break; + case 0x2265: + pC = " >= "; + break; + + case 0x227A: + pC = " prec "; + break; + case 0x227B: + pC = " succ "; + break; + case 0x227C: + pC = " preccurlyeq "; + break; + case 0x227D: + pC = " succcurlyeq "; + break; + case 0x227E: + pC = " precsim "; + break; + case 0x227F: + pC = " succsim "; + break; + case 0x2280: + pC = " nprec "; + break; + case 0x2281: + pC = " nsucc "; + break; + + case 0x2282: // subset + case 0x2283: // supset + case 0x2284: // nsubset + case 0x2285: // nsupset + case 0x2286: // subseteq + case 0x2287: // supseteq + case 0x2288: // nsubseteq + case 0x2289: // nsupseteq + case 0x22b2: // NORMAL SUBGROUP OF + case 0x22b3: // CONTAINS AS NORMAL SUBGROUP + rRet.append(" func ").append(OUStringChar(nChar)).append(" "); + break; + case 0x22a5: + pC = " ortho "; + break; + case 0x22c5: + pC = " cdot "; + break; + case 0x22ee: + pC = " dotsvert "; + break; + case 0x22ef: + pC = " dotsaxis "; + break; + case 0x22f0: + pC = " dotsup "; + break; + case 0x22f1: + pC = " dotsdown "; + break; + case MS_LANGLE: + case MS_LMATHANGLE: + pC = " langle "; + break; + case MS_RANGLE: + case MS_RMATHANGLE: + pC = " rangle "; + break; + case 0x301a: + pC = " ldbracket "; + break; + case 0x301b: + pC = " rdbracket "; + break; + case 0xe083: + rRet.append("+"); + bRet=true; + break; + case '^': + case 0xe091: + pC = " widehat "; + break; + case 0xe096: + pC = " widetilde "; + break; + case 0xe098: + pC = " widevec "; + break; + case 0xE421: + pC = " geslant "; + break; + case 0xE425: + pC = " leslant "; + break; + case 0xeb01: //no space + case 0xeb08: //normal space + bRet=true; + break; + case 0xef04: //tiny space + case 0xef05: //tiny space + case 0xeb02: //small space + case 0xeb04: //medium space + rRet.append("`"); + break; + case 0xeb05: //large space + rRet.append("~"); + break; + case 0x3a9: + pC = " %OMEGA "; + break; + default: + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + } + if (pC) + rRet.appendAscii(pC); + return bRet; +} + +void MathTypeFont::AppendStyleToText(OUString &rRet) +{ + const char *pC = nullptr; + switch (nStyle) + { + default: + case 0: + break; + case 1: + pC = " ital "; + break; + case 2: + pC = " bold "; + break; + case 3: + pC = " bold italic"; + break; + } + if (pC) + rRet += OUString::createFromAscii( pC ); +} + +void MathType::TypeFaceToString(OUString &rTxt,sal_uInt8 nFace) +{ + MathTypeFont aFont(nFace); + MathTypeFontSet::iterator aItr = aUserStyles.find(aFont); + if (aItr != aUserStyles.end()) + aFont.nStyle = aItr->nStyle; + aFont.AppendStyleToText(rTxt); +} + +bool MathType::Parse(SotStorage *pStor) +{ + tools::SvRef<SotStorageStream> xSrc = pStor->OpenSotStream( + "Equation Native", + StreamMode::STD_READ); + if ( (!xSrc.is()) || (ERRCODE_NONE != xSrc->GetError())) + return false; + return Parse(xSrc.get()); +} + +bool MathType::Parse(SvStream* pStream) +{ + pS = pStream; + pS->SetEndian( SvStreamEndian::LITTLE ); + + EQNOLEFILEHDR aHdr; + aHdr.Read(pS); + sal_uInt8 nProdVersion; + sal_uInt8 nProdSubVersion; + sal_uInt8 nPlatform; + sal_uInt8 nProduct; + pS->ReadUChar( nVersion ); + pS->ReadUChar( nPlatform ); + pS->ReadUChar( nProduct ); + pS->ReadUChar( nProdVersion ); + pS->ReadUChar( nProdSubVersion ); + + if (nVersion > 3) // allow only supported versions of MathType to be parsed + return false; + + bool bRet = HandleRecords(0); + //little crude hack to close occasionally open expressions + //a sophisticated system to determine what expressions are + //opened is required, but this is as much work as rewriting + //starmaths internals. + rRet.append("{}"); + + return bRet; +} + +static void lcl_PrependDummyTerm(OUStringBuffer &rRet, sal_Int32 &rTextStart) +{ + if ((rTextStart < rRet.getLength()) && + (rRet[rTextStart] == '=') && + ((rTextStart == 0) || (rRet[ rTextStart-1 ] == '{')) + ) + { + rRet.insert(rTextStart, " {}"); + rTextStart+=3; + } +} + +static void lcl_AppendDummyTerm(OUStringBuffer &rRet) +{ + bool bOk=false; + for(int nI=rRet.getLength()-1;nI >= 0; nI--) + { + sal_Int32 nIdx = sal::static_int_cast< sal_Int32 >(nI); + sal_Unicode nChar = rRet[nIdx]; + if (nChar == ' ') + continue; + if (rRet[nIdx] != '{') + bOk=true; + break; + } + if (!bOk) //No term, use dummy + rRet.append(" {}"); +} + +void MathType::HandleNudge() +{ + sal_uInt8 nXNudge; + pS->ReadUChar( nXNudge ); + sal_uInt8 nYNudge; + pS->ReadUChar( nYNudge ); + if (nXNudge == 128 && nYNudge == 128) + { + sal_uInt16 nXLongNudge; + sal_uInt16 nYLongNudge; + pS->ReadUInt16( nXLongNudge ); + pS->ReadUInt16( nYLongNudge ); + } +} + +/* Fabulously complicated as many tokens have to be reordered and generally + * moved around from mathtypes paradigm to starmaths. */ +bool MathType::HandleRecords(int nLevel, sal_uInt8 nSelector, + sal_uInt8 nVariation, int nMatrixRows, int nMatrixCols) +{ + //depth-protect + if (nLevel > 1024) + return false; + + sal_uInt8 nTag,nRecord; + sal_uInt8 nTabType,nTabStops; + sal_uInt16 nTabOffset; + int i, newline=0; + bool bSilent=false; + int nPart=0; + OUString sPush,sMainTerm; + int nSetSize=0,nSetAlign=0; + int nCurRow=0,nCurCol=0; + bool bOpenString=false; + sal_Int32 nTextStart = 0; + sal_Int32 nSubSupStartPos = 0; + sal_Int32 nLastTemplateBracket=-1; + bool bRet = true; + + do + { + nTag = 0; + pS->ReadUChar( nTag ); + nRecord = nTag&0x0F; + + /*MathType strings can of course include words which + *are StarMath keywords, the simplest solution is + to escape strings of greater than len 1 with double + quotes to avoid scanning the TokenTable for matches + + Unfortunately it may turn out that the string gets + split during the handling of a character emblishment + so this special case must be handled in the + character handler case 2: + */ + if ((nRecord == CHAR) && (!bOpenString)) + { + bOpenString=true; + nTextStart = rRet.getLength(); + } + else if ((nRecord != CHAR) && bOpenString) + { + bOpenString=false; + if ((rRet.getLength() - nTextStart) > 1) + { + OUString aStr; + TypeFaceToString(aStr,nTypeFace); + aStr += "\""; + rRet.insert(nTextStart,aStr); + rRet.append("\""); + } + else if (nRecord == END && !rRet.isEmpty()) + { + sal_Unicode cChar = 0; + sal_Int32 nI = rRet.getLength()-1; + while (nI) + { + cChar = rRet[nI]; + if (cChar != ' ') + break; + --nI; + } + if ((cChar == '=') || (cChar == '+') || (cChar == '-')) + rRet.append("{}"); + } + } + + switch(nRecord) + { + case LINE: + { + if (xfLMOVE(nTag)) + HandleNudge(); + + if (newline>0) + rRet.append("\nnewline\n"); + if (!(xfNULL(nTag))) + { + switch (nSelector) + { + case tmANGLE: + if (nVariation==0) + rRet.append(" langle "); + else if (nVariation==1) + rRet.append(" \\langle "); + break; + case tmPAREN: + if (nVariation==0) + rRet.append(" left ("); + else if (nVariation==1) + rRet.append("\\("); + break; + case tmBRACE: + if ((nVariation==0) || (nVariation==1)) + rRet.append(" left lbrace "); + else + rRet.append(" left none "); + break; + case tmBRACK: + if (nVariation==0) + rRet.append(" left ["); + else if (nVariation==1) + rRet.append("\\["); + break; + case tmLBLB: + case tmLBRP: + rRet.append(" \\["); + break; + case tmBAR: + if (nVariation==0) + rRet.append(" lline "); + else if (nVariation==1) + rRet.append(" \\lline "); + break; + case tmDBAR: + if (nVariation==0) + rRet.append(" ldline "); + else if (nVariation==1) + rRet.append(" \\ldline "); + break; + case tmFLOOR: + if (nVariation == 0 || nVariation & 0x01) // tvFENCE_L + rRet.append(" left lfloor "); + else + rRet.append(" left none "); + break; + case tmCEILING: + if (nVariation==0) + rRet.append(" lceil "); + else if (nVariation==1) + rRet.append(" \\lceil "); + break; + case tmRBRB: + case tmRBLB: + rRet.append(" \\]"); + break; + case tmLPRB: + rRet.append(" \\("); + break; + case tmROOT: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" sqrt"); + else + { + rRet.append(" nroot"); + sPush = rRet.makeStringAndClear(); + } + } + rRet.append(" {"); + break; + case tmFRACT: + if (nPart == 0) + rRet.append(" { "); + + + if (nPart == 1) + rRet.append(" over "); + rRet.append(" {"); + break; + case tmSCRIPT: + nSubSupStartPos = rRet.getLength(); + if ((nVariation == 0) || + ((nVariation == 2) && (nPart==1))) + { + lcl_AppendDummyTerm(rRet); + rRet.append(" rSup"); + } + else if ((nVariation == 1) || + ((nVariation == 2) && (nPart==0))) + { + lcl_AppendDummyTerm(rRet); + rRet.append(" rSub"); + } + rRet.append(" {"); + break; + case tmUBAR: + if (nVariation == 0) + rRet.append(" {underline "); + else if (nVariation == 1) + rRet.append(" {underline underline "); + rRet.append(" {"); + break; + case tmOBAR: + if (nVariation == 0) + rRet.append(" {overline "); + else if (nVariation == 1) + rRet.append(" {overline overline "); + rRet.append(" {"); + break; + case tmLARROW: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" widevec ");//left arrow above + else if (nVariation == 1) + rRet.append(" widevec ");//left arrow below + rRet.append(" {"); + } + break; + case tmRARROW: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" widevec ");//right arrow above + else if (nVariation == 1) + rRet.append(" widevec ");//right arrow below + rRet.append(" {"); + } + break; + case tmBARROW: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" widevec ");//double arrow above + else if (nVariation == 1) + rRet.append(" widevec ");//double arrow below + rRet.append(" {"); + } + break; + case tmSINT: + if (nPart == 0) + { + if ((nVariation == 3) || (nVariation == 4)) + rRet.append(" lInt"); + else + rRet.append(" Int"); + if ( (nVariation != 0) && (nVariation != 3)) + { + sPush = rRet.makeStringAndClear(); + } + } + if (((nVariation == 1) || + (nVariation == 4)) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 2) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 2) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmDINT: + if (nPart == 0) + { + if ((nVariation == 2) || (nVariation == 3)) + rRet.append(" llInt"); + else + rRet.append(" iInt"); + if ( (nVariation != 0) && (nVariation != 2)) + { + sPush = rRet.makeStringAndClear(); + } + } + if (((nVariation == 1) || + (nVariation == 3)) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmTINT: + if (nPart == 0) + { + if ((nVariation == 2) || (nVariation == 3)) + rRet.append(" lllInt"); + else + rRet.append(" iiInt"); + if ( (nVariation != 0) && (nVariation != 2)) + { + sPush = rRet.makeStringAndClear(); + } + } + if (((nVariation == 1) || + (nVariation == 3)) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmSSINT: + if (nPart == 0) + { + if (nVariation == 2) + rRet.append(" lInt"); + else + rRet.append(" Int"); + sPush = rRet.makeStringAndClear(); + } + if (((nVariation == 1) || + (nVariation == 2)) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 0) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmDSINT: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" llInt"); + else + rRet.append(" iInt"); + sPush = rRet.makeStringAndClear(); + } + if (nPart==1) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmTSINT: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" lllInt"); + else + rRet.append(" iiInt"); + sPush = rRet.makeStringAndClear(); + } + if (nPart==1) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmUHBRACE: + case tmLHBRACE: + rRet.append(" {"); + break; + case tmSUM: + if (nPart == 0) + { + rRet.append(" Sum"); + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmISUM: + if (nPart == 0) + { + rRet.append(" Sum"); + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmPROD: + if (nPart == 0) + { + rRet.append(" Prod"); + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmIPROD: + if (nPart == 0) + { + rRet.append(" Prod"); + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmCOPROD: + if (nPart == 0) + { + rRet.append(" coProd"); + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmICOPROD: + if (nPart == 0) + { + rRet.append(" coProd"); + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmUNION: + if (nPart == 0) + { + rRet.append(" union"); //union + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmIUNION: + if (nPart == 0) + { + rRet.append(" union"); //union + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmINTER: + if (nPart == 0) + { + rRet.append(" intersect"); //intersect + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmIINTER: + if (nPart == 0) + { + rRet.append(" intersect"); //intersect + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmLIM: + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 2) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 2) && (nPart==2)) + rRet.append(" cSup"); + rRet.append(" {"); + break; + case tmLDIV: + if (nVariation == 0) + { + if (nPart == 0) + { + sPush = rRet.makeStringAndClear(); + } + } + rRet.append(" {"); + if (nVariation == 0) + { + if (nPart == 1) + rRet.append("alignr "); + } + if (nPart == 0) + rRet.append("\\lline "); + if (nVariation == 1) + rRet.append("overline "); + break; + case tmSLFRACT: + rRet.append(" {"); + break; + case tmINTOP: + if (nPart == 0) + { + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==0)) + rRet.append(" rSup"); + else if ((nVariation == 2) && (nPart==1)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==0)) + rRet.append(" rSub"); + else if ((nVariation == 2) && (nPart==0)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmSUMOP: + if (nPart == 0) + { + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==0)) + rRet.append(" cSup"); + else if ((nVariation == 2) && (nPart==1)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==0)) + rRet.append(" cSub"); + else if ((nVariation == 2) && (nPart==0)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmLSCRIPT: + if (nPart == 0) + rRet.append("\"\""); + if ((nVariation == 0) + || ((nVariation == 2) && (nPart==1))) + rRet.append(" lSup"); + else if ((nVariation == 1) + || ((nVariation == 2) && (nPart==0))) + rRet.append(" lSub"); + rRet.append(" {"); + break; + case tmDIRAC: + if (nVariation==0) + { + if (nPart == 0) + rRet.append(" langle "); + } + else if (nVariation==1) + { + rRet.append(" \\langle "); + newline--; + } + else if (nVariation==2) + { + rRet.append(" \\lline "); + newline--; + } + break; + case tmUARROW: + if (nVariation == 0) + rRet.append(" widevec ");//left below + else if (nVariation == 1) + rRet.append(" widevec ");//right below + else if (nVariation == 2) + rRet.append(" widevec ");//double headed below + rRet.append(" {"); + break; + case tmOARROW: + if (nVariation == 0) + rRet.append(" widevec ");//left above + else if (nVariation == 1) + rRet.append(" widevec ");//right above + else if (nVariation == 2) + rRet.append(" widevec ");//double headed above + rRet.append(" {"); + break; + default: + break; + } + sal_Int16 nOldCurSize=nCurSize; + sal_Int32 nSizeStartPos = rRet.getLength(); + HandleSize( nLSize, nDSize, nSetSize ); + bRet = HandleRecords( nLevel+1 ); + while (nSetSize) + { + bool bOk=false; + sal_Int32 nI = rRet.lastIndexOf('{'); + if (nI != -1) + { + for(nI=nI+1;nI<rRet.getLength();nI++) + if (rRet[nI] != ' ') + { + bOk=true; + break; + } + } + else + bOk=true; + + if (bOk) + rRet.append("} "); + else if (rRet.getLength() > nSizeStartPos) + rRet = rRet.truncate(nSizeStartPos); + nSetSize--; + nCurSize=nOldCurSize; + } + + + HandleMatrixSeparator(nMatrixRows,nMatrixCols, + nCurCol,nCurRow); + + switch (nSelector) + { + case tmANGLE: + if (nVariation==0) + rRet.append(" rangle "); + else if (nVariation==2) + rRet.append(" \\rangle "); + break; + case tmPAREN: + if (nVariation==0) + rRet.append(" right )"); + else if (nVariation==2) + rRet.append("\\)"); + break; + case tmBRACE: + if ((nVariation==0) || (nVariation==2)) + rRet.append(" right rbrace "); + else + rRet.append(" right none "); + break; + case tmBRACK: + if (nVariation==0) + rRet.append(" right ]"); + else if (nVariation==2) + rRet.append("\\]"); + break; + case tmBAR: + if (nVariation==0) + rRet.append(" rline "); + else if (nVariation==2) + rRet.append(" \\rline "); + break; + case tmDBAR: + if (nVariation==0) + rRet.append(" rdline "); + else if (nVariation==2) + rRet.append(" \\rdline "); + break; + case tmFLOOR: + if (nVariation == 0 || nVariation & 0x02) // tvFENCE_R + rRet.append(" right rfloor "); + else + rRet.append(" right none "); + break; + case tmCEILING: + if (nVariation==0) + rRet.append(" rceil "); + else if (nVariation==2) + rRet.append(" \\rceil "); + break; + case tmLBLB: + case tmRBLB: + rRet.append("\\["); + break; + case tmRBRB: + case tmLPRB: + rRet.append("\\]"); + break; + case tmROOT: + rRet.append("} "); + if (nVariation == 1) + { + if (nPart == 0) + { + newline--; + sMainTerm = rRet.makeStringAndClear(); + } + else if (nPart == 1) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + } + } + else + { + if (nPart == 0) + newline--; + } + nPart++; + break; + case tmLBRP: + rRet.append("\\)"); + break; + case tmFRACT: + rRet.append("} "); + if (nPart == 0) + newline--; + else + rRet.append("} "); + nPart++; + break; + case tmSCRIPT: + { + if ((nPart == 0) && + ((nVariation == 2) || (nVariation == 1))) + newline--; + + bool bOk=false; + sal_Int32 nI = rRet.lastIndexOf('{'); + if (nI != -1) + { + for(nI=nI+1;nI<rRet.getLength();nI++) + if (rRet[nI] != ' ') + { + bOk=true; + break; + } + } + else + bOk=true; + + if (bOk) + rRet.append("} "); + else if (rRet.getLength() > nSubSupStartPos) + rRet = rRet.truncate(nSubSupStartPos); + nPart++; + } + break; + case tmLSCRIPT: + if ((nPart == 0) && + ((nVariation == 2) || (nVariation == 1))) + newline--; + rRet.append("} "); + nPart++; + break; + case tmUARROW: + case tmOARROW: + rRet.append("} "); + break; + case tmUBAR: + case tmOBAR: + rRet.append("}} "); + break; + case tmLARROW: + case tmRARROW: + case tmBARROW: + if (nPart == 0) + { + newline--; + rRet.append("} "); + } + nPart++; + break; + case tmUHBRACE: + rRet.append("} "); + if (nPart == 0) + { + newline--; + rRet.append("overbrace"); + } + nPart++; + break; + case tmLHBRACE: + rRet.append("} "); + if (nPart == 0) + { + newline--; + rRet.append("underbrace"); + } + nPart++; + break; + case tmLIM: + if (nPart==0) + newline--; + else if ((nPart==1) && + ((nVariation == 2) || (nVariation == 1))) + newline--; + rRet.append("} "); + nPart++; + break; + case tmLDIV: + rRet.append("} "); + if (nVariation == 0) + { + if (nPart == 0) + { + sMainTerm = rRet.makeStringAndClear(); + } + else if (nPart == 1) + { + rRet.insert(0, sPush); + rRet.append(" over ").append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + } + } + if (nPart == 0) + newline--; + nPart++; + break; + case tmSLFRACT: + rRet.append("} "); + if (nPart == 0) + { + newline--; + switch (nVariation) + { + case 1: + rRet.append("slash"); + break; + default: + rRet.append("wideslash"); + break; + } + } + nPart++; + break; + case tmSUM: + case tmISUM: + case tmPROD: + case tmIPROD: + case tmCOPROD: + case tmICOPROD: + case tmUNION: + case tmIUNION: + case tmINTER: + case tmIINTER: + rRet.append("} "); + if (nPart == 0) + { + if (nVariation != 2) + { + sMainTerm = rRet.makeStringAndClear(); + } + newline--; + } + else if ((nPart == 1) && (nVariation == 0)) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + else if ((nPart == 1) && (nVariation == 1)) + newline--; + else if ((nPart == 2) && (nVariation == 1)) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmSINT: + rRet.append("} "); + if (nPart == 0) + { + if ((nVariation != 0) && (nVariation != 3)) + { + sMainTerm = rRet.makeStringAndClear(); + } + newline--; + } + else if ((nPart == 1) && + ((nVariation == 1) || (nVariation==4))) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + else if ((nPart == 1) && (nVariation == 2)) + newline--; + else if ((nPart == 2) && (nVariation == 2)) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmDINT: + case tmTINT: + rRet.append("} "); + if (nPart == 0) + { + if ((nVariation != 0) && (nVariation != 2)) + { + sMainTerm = rRet.makeStringAndClear(); + } + newline--; + } + else if ((nPart == 1) && + ((nVariation == 1) || (nVariation==3))) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmSSINT: + rRet.append("} "); + if (nPart == 0) + { + sMainTerm = rRet.makeStringAndClear(); + newline--; + } + else if ((nPart == 1) && + ((nVariation == 1) || (nVariation==2))) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + else if ((nPart == 1) && (nVariation == 0)) + newline--; + else if ((nPart == 2) && (nVariation == 0)) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmDSINT: + case tmTSINT: + rRet.append("} "); + if (nPart == 0) + { + sMainTerm = rRet.makeStringAndClear(); + newline--; + } + else if (nPart == 1) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmINTOP: + case tmSUMOP: + rRet.append("} "); + + if ((nPart == 0) && + ((nVariation == 0) || (nVariation == 1))) + { + sMainTerm = rRet.makeStringAndClear(); + newline--; + } + else if ((nPart == 0) && (nVariation == 2)) + newline--; + else if ((nPart == 1) && (nVariation == 2)) + { + sMainTerm = rRet.makeStringAndClear(); + newline--; + } + else if ((nPart == 2) || ((nPart == 1) && + (nVariation == 0 || nVariation == 1))) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + } + nPart++; + break; + case tmDIRAC: + if (nVariation==0) + { + if (nPart == 0) + { + newline--; //there is another term to arrive + rRet.append(" mline "); + } + else + rRet.append(" rangle "); + } + else if (nVariation==1) + rRet.append(" \\lline "); + else if (nVariation==2) + rRet.append(" \\rangle "); + nPart++; + break; + default: + break; + } + bSilent = true; //Skip the optional brackets and/or + //symbols that follow some of these + //records. Foo Data. + + /*In matrices and piles we cannot separate equation + *lines with the newline keyword*/ + if (nMatrixCols==0) + newline++; + } + } + break; + case CHAR: + if (xfLMOVE(nTag)) + HandleNudge(); + bRet = HandleChar( nTextStart, nSetSize, nLevel, nTag, nSelector, nVariation, bSilent ); + break; + case TMPL: + if (xfLMOVE(nTag)) + HandleNudge(); + bRet = HandleTemplate( nLevel, nSelector, nVariation, nLastTemplateBracket ); + break; + case PILE: + if (xfLMOVE(nTag)) + HandleNudge(); + bRet = HandlePile( nSetAlign, nLevel, nSelector, nVariation ); + HandleMatrixSeparator( nMatrixRows, nMatrixCols, nCurCol, nCurRow ); + break; + case MATRIX: + if (xfLMOVE(nTag)) + HandleNudge(); + bRet = HandleMatrix( nLevel, nSelector, nVariation ); + HandleMatrixSeparator( nMatrixRows, nMatrixCols, nCurCol, nCurRow ); + break; + case EMBEL: + if (xfLMOVE(nTag)) + HandleNudge(); + HandleEmblishments(); + break; + case RULER: + pS->ReadUChar( nTabStops ); + for (i=0;i<nTabStops;i++) + { + pS->ReadUChar( nTabType ); + pS->ReadUInt16( nTabOffset ); + } + SAL_WARN("starmath", "Not seen in the wild Equation Ruler Field"); + break; + case FONT: + { + MathTypeFont aFont; + pS->ReadUChar( aFont.nTface ); + /* + The typeface number is the negative (which makes it + positive) of the typeface value (unbiased) that appears in + CHAR records that might follow a given FONT record + */ + aFont.nTface = 128-aFont.nTface; + pS->ReadUChar( aFont.nStyle ); + aUserStyles.insert(aFont); + // read font name + while(true) + { + char nChar8(0); + pS->ReadChar( nChar8 ); + if (nChar8 == 0) + break; + } + } + break; + case SIZE: + HandleSetSize(); + break; + case 10: + case 11: + case 12: + case 13: + case 14: + nLSize=nRecord-10; + break; + case END: + default: + break; + } + } + while (nRecord != END && !pS->eof()); + while (nSetSize) + { + rRet.append("}"); + nSetSize--; + } + return bRet; +} + +/*Simply determine if we are at the end of a record or the end of a line, + *with fiddly logic to see if we are in a matrix or a pile or neither + + Note we cannot tell until after the event that this is the last entry + of a pile, so we must strip the last separator of a pile after this + is detected in the PILE handler + */ +void MathType::HandleMatrixSeparator(int nMatrixRows,int nMatrixCols, + int &rCurCol,int &rCurRow) +{ + if (nMatrixRows==0) + return; + + if (rCurCol == nMatrixCols-1) + { + if (rCurRow != nMatrixRows-1) + rRet.append(" {} ##\n"); + if (nMatrixRows!=-1) + { + rCurCol=0; + rCurRow++; + } + } + else + { + rRet.append(" {} # "); + if (nMatrixRows!=-1) + rCurCol++; + else + rRet.append("\n"); + } +} + +/* set the alignment of the following term, but starmath currently + * cannot handle vertical alignment */ +void MathType::HandleAlign(sal_uInt8 nHorAlign, int &rSetAlign) +{ + switch(nHorAlign) + { + case 1: + default: + rRet.append("alignl {"); + break; + case 2: + rRet.append("alignc {"); + break; + case 3: + rRet.append("alignr {"); + break; + } + rSetAlign++; +} + +/* set size of text, complexity due to overuse of signedness as a flag + * indicator by mathtype file format*/ +bool MathType::HandleSize(sal_Int16 nLstSize,sal_Int16 nDefSize, int &rSetSize) +{ + const sal_Int16 nDefaultSize = 12; + bool bRet=false; + if (nLstSize < 0) + { + if ((-nLstSize/32 != nDefaultSize) && (-nLstSize/32 != nCurSize)) + { + if (rSetSize) + { + rSetSize--; + rRet.append("}"); + bRet=true; + } + if (-nLstSize/32 != nLastSize) + { + nLastSize = nCurSize; + rRet.append(" size "); + rRet.append(OUString::number(-nLstSize/32)); + rRet.append("{"); + bRet=true; + rSetSize++; + } + nCurSize = -nLstSize/32; + } + } + else + { + /*sizetable should theoretically be filled with the default sizes + *of the various font groupings matching starmaths equivalents + in aTypeFaces, and a test would be done to see if the new font + size would be the same as what starmath would have chosen for + itself anyway in which case the size setting could be ignored*/ + nLstSize = aSizeTable.at(nLstSize); + nLstSize = nLstSize + nDefSize; + if (nLstSize != nCurSize) + { + if (rSetSize) + { + rSetSize--; + rRet.append("}"); + bRet=true; + } + if (nLstSize != nLastSize) + { + nLastSize = nCurSize; + rRet.append(" size "); + rRet.append(OUString::number(nLstSize)); + rRet.append("{"); + bRet=true; + rSetSize++; + } + nCurSize = nLstSize; + } + } + return bRet; +} + +bool MathType::ConvertFromStarMath( SfxMedium& rMedium ) +{ + if (!pTree) + return false; + + SvStream *pStream = rMedium.GetOutStream(); + if ( pStream ) + { + tools::SvRef<SotStorage> pStor = new SotStorage( pStream, false ); + + SvGlobalName aGName(MSO_EQUATION3_CLASSID); + pStor->SetClass( aGName, SotClipboardFormatId::NONE, "Microsoft Equation 3.0"); + + static sal_uInt8 const aCompObj[] = { + 0x01, 0x00, 0xFE, 0xFF, 0x03, 0x0A, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0xCE, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x46, 0x17, 0x00, 0x00, 0x00, + 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, + 0x74, 0x20, 0x45, 0x71, 0x75, 0x61, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x33, 0x2E, 0x30, 0x00, 0x0C, + 0x00, 0x00, 0x00, 0x44, 0x53, 0x20, 0x45, 0x71, + 0x75, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x0B, + 0x00, 0x00, 0x00, 0x45, 0x71, 0x75, 0x61, 0x74, + 0x69, 0x6F, 0x6E, 0x2E, 0x33, 0x00, 0xF4, 0x39, + 0xB2, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + tools::SvRef<SotStorageStream> xStor( pStor->OpenSotStream("\1CompObj")); + xStor->WriteBytes(aCompObj, sizeof(aCompObj)); + + static sal_uInt8 const aOle[] = { + 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + tools::SvRef<SotStorageStream> xStor2( pStor->OpenSotStream("\1Ole")); + xStor2->WriteBytes(aOle, sizeof(aOle)); + xStor.clear(); + xStor2.clear(); + + tools::SvRef<SotStorageStream> xSrc = pStor->OpenSotStream("Equation Native"); + if ( (!xSrc.is()) || (ERRCODE_NONE != xSrc->GetError())) + return false; + + pS = xSrc.get(); + pS->SetEndian( SvStreamEndian::LITTLE ); + + pS->SeekRel(EQNOLEFILEHDR_SIZE); //Skip 28byte Header and fill it in later + pS->WriteUChar( 0x03 ); + pS->WriteUChar( 0x01 ); + pS->WriteUChar( 0x01 ); + pS->WriteUChar( 0x03 ); + pS->WriteUChar( 0x00 ); + sal_uInt32 nSize = pS->Tell(); + nPendingAttributes=0; + + HandleNodes(pTree, 0); + pS->WriteUChar( END ); + + nSize = pS->Tell()-nSize; + pS->Seek(0); + EQNOLEFILEHDR aHdr(nSize+4+1); + aHdr.Write(pS); + + pStor->Commit(); + } + + return true; +} + + +void MathType::HandleNodes(SmNode *pNode,int nLevel) +{ + switch(pNode->GetType()) + { + case SmNodeType::Attribut: + HandleAttributes(pNode,nLevel); + break; + case SmNodeType::Text: + HandleText(pNode); + break; + case SmNodeType::VerticalBrace: + HandleVerticalBrace(pNode,nLevel); + break; + case SmNodeType::Brace: + HandleBrace(pNode,nLevel); + break; + case SmNodeType::Oper: + HandleOperator(pNode,nLevel); + break; + case SmNodeType::BinVer: + HandleFractions(pNode,nLevel); + break; + case SmNodeType::Root: + HandleRoot(pNode,nLevel); + break; + case SmNodeType::Special: + { + SmTextNode *pText = static_cast<SmTextNode *>(pNode); + //if the token str and the result text are the same then this + //is to be seen as text, else assume it's a mathchar + if (pText->GetText() == pText->GetToken().aText) + HandleText(pText); + else + HandleMath(pText); + } + break; + case SmNodeType::Math: + case SmNodeType::MathIdent: + HandleMath(pNode); + break; + case SmNodeType::SubSup: + HandleSubSupScript(pNode,nLevel); + break; + case SmNodeType::Expression: + { + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + HandleNodes(pTemp,nLevel+1); + } + break; + } + case SmNodeType::Table: + //Root Node, PILE equivalent, i.e. vertical stack + HandleTable(pNode,nLevel); + break; + case SmNodeType::Matrix: + HandleSmMatrix(static_cast<SmMatrixNode *>(pNode),nLevel); + break; + case SmNodeType::Line: + { + pS->WriteUChar( 0x0a ); + pS->WriteUChar( LINE ); + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + HandleNodes(pTemp,nLevel+1); + } + pS->WriteUChar( END ); + break; + } + case SmNodeType::Align: + HandleMAlign(pNode,nLevel); + break; + case SmNodeType::Blank: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x98 ); + if (pNode->GetToken().eType == TSBLANK) + pS->WriteUInt16( 0xEB04 ); + else + pS->WriteUInt16( 0xEB05 ); + break; + default: + { + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + HandleNodes(pTemp,nLevel+1); + } + break; + } + } +} + + +int MathType::StartTemplate(sal_uInt16 nSelector,sal_uInt16 nVariation) +{ + int nOldPending=nPendingAttributes; + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( nSelector ); //selector + pS->WriteUChar( nVariation ); //variation + pS->WriteUChar( 0x00 ); //options + pS->WriteUChar( LINE ); + //there's just no way we can now handle any character + //attributes (from mathtypes perspective) centered + //over an expression but above template attribute + //such as widevec and similar constructs + //we have to drop them + nPendingAttributes=0; + return nOldPending; +} + +void MathType::EndTemplate(int nOldPendingAttributes) +{ + pS->WriteUChar( END ); //end line + pS->WriteUChar( END ); //end template + nPendingAttributes=nOldPendingAttributes; +} + + +void MathType::HandleSmMatrix(SmMatrixNode *pMatrix,int nLevel) +{ + pS->WriteUChar( MATRIX ); + pS->WriteUChar( 0x00 ); //vAlign ? + pS->WriteUChar( 0x00 ); //h_just + pS->WriteUChar( 0x00 ); //v_just + pS->WriteUChar( pMatrix->GetNumRows() ); //v_just + pS->WriteUChar( pMatrix->GetNumCols() ); //v_just + int nBytes=(pMatrix->GetNumRows()+1)*2/8; + if (((pMatrix->GetNumRows()+1)*2)%8) + nBytes++; + for (int j = 0; j < nBytes; j++) + pS->WriteUChar( 0x00 ); //row_parts + nBytes=(pMatrix->GetNumCols()+1)*2/8; + if (((pMatrix->GetNumCols()+1)*2)%8) + nBytes++; + for (int k = 0; k < nBytes; k++) + pS->WriteUChar( 0x00 ); //col_parts + size_t nSize = pMatrix->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pMatrix->GetSubNode(i)) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //end line + } + } + pS->WriteUChar( END ); +} + + +//Root Node, PILE equivalent, i.e. vertical stack +void MathType::HandleTable(SmNode *pNode,int nLevel) +{ + size_t nSize = pNode->GetNumSubNodes(); + //The root of the starmath is a table, if + //we convert this them each iteration of + //conversion from starmath to mathtype will + //add an extra unnecessary level to the + //mathtype output stack which would grow + //without bound in a multi step conversion + + if (nLevel == 0) + pS->WriteUChar( 0x0A ); //initial size + + if ( nLevel || (nSize >1)) + { + pS->WriteUChar( PILE ); + pS->WriteUChar( nHAlign ); //vAlign ? + pS->WriteUChar( 0x01 ); //hAlign + } + + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + { + pS->WriteUChar( LINE ); + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + } + } + if (nLevel || (nSize>1)) + pS->WriteUChar( END ); +} + + +void MathType::HandleRoot(SmNode *pNode,int nLevel) +{ + SmNode *pTemp; + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x0D ); //selector + if (pNode->GetSubNode(0)) + pS->WriteUChar( 0x01 ); //variation + else + pS->WriteUChar( 0x00 ); //variation + pS->WriteUChar( 0x00 ); //options + + if (nullptr != (pTemp = pNode->GetSubNode(2))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + } + + if (nullptr != (pTemp = pNode->GetSubNode(0))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + } + else + pS->WriteUChar( LINE|0x10 ); //dummy line + + + pS->WriteUChar( END ); +} + +sal_uInt8 MathType::HandleCScript(SmNode *pNode,SmNode *pContent,int nLevel, + sal_uInt64 *pPos,bool bTest) +{ + sal_uInt8 nVariation2=0xff; + + if (bTest && pNode->GetSubNode(CSUP+1)) + { + nVariation2=0; + if (pNode->GetSubNode(CSUB+1)) + nVariation2=2; + } + else if (pNode->GetSubNode(CSUB+1)) + nVariation2=1; + + if (nVariation2!=0xff) + { + if (pPos) + *pPos = pS->Tell(); + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x2B ); //selector + pS->WriteUChar( nVariation2 ); + pS->WriteUChar( 0x00 ); //options + + if (pContent) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pContent,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + + pS->WriteUChar( 0x0B ); + + SmNode *pTemp; + if (nullptr != (pTemp = pNode->GetSubNode(CSUB+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + if (bTest && nullptr != (pTemp = pNode->GetSubNode(CSUP+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + } + return nVariation2; +} + + +/* + Sub and Sup scripts and another problem area, StarMath + can have all possible options used at the same time, whereas + Mathtype cannot. The ordering of the nodes for each system + is quite different as well leading to some complexity + */ +void MathType::HandleSubSupScript(SmNode *pNode,int nLevel) +{ + sal_uInt8 nVariation=0xff; + if (pNode->GetSubNode(LSUP+1)) + { + nVariation=0; + if (pNode->GetSubNode(LSUB+1)) + nVariation=2; + } + else if ( nullptr != pNode->GetSubNode(LSUB+1) ) + nVariation=1; + + SmNode *pTemp; + if (nVariation!=0xff) + { + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x2c ); //selector + pS->WriteUChar( nVariation ); + pS->WriteUChar( 0x00 ); //options + pS->WriteUChar( 0x0B ); + + if (nullptr != (pTemp = pNode->GetSubNode(LSUB+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + if (nullptr != (pTemp = pNode->GetSubNode(LSUP+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + pS->WriteUChar( END ); + nVariation=0xff; + } + + + sal_uInt8 nVariation2=HandleCScript(pNode,nullptr,nLevel); + + if (nullptr != (pTemp = pNode->GetSubNode(0))) + { + HandleNodes(pTemp,nLevel+1); + } + + if (nVariation2 != 0xff) + pS->WriteUChar( END ); + + if (nullptr != (pNode->GetSubNode(RSUP+1))) + { + nVariation=0; + if (pNode->GetSubNode(RSUB+1)) + nVariation=2; + } + else if (nullptr != pNode->GetSubNode(RSUB+1)) + nVariation=1; + + if (nVariation!=0xff) + { + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x0F ); //selector + pS->WriteUChar( nVariation ); + pS->WriteUChar( 0x00 ); //options + pS->WriteUChar( 0x0B ); + + if (nullptr != (pTemp = pNode->GetSubNode(RSUB+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + if (nullptr != (pTemp = pNode->GetSubNode(RSUP+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + pS->WriteUChar( END ); //line + } + + //After subscript mathtype will keep the size of + //normal text at the subscript size, sigh. + pS->WriteUChar( 0x0A ); +} + + +void MathType::HandleFractions(SmNode *pNode,int nLevel) +{ + SmNode *pTemp; + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x0E ); //selector + pS->WriteUChar( 0x00 ); //variation + pS->WriteUChar( 0x00 ); //options + + pS->WriteUChar( 0x0A ); + pS->WriteUChar( LINE ); //line + if (nullptr != (pTemp = pNode->GetSubNode(0))) + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + + pS->WriteUChar( 0x0A ); + pS->WriteUChar( LINE ); //line + if (nullptr != (pTemp = pNode->GetSubNode(2))) + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + + pS->WriteUChar( END ); +} + + +void MathType::HandleBrace(SmNode *pNode,int nLevel) +{ + SmNode *pTemp; + SmNode *pLeft=pNode->GetSubNode(0); + SmNode *pRight=pNode->GetSubNode(2); + + pS->WriteUChar( TMPL ); //Template + bIsReInterpBrace=false; + sal_uInt8 nBSpec=0x10; + auto nLoc = pS->Tell(); + if (pLeft) + { + switch (pLeft->GetToken().eType) + { + case TLANGLE: + pS->WriteUChar( tmANGLE ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + break; + case TLBRACE: + pS->WriteUChar( tmBRACE ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + nBSpec+=3; + break; + case TLBRACKET: + pS->WriteUChar( tmBRACK ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + nBSpec+=3; + break; + case TLFLOOR: + pS->WriteUChar( tmFLOOR ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + break; + case TLLINE: + pS->WriteUChar( tmBAR ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + nBSpec+=3; + break; + case TLDLINE: + pS->WriteUChar( tmDBAR ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + break; + default: + pS->WriteUChar( tmPAREN ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + nBSpec+=3; + break; + } + } + + if (nullptr != (pTemp = pNode->GetSubNode(1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //options + } + nSpec=nBSpec; + if (pLeft) + HandleNodes(pLeft,nLevel+1); + if (bIsReInterpBrace) + { + auto nLoc2 = pS->Tell(); + pS->Seek(nLoc); + pS->WriteUChar( 0x2D ); + pS->Seek(nLoc2); + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x96 ); + pS->WriteUInt16( 0xEC07 ); + bIsReInterpBrace=false; + } + if (pRight) + HandleNodes(pRight,nLevel+1); + nSpec=0x0; + pS->WriteUChar( END ); +} + + +void MathType::HandleVerticalBrace(SmNode *pNode,int nLevel) +{ + SmNode *pTemp; + pS->WriteUChar( TMPL ); //Template + if (pNode->GetToken().eType == TUNDERBRACE) + pS->WriteUChar( tmLHBRACE ); //selector + else + pS->WriteUChar( tmUHBRACE ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + + if (nullptr != (pTemp = pNode->GetSubNode(0))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //options + } + + if (nullptr != (pTemp = pNode->GetSubNode(2))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //options + } + pS->WriteUChar( END ); +} + +void MathType::HandleOperator(SmNode *pNode,int nLevel) +{ + if (HandleLim(pNode,nLevel)) + return; + + sal_uInt64 nPos; + sal_uInt8 nVariation; + + switch (pNode->GetToken().eType) + { + case TIINT: + case TIIINT: + case TLINT: + case TLLINT: + case TLLLINT: + nVariation=HandleCScript(pNode->GetSubNode(0), + pNode->GetSubNode(1),nLevel,&nPos,false); + break; + default: + nVariation=HandleCScript(pNode->GetSubNode(0), + pNode->GetSubNode(1),nLevel,&nPos); + break; + } + + sal_uInt8 nOldVariation=nVariation; + sal_uInt8 nIntVariation=nVariation; + + sal_uInt64 nPos2=0; + if (nVariation != 0xff) + { + nPos2 = pS->Tell(); + pS->Seek(nPos); + if (nVariation == 2) + { + nIntVariation=0; + nVariation = 1; + } + else if (nVariation == 0) + nVariation = 1; + else if (nVariation == 1) + nVariation = 0; + } + else + { + nVariation = 2; + nIntVariation=0; + } + pS->WriteUChar( TMPL ); + switch(pNode->GetToken().eType) + { + case TINT: + case TINTD: + if (nOldVariation != 0xff) + pS->WriteUChar( 0x18 ); //selector + else + pS->WriteUChar( 0x15 ); //selector + pS->WriteUChar( nIntVariation ); //variation + break; + case TIINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x19 ); + pS->WriteUChar( 0x01 ); + } + else + { + pS->WriteUChar( 0x16 ); + pS->WriteUChar( 0x00 ); + } + break; + case TIIINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x1a ); + pS->WriteUChar( 0x01 ); + } + else + { + pS->WriteUChar( 0x17 ); + pS->WriteUChar( 0x00 ); + } + break; + case TLINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x18 ); + pS->WriteUChar( 0x02 ); + } + else + { + pS->WriteUChar( 0x15 ); + pS->WriteUChar( 0x03 ); + } + break; + case TLLINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x19 ); + pS->WriteUChar( 0x00 ); + } + else + { + pS->WriteUChar( 0x16 ); + pS->WriteUChar( 0x02 ); + } + break; + case TLLLINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x1a ); + pS->WriteUChar( 0x00 ); + } + else + { + pS->WriteUChar( 0x17 ); + pS->WriteUChar( 0x02 ); + } + break; + case TSUM: + default: + pS->WriteUChar( 0x1d ); + pS->WriteUChar( nVariation ); + break; + case TPROD: + pS->WriteUChar( 0x1f ); + pS->WriteUChar( nVariation ); + break; + case TCOPROD: + pS->WriteUChar( 0x21 ); + pS->WriteUChar( nVariation ); + break; + } + pS->WriteUChar( 0 ); //options + + if (nPos2) + pS->Seek(nPos2); + else + { + pS->WriteUChar( LINE ); //line + HandleNodes(pNode->GetSubNode(1),nLevel+1); + pS->WriteUChar( END ); //line + pS->WriteUChar( LINE|0x10 ); + pS->WriteUChar( LINE|0x10 ); + } + + pS->WriteUChar( 0x0D ); + switch(pNode->GetToken().eType) + { + case TSUM: + default: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x2211 ); + break; + case TPROD: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x220F ); + break; + case TCOPROD: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x8B ); + pS->WriteUInt16( 0x2210 ); + break; + case TIIINT: + case TLLLINT: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x222B ); + [[fallthrough]]; + case TIINT: + case TLLINT: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x222B ); + [[fallthrough]]; + case TINT: + case TINTD: + case TLINT: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x222B ); + break; + } + pS->WriteUChar( END ); + pS->WriteUChar( 0x0A ); +} + + +bool MathType::HandlePile(int &rSetAlign, int nLevel, sal_uInt8 nSelector, sal_uInt8 nVariation) +{ + sal_uInt8 nVAlign; + pS->ReadUChar( nHAlign ); + pS->ReadUChar( nVAlign ); + + HandleAlign(nHAlign, rSetAlign); + + rRet.append(" stack {\n"); + bool bRet = HandleRecords( nLevel+1, nSelector, nVariation, -1, -1 ); + int nRemoveFrom = rRet.getLength() >= 3 ? rRet.getLength() - 3 : 0; + rRet.remove(nRemoveFrom, 2); + rRet.append("} "); + + while (rSetAlign) + { + rRet.append("} "); + rSetAlign--; + } + return bRet; +} + +bool MathType::HandleMatrix(int nLevel, sal_uInt8 nSelector, sal_uInt8 nVariation) +{ + sal_uInt8 nH_just,nV_just,nRows,nCols,nVAlign; + pS->ReadUChar( nVAlign ); + pS->ReadUChar( nH_just ); + pS->ReadUChar( nV_just ); + pS->ReadUChar( nRows ); + pS->ReadUChar( nCols ); + int nBytes = ((nRows+1)*2)/8; + if (((nRows+1)*2)%8) + nBytes++; + pS->SeekRel(nBytes); + nBytes = ((nCols+1)*2)/8; + if (((nCols+1)*2)%8) + nBytes++; + pS->SeekRel(nBytes); + rRet.append(" matrix {\n"); + bool bRet = HandleRecords( nLevel+1, nSelector, nVariation, nRows, nCols ); + + sal_Int32 nI = rRet.lastIndexOf('#'); + if (nI > 0) + if (rRet[nI-1] != '#') //missing column + rRet.append("{}"); + + rRet.append("\n} "); + return bRet; +} + +bool MathType::HandleTemplate(int nLevel, sal_uInt8 &rSelector, + sal_uInt8 &rVariation, sal_Int32 &rLastTemplateBracket) +{ + sal_uInt8 nOption; //This appears utterly unused + pS->ReadUChar( rSelector ); + pS->ReadUChar( rVariation ); + pS->ReadUChar( nOption ); + OSL_ENSURE(rSelector < 48,"Selector out of range"); + if ((rSelector >= 21) && (rSelector <=26)) + { + OSL_ENSURE(nOption < 2,"Option out of range"); + } + else if (rSelector <= 12) + { + OSL_ENSURE(nOption < 3,"Option out of range"); + } + + //For the (broken) case where one subscript template ends, and there is + //another one after it, mathtype handles it as if the second one was + //inside the first one and renders it as sub of sub + bool bRemove=false; + if ( (rSelector == 0xf) && (rLastTemplateBracket != -1) ) + { + bRemove=true; + for (sal_Int32 nI = rLastTemplateBracket+1; nI < rRet.getLength(); nI++ ) + if (rRet[nI] != ' ') + { + bRemove=false; + break; + } + } + + //suborderlist + bool bRet = HandleRecords( nLevel+1, rSelector, rVariation ); + + if (bRemove) + { + if (rLastTemplateBracket < rRet.getLength()) + rRet.remove(rLastTemplateBracket, 1); + rRet.append("} "); + rLastTemplateBracket = -1; + } + if (rSelector == 0xf) + rLastTemplateBracket = rRet.lastIndexOf('}'); + else + rLastTemplateBracket = -1; + + rSelector = sal::static_int_cast< sal_uInt8 >(-1); + return bRet; +} + +void MathType::HandleEmblishments() +{ + sal_uInt8 nEmbel; + do + { + pS->ReadUChar( nEmbel ); + if (!pS->good()) + break; + switch (nEmbel) + { + case 0x02: + rRet.append(" dot "); + break; + case 0x03: + rRet.append(" ddot "); + break; + case 0x04: + rRet.append(" dddot "); + break; + case 0x05: + if (!nPostSup) + { + sPost.append(" sup {}"); + nPostSup = sPost.getLength(); + } + sPost.insert(nPostSup-1," ' "); + nPostSup += 3; + break; + case 0x06: + if (!nPostSup) + { + sPost.append(" sup {}"); + nPostSup = sPost.getLength(); + } + sPost.insert(nPostSup-1," '' "); + nPostSup += 4; + break; + case 0x07: + if (!nPostlSup) + { + sPost.append(" lsup {}"); + nPostlSup = sPost.getLength(); + } + sPost.insert(nPostlSup-1," ' "); + nPostlSup += 3; + break; + case 0x08: + rRet.append(" tilde "); + break; + case 0x09: + rRet.append(" hat "); + break; + case 0x0b: + rRet.append(" vec "); + break; + case 0x10: + rRet.append(" overstrike "); + break; + case 0x11: + rRet.append(" bar "); + break; + case 0x12: + if (!nPostSup) + { + sPost.append(" sup {}"); + nPostSup = sPost.getLength(); + } + sPost.insert(nPostSup-1," ''' "); + nPostSup += 5; + break; + case 0x14: + rRet.append(" breve "); + break; + default: + OSL_ENSURE(nEmbel < 21,"Embel out of range"); + break; + } + if (nVersion < 3) + break; + }while (nEmbel); +} + +void MathType::HandleSetSize() +{ + sal_uInt8 nTemp; + pS->ReadUChar( nTemp ); + switch (nTemp) + { + case 101: + pS->ReadInt16( nLSize ); + nLSize = -nLSize; + break; + case 100: + pS->ReadUChar( nTemp ); + nLSize = nTemp; + pS->ReadInt16( nDSize ); + break; + default: + nLSize = nTemp; + pS->ReadUChar( nTemp ); + nDSize = nTemp-128; + break; + } +} + +bool MathType::HandleChar(sal_Int32 &rTextStart, int &rSetSize, int nLevel, + sal_uInt8 nTag, sal_uInt8 nSelector, sal_uInt8 nVariation, bool bSilent) +{ + sal_Unicode nChar(0); + bool bRet = true; + + if (xfAUTO(nTag)) + { + //This is a candidate for function recognition, whatever + //that is! + } + + sal_uInt8 nOldTypeFace = nTypeFace; + pS->ReadUChar( nTypeFace ); + if (nVersion < 3) + { + sal_uInt8 nChar8(0); + pS->ReadUChar( nChar8 ); + nChar = nChar8; + } + else + pS->ReadUtf16( nChar ); + + /* + bad character, old mathtype < 3 has these + */ + if (nChar < 0x20) + return bRet; + + if (xfEMBELL(nTag)) + { + //A bit tricky, the character emblishments for + //mathtype can all be listed after each other, in + //starmath some must go before the character and some + //must go after. In addition some of the emblishments + //may repeated and in starmath some of these groups + //must be gathered together. sPost is the portion that + //follows the char and nPostSup and nPostlSup are the + //indexes at which this class of emblishment is + //collated together + sPost = ""; + nPostSup = nPostlSup = 0; + int nOriglen=rRet.getLength()-rTextStart; + rRet.append(" {"); // #i24340# make what would be "vec {A}_n" become "{vec {A}}_n" + if ((!bSilent) && (nOriglen > 1)) + rRet.append("\""); + bRet = HandleRecords( nLevel+1, nSelector, nVariation ); + if (!bSilent) + { + if (nOriglen > 1) + { + OUString aStr; + TypeFaceToString(aStr,nOldTypeFace); + aStr += "\""; + rRet.insert(std::min(rTextStart, rRet.getLength()), aStr); + + aStr.clear(); + TypeFaceToString(aStr,nTypeFace); + rRet.append(aStr).append("{"); + } + else + rRet.append(" {"); + rTextStart = rRet.getLength(); + } + } + + if (!bSilent) + { + sal_Int32 nOldLen = rRet.getLength(); + if ( + HandleSize(nLSize,nDSize,rSetSize) || + (nOldTypeFace != nTypeFace) + ) + { + if ((nOldLen - rTextStart) > 1) + { + rRet.insert(nOldLen, "\""); + OUString aStr; + TypeFaceToString(aStr,nOldTypeFace); + aStr += "\""; + rRet.insert(rTextStart,aStr); + } + rTextStart = rRet.getLength(); + } + nOldLen = rRet.getLength(); + if (!LookupChar(nChar,rRet,nVersion,nTypeFace)) + { + if (nOldLen - rTextStart > 1) + { + rRet.insert(nOldLen, "\""); + OUString aStr; + TypeFaceToString(aStr,nOldTypeFace); + aStr += "\""; + rRet.insert(rTextStart, aStr); + } + rTextStart = rRet.getLength(); + } + lcl_PrependDummyTerm(rRet, rTextStart); + } + + if ((xfEMBELL(nTag)) && (!bSilent)) + { + rRet.append("}}").append(sPost); // #i24340# make what would be "vec {A}_n" become "{vec {A}}_n" + rTextStart = rRet.getLength(); + } + return bRet; +} + +bool MathType::HandleLim(SmNode *pNode,int nLevel) +{ + bool bRet=false; + //Special case for the "lim" option in StarMath + if ((pNode->GetToken().eType == TLIM) + || (pNode->GetToken().eType == TLIMSUP) + || (pNode->GetToken().eType == TLIMINF) + ) + { + if (pNode->GetSubNode(1)) + { + sal_uInt8 nVariation2=HandleCScript(pNode->GetSubNode(0),nullptr, + nLevel); + + pS->WriteUChar( 0x0A ); + pS->WriteUChar( LINE ); //line + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'l' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'i' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'm' ); + + if (pNode->GetToken().eType == TLIMSUP) + { + pS->WriteUChar( CHAR ); //some space + pS->WriteUChar( 0x98 ); + pS->WriteUInt16( 0xEB04 ); + + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 's' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'u' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'p' ); + } + else if (pNode->GetToken().eType == TLIMINF) + { + pS->WriteUChar( CHAR ); //some space + pS->WriteUChar( 0x98 ); + pS->WriteUInt16( 0xEB04 ); + + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'i' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'n' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'f' ); + } + + + pS->WriteUChar( CHAR ); //some space + pS->WriteUChar( 0x98 ); + pS->WriteUInt16( 0xEB04 ); + + if (nVariation2 != 0xff) + { + pS->WriteUChar( END ); + pS->WriteUChar( END ); + } + HandleNodes(pNode->GetSubNode(1),nLevel+1); + bRet = true; + } + } + return bRet; +} + +void MathType::HandleMAlign(SmNode *pNode,int nLevel) +{ + sal_uInt8 nPushedHAlign=nHAlign; + switch(pNode->GetToken().eType) + { + case TALIGNC: + nHAlign=2; + break; + case TALIGNR: + nHAlign=3; + break; + default: + nHAlign=1; + break; + } + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + HandleNodes(pTemp,nLevel+1); + } + nHAlign=nPushedHAlign; +} + +void MathType::HandleMath(SmNode *pNode) +{ + if (pNode->GetToken().eType == TMLINE) + { + pS->WriteUChar( END ); + pS->WriteUChar( LINE ); + bIsReInterpBrace=true; + return; + } + SmMathSymbolNode *pTemp = static_cast<SmMathSymbolNode *>(pNode); + for(sal_Int32 i=0;i<pTemp->GetText().getLength();i++) + { + sal_Unicode nArse = SmTextNode::ConvertSymbolToUnicode(pTemp->GetText()[i]); + if ((nArse == 0x2224) || (nArse == 0x2288) || (nArse == 0x2285) || + (nArse == 0x2289)) + { + pS->WriteUChar( CHAR|0x20 ); + } + else if (nPendingAttributes && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + pS->WriteUChar( 0x22 ); + } + else + pS->WriteUChar( CHAR ); //char without formula recognition + //The typeface seems to be MTEXTRA for unicode characters, + //though how to determine when mathtype chooses one over + //the other is unknown. This should do the trick + //nevertheless. + sal_uInt8 nBias; + if ( (nArse == 0x2213) || (nArse == 0x2218) || + (nArse == 0x210F) || ( + (nArse >= 0x22EE) && (nArse <= 0x22FF) + )) + { + nBias = 0xB; //typeface + } + else if ((nArse == 0x2F) || (nArse == 0x2225)) + nBias = 0x2; //typeface + else if ((nArse > 0x2000) || (nArse == 0x00D7)) + nBias = 0x6; //typeface + else if (nArse == 0x3d1) + nBias = 0x4; + else if ((nArse > 0xFF) && ((nArse < 0x393) || (nArse > 0x3c9))) + nBias = 0xB; //typeface + else + nBias = 0x3; //typeface + + pS->WriteUChar( nSpec+nBias+128 ); //typeface + + if (nArse == 0x2224) + { + pS->WriteUInt16( 0x7C ); + pS->WriteUChar( EMBEL ); + pS->WriteUChar( 0x0A ); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + else if (nArse == 0x2225) + pS->WriteUInt16( 0xEC09 ); + else if (nArse == 0xE421) + pS->WriteUInt16( 0x2265 ); + else if (nArse == 0x230A) + pS->WriteUInt16( 0xF8F0 ); + else if (nArse == 0x230B) + pS->WriteUInt16( 0xF8FB ); + else if (nArse == 0xE425) + pS->WriteUInt16( 0x2264 ); + else if (nArse == 0x226A) + { + pS->WriteUInt16( 0x3C ); + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x98 ); + pS->WriteUInt16( 0xEB01 ); + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x3c ); + } + else if (nArse == 0x2288) + { + pS->WriteUInt16( 0x2286 ); + pS->WriteUChar( EMBEL ); + pS->WriteUChar( 0x0A ); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + else if (nArse == 0x2289) + { + pS->WriteUInt16( 0x2287 ); + pS->WriteUChar( EMBEL ); + pS->WriteUChar( 0x0A ); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + else if (nArse == 0x2285) + { + pS->WriteUInt16( 0x2283 ); + pS->WriteUChar( EMBEL ); + pS->WriteUChar( 0x0A ); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + else + pS->WriteUInt16( nArse ); + } + nPendingAttributes = 0; +} + +void MathType::HandleAttributes(SmNode *pNode,int nLevel) +{ + int nOldPending = 0; + SmNode *pTemp = nullptr; + SmTextNode *pIsText = nullptr; + + if (nullptr != (pTemp = pNode->GetSubNode(0))) + { + pIsText = static_cast<SmTextNode *>(pNode->GetSubNode(1)); + + switch (pTemp->GetToken().eType) + { + case TWIDEVEC: + //there's just no way we can now handle any character + //attributes (from mathtypes perspective) centered + //over an expression but above template attributes + //such as widevec and similar constructs + //we have to drop them + nOldPending = StartTemplate(0x2f,0x01); + break; + case TCHECK: //Not Exportable + case TACUTE: //Not Exportable + case TGRAVE: //Not Exportable + case TCIRCLE: //Not Exportable + case TWIDEHARPOON: //Not Exportable + case TWIDETILDE: //Not Exportable + case TWIDEHAT: //Not Exportable + break; + case TUNDERLINE: + nOldPending = StartTemplate(0x10); + break; + case TOVERLINE: //If the next node is not text + //or text with more than one char + if ((pIsText->GetToken().eType != TTEXT) || + (pIsText->GetText().getLength() > 1)) + nOldPending = StartTemplate(0x11); + break; + default: + nPendingAttributes++; + break; + } + } + + if (pIsText) + HandleNodes(pIsText,nLevel+1); + + switch (pTemp->GetToken().eType) + { + case TWIDEVEC: + case TUNDERLINE: + EndTemplate(nOldPending); + break; + case TOVERLINE: + if ((pIsText->GetToken().eType != TTEXT) || + (pIsText->GetText().getLength() > 1)) + EndTemplate(nOldPending); + break; + default: + break; + } + + //if there was no suitable place to put the attribute, + //then we have to just give up on it + if (nPendingAttributes) + nPendingAttributes--; + else + { + if ((nInsertion != 0) && nullptr != (pTemp = pNode->GetSubNode(0))) + { + auto nPos = pS->Tell(); + nInsertion--; + pS->Seek(nInsertion); + switch(pTemp->GetToken().eType) + { + case TACUTE: //Not Exportable + case TGRAVE: //Not Exportable + case TCIRCLE: //Not Exportable + break; + case TCDOT: + pS->WriteUChar( 2 ); + break; + case TDDOT: + pS->WriteUChar( 3 ); + break; + case TDDDOT: + pS->WriteUChar( 4 ); + break; + case TTILDE: + pS->WriteUChar( 8 ); + break; + case THAT: + pS->WriteUChar( 9 ); + break; + case TVEC: + pS->WriteUChar( 11 ); + break; + case TOVERSTRIKE: + pS->WriteUChar( 16 ); + break; + case TOVERLINE: + if ((pIsText->GetToken().eType == TTEXT) && + (pIsText->GetText().getLength() == 1)) + pS->WriteUChar( 17 ); + break; + case TBREVE: + pS->WriteUChar( 20 ); + break; + case TWIDEVEC: + case TWIDEHARPOON: + case TUNDERLINE: + case TWIDETILDE: + case TWIDEHAT: + break; + case TBAR: + pS->WriteUChar( 17 ); + break; + default: + pS->WriteUChar( 2 ); + break; + } + pS->Seek(nPos); + } + } +} + +void MathType::HandleText(SmNode *pNode) +{ + SmTextNode *pTemp = static_cast<SmTextNode *>(pNode); + for(sal_Int32 i=0;i<pTemp->GetText().getLength();i++) + { + if (nPendingAttributes && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + pS->WriteUChar( 0x22 ); //char, with attributes right + //after the character + } + else + pS->WriteUChar( CHAR ); + + sal_uInt8 nFace = 0x1; + if (pNode->GetFont().GetItalic() == ITALIC_NORMAL) + nFace = 0x3; + else if (pNode->GetFont().GetWeight() == WEIGHT_BOLD) + nFace = 0x7; + pS->WriteUChar( nFace+128 ); //typeface + sal_uInt16 nChar = pTemp->GetText()[i]; + pS->WriteUInt16( SmTextNode::ConvertSymbolToUnicode(nChar) ); + + //Mathtype can only have these sort of character + //attributes on a single character, starmath can put them + //anywhere, when the entity involved is a text run this is + //a large effort to place the character attribute on the + //central mathtype character so that it does pretty much + //what the user probably has in mind. The attributes + //filled in here are dummy ones which are replaced in the + //ATTRIBUT handler if a suitable location for the + //attributes was found here. Unfortunately it is + //possible for starmath to place character attributes on + //entities which cannot occur in mathtype e.g. a Summation + //symbol so these attributes may be lost + if (nPendingAttributes && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + pS->WriteUChar( EMBEL ); + while (nPendingAttributes) + { + pS->WriteUChar( 2 ); + //wedge the attributes in here and clear + //the pending stack + nPendingAttributes--; + } + nInsertion=pS->Tell(); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportMathType(SvStream &rStream) +{ + OUStringBuffer sText; + MathType aEquation(sText); + bool bRet = false; + try + { + bRet = aEquation.Parse(&rStream); + } + catch (const std::out_of_range&) + { + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathtype.hxx b/starmath/source/mathtype.hxx new file mode 100644 index 000000000..1e3a744d7 --- /dev/null +++ b/starmath/source/mathtype.hxx @@ -0,0 +1,193 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_MATHTYPE_HXX +#define INCLUDED_STARMATH_SOURCE_MATHTYPE_HXX + +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> + +#include <set> +#include <vector> + +class SfxMedium; +class SmMatrixNode; +class SmNode; +class SotStorage; +class SvStream; + +class MathTypeFont +{ +public: + sal_uInt8 nTface; + sal_uInt8 nStyle; + MathTypeFont() : nTface(0),nStyle(0) {} + explicit MathTypeFont(sal_uInt8 nFace) : nTface(nFace),nStyle(0) {} + void AppendStyleToText(OUString &rS); +}; + +struct LessMathTypeFont +{ + bool operator() (const MathTypeFont &rValue1, + const MathTypeFont &rValue2) const + { + return rValue1.nTface < rValue2.nTface; + } +}; + +typedef ::std::set< MathTypeFont, LessMathTypeFont > MathTypeFontSet; + +class MathType +{ +public: + explicit MathType(OUStringBuffer &rIn) + : nVersion(0) + , pS(nullptr) + , rRet(rIn) + , pTree(nullptr) + , nHAlign(0) + , nPendingAttributes(0) + , nInsertion(0) + , nLSize(0) + , nDSize(0) + , nCurSize(0) + , nLastSize(0) + , nSpec(0) + , bIsReInterpBrace(false) + , nPostSup(0) + , nPostlSup(0) + , nTypeFace(0) + { + Init(); + } + + MathType(OUStringBuffer &rIn,SmNode *pIn) + : nVersion(0) + , pS(nullptr) + , rRet(rIn) + , pTree(pIn) + , nHAlign(2) + , nPendingAttributes(0) + , nInsertion(0) + , nLSize(0) + , nDSize(0) + , nCurSize(0) + , nLastSize(0) + , nSpec(0) + , bIsReInterpBrace(false) + , nPostSup(0) + , nPostlSup(0) + , nTypeFace(0) + { + Init(); + } + + bool Parse(SotStorage* pStor); + bool Parse(SvStream* pStream); + bool ConvertFromStarMath( SfxMedium& rMedium ); + +private: +/*Ver 2 Header*/ + sal_uInt8 nVersion; + + SvStream* pS; + + void Init(); + + bool HandleRecords(int nLevel, sal_uInt8 nSelector =0xFF, + sal_uInt8 nVariation =0xFF, int nRows =0, int nCols =0); + bool HandleSize(sal_Int16 nLSize, sal_Int16 nDSize, int &rSetSize); + void HandleAlign(sal_uInt8 nHAlign, int &rSetAlign); + bool HandlePile(int &rSetAlign, int nLevel, sal_uInt8 nSelector, sal_uInt8 nVariation); + bool HandleMatrix(int nLevel, sal_uInt8 nSelector, sal_uInt8 nVariarion); + void HandleMatrixSeparator(int nMatrixRows, int nMatrixCols, int &rCurCol, int &rCurRow); + bool HandleTemplate(int nLevel, sal_uInt8 &rSelector, sal_uInt8 &rVariation, + sal_Int32 &rLastTemplateBracket); + void HandleEmblishments(); + void HandleSetSize(); + bool HandleChar(sal_Int32 &rTextStart, int &rSetSize, int nLevel, + sal_uInt8 nTag, sal_uInt8 nSelector, sal_uInt8 nVariation, bool bSilent); + void HandleNudge(); + + static int xfLMOVE(sal_uInt8 nTest) {return nTest&0x80;} + static int xfAUTO(sal_uInt8 nTest) {return nTest&0x10;} + static int xfEMBELL(sal_uInt8 nTest) {return nTest&0x20;} + static int xfNULL(sal_uInt8 nTest) {return nTest&0x10;} + + void HandleNodes(SmNode *pNode,int nLevel); + int StartTemplate(sal_uInt16 nSelector,sal_uInt16 nVariation=0); + void EndTemplate(int nOldPendingAttributes); + void HandleSmMatrix(SmMatrixNode *pMatrix,int nLevel); + void HandleTable(SmNode *pNode,int nLevel); + void HandleRoot(SmNode *pNode,int nLevel); + void HandleSubSupScript(SmNode *pNode,int nLevel); + sal_uInt8 HandleCScript(SmNode *pNode,SmNode *pContent,int nLevel, + sal_uInt64 *pPos=nullptr,bool bTest=true); + void HandleFractions(SmNode *pNode,int nLevel); + void HandleBrace(SmNode *pNode,int nLevel); + void HandleVerticalBrace(SmNode *pNode,int nLevel); + void HandleOperator(SmNode *pNode,int nLevel); + bool HandleLim(SmNode *pNode,int nLevel); + void HandleMAlign(SmNode *pNode,int nLevel); + void HandleMath(SmNode *pNode); + void HandleText(SmNode *pNode); + void HandleAttributes(SmNode *pNode,int nLevel); + void TypeFaceToString(OUString &rRet,sal_uInt8 nFace); + + OUStringBuffer &rRet; + SmNode *pTree; + + sal_uInt8 nHAlign; + + int nPendingAttributes; + sal_uInt64 nInsertion; + + std::vector<sal_Int16> aSizeTable; + sal_Int16 nLSize; + sal_Int16 nDSize; + sal_Int16 nCurSize; + sal_Int16 nLastSize; + sal_uInt8 nSpec; + bool bIsReInterpBrace; + OUStringBuffer sPost; + sal_Int32 nPostSup; + sal_Int32 nPostlSup; + sal_uInt8 nTypeFace; + MathTypeFontSet aUserStyles; + + enum MTOKENS {END,LINE,CHAR,TMPL,PILE,MATRIX,EMBEL,RULER,FONT,SIZE}; + enum MTEMPLATES + { + tmANGLE,tmPAREN,tmBRACE,tmBRACK,tmBAR,tmDBAR,tmFLOOR,tmCEILING, + tmLBLB,tmRBRB,tmRBLB,tmLBRP,tmLPRB,tmROOT,tmFRACT,tmSCRIPT,tmUBAR, + tmOBAR,tmLARROW,tmRARROW,tmBARROW,tmSINT,tmDINT,tmTINT,tmSSINT, + tmDSINT,tmTSINT,tmUHBRACE,tmLHBRACE,tmSUM,tmISUM,tmPROD,tmIPROD, + tmCOPROD,tmICOPROD,tmUNION,tmIUNION,tmINTER,tmIINTER,tmLIM,tmLDIV, + tmSLFRACT,tmINTOP,tmSUMOP,tmLSCRIPT,tmDIRAC,tmUARROW,tmOARROW, + tmOARC + }; +public: + static bool LookupChar(sal_Unicode nChar,OUStringBuffer &rRet, + sal_uInt8 nVersion,sal_uInt8 nTypeFace=0); +}; + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/node.cxx b/starmath/source/node.cxx new file mode 100644 index 000000000..526b2e3ee --- /dev/null +++ b/starmath/source/node.cxx @@ -0,0 +1,2940 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <node.hxx> +#include <parse.hxx> +#include <rect.hxx> +#include <symbol.hxx> +#include <smmod.hxx> +#include "mathtype.hxx" +#include "tmpdevice.hxx" +#include <visitors.hxx> + +#include <comphelper/string.hxx> +#include <tools/color.hxx> +#include <tools/fract.hxx> +#include <tools/gen.hxx> +#include <vcl/metric.hxx> +#include <vcl/outdev.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <rtl/math.hxx> + +#include <cassert> +#include <math.h> +#include <memory> +#include <float.h> +#include <vector> + +namespace { + +template<typename F> +void ForEachNonNull(SmNode *pNode, F && f) +{ + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + SmNode *pSubNode = pNode->GetSubNode(i); + if (pSubNode != nullptr) + f(pSubNode); + } +} + +} + +SmNode::SmNode(SmNodeType eNodeType, const SmToken &rNodeToken) + : maNodeToken( rNodeToken ) + , meType( eNodeType ) + , meScaleMode( SmScaleMode::None ) + , meRectHorAlign( RectHorAlign::Left ) + , mnFlags( FontChangeMask::None ) + , mnAttributes( FontAttribute::None ) + , mbIsPhantom( false ) + , mbIsSelected( false ) + , mnAccIndex( -1 ) + , mpParentNode( nullptr ) +{ +} + +SmNode::~SmNode() +{ +} + +const SmNode * SmNode::GetLeftMost() const + // returns leftmost node of current subtree. + //! (this assumes the one with index 0 is always the leftmost subnode + //! for the current node). +{ + const SmNode *pNode = GetNumSubNodes() > 0 ? + GetSubNode(0) : nullptr; + + return pNode ? pNode->GetLeftMost() : this; +} + + +void SmNode::SetPhantom(bool bIsPhantomP) +{ + if (! (Flags() & FontChangeMask::Phantom)) + mbIsPhantom = bIsPhantomP; + + bool b = mbIsPhantom; + ForEachNonNull(this, [b](SmNode *pNode){pNode->SetPhantom(b);}); +} + + +void SmNode::SetColor(const Color& rColor) +{ + if (! (Flags() & FontChangeMask::Color)) + GetFont().SetColor(rColor); + + ForEachNonNull(this, [&rColor](SmNode *pNode){pNode->SetColor(rColor);}); +} + + +void SmNode::SetAttribut(FontAttribute nAttrib) +{ + if ( + (nAttrib == FontAttribute::Bold && !(Flags() & FontChangeMask::Bold)) || + (nAttrib == FontAttribute::Italic && !(Flags() & FontChangeMask::Italic)) + ) + { + mnAttributes |= nAttrib; + } + + ForEachNonNull(this, [nAttrib](SmNode *pNode){pNode->SetAttribut(nAttrib);}); +} + + +void SmNode::ClearAttribut(FontAttribute nAttrib) +{ + if ( + (nAttrib == FontAttribute::Bold && !(Flags() & FontChangeMask::Bold)) || + (nAttrib == FontAttribute::Italic && !(Flags() & FontChangeMask::Italic)) + ) + { + mnAttributes &= ~nAttrib; + } + + ForEachNonNull(this, [nAttrib](SmNode *pNode){pNode->ClearAttribut(nAttrib);}); +} + + +void SmNode::SetFont(const SmFace &rFace) +{ + if (!(Flags() & FontChangeMask::Face)) + GetFont() = rFace; + + ForEachNonNull(this, [&rFace](SmNode *pNode){pNode->SetFont(rFace);}); +} + + +void SmNode::SetFontSize(const Fraction &rSize, FontSizeType nType) + //! 'rSize' is in units of pts +{ + Size aFntSize; + + if (!(Flags() & FontChangeMask::Size)) + { + Fraction aVal (SmPtsTo100th_mm(rSize.GetNumerator()), + rSize.GetDenominator()); + long nHeight = static_cast<long>(aVal); + + aFntSize = GetFont().GetFontSize(); + aFntSize.setWidth( 0 ); + switch(nType) + { + case FontSizeType::ABSOLUT: + aFntSize.setHeight( nHeight ); + break; + + case FontSizeType::PLUS: + aFntSize.AdjustHeight(nHeight ); + break; + + case FontSizeType::MINUS: + aFntSize.AdjustHeight( -nHeight ); + break; + + case FontSizeType::MULTIPLY: + aFntSize.setHeight( static_cast<long>(Fraction(aFntSize.Height()) * rSize) ); + break; + + case FontSizeType::DIVIDE: + if (rSize != Fraction(0)) + aFntSize.setHeight( static_cast<long>(Fraction(aFntSize.Height()) / rSize) ); + break; + default: + break; + } + + // check the requested size against maximum value + static int const nMaxVal = SmPtsTo100th_mm(128); + if (aFntSize.Height() > nMaxVal) + aFntSize.setHeight( nMaxVal ); + + GetFont().SetSize(aFntSize); + } + + ForEachNonNull(this, [&rSize, &nType](SmNode *pNode){pNode->SetFontSize(rSize, nType);}); +} + + +void SmNode::SetSize(const Fraction &rSize) +{ + GetFont() *= rSize; + + ForEachNonNull(this, [&rSize](SmNode *pNode){pNode->SetSize(rSize);}); +} + + +void SmNode::SetRectHorAlign(RectHorAlign eHorAlign, bool bApplyToSubTree ) +{ + meRectHorAlign = eHorAlign; + + if (bApplyToSubTree) + ForEachNonNull(this, [eHorAlign](SmNode *pNode){pNode->SetRectHorAlign(eHorAlign);}); +} + + +void SmNode::PrepareAttributes() +{ + GetFont().SetWeight((Attributes() & FontAttribute::Bold) ? WEIGHT_BOLD : WEIGHT_NORMAL); + GetFont().SetItalic((Attributes() & FontAttribute::Italic) ? ITALIC_NORMAL : ITALIC_NONE); +} + + +void SmNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + if (nDepth > 1024) + throw std::range_error("parser depth limit"); + + mbIsPhantom = false; + mnFlags = FontChangeMask::None; + mnAttributes = FontAttribute::None; + + switch (rFormat.GetHorAlign()) + { case SmHorAlign::Left: meRectHorAlign = RectHorAlign::Left; break; + case SmHorAlign::Center: meRectHorAlign = RectHorAlign::Center; break; + case SmHorAlign::Right: meRectHorAlign = RectHorAlign::Right; break; + } + + GetFont() = rFormat.GetFont(FNT_MATH); + OSL_ENSURE( GetFont().GetCharSet() == RTL_TEXTENCODING_UNICODE, + "unexpected CharSet" ); + GetFont().SetWeight(WEIGHT_NORMAL); + GetFont().SetItalic(ITALIC_NONE); + + ForEachNonNull(this, [&rFormat, &rDocShell, nDepth](SmNode *pNode){pNode->Prepare(rFormat, rDocShell, nDepth + 1);}); +} + +void SmNode::Move(const Point& rPosition) +{ + if (rPosition.X() == 0 && rPosition.Y() == 0) + return; + + SmRect::Move(rPosition); + + ForEachNonNull(this, [&rPosition](SmNode *pNode){pNode->Move(rPosition);}); +} + +void SmNode::CreateTextFromNode(OUStringBuffer &rText) +{ + auto nSize = GetNumSubNodes(); + if (nSize > 1) + rText.append("{"); + ForEachNonNull(this, [&rText](SmNode *pNode){pNode->CreateTextFromNode(rText);}); + if (nSize > 1) + { + rText.stripEnd(' '); + rText.append("} "); + } +} + +void SmNode::AdaptToX(OutputDevice &/*rDev*/, sal_uLong /*nWidth*/) +{ +} + + +void SmNode::AdaptToY(OutputDevice &/*rDev*/, sal_uLong /*nHeight*/) +{ +} + + +const SmNode * SmNode::FindTokenAt(sal_uInt16 nRow, sal_uInt16 nCol) const + // returns (first) ** visible ** (sub)node with the tokens text at + // position 'nRow', 'nCol'. + //! (there should be exactly one such node if any) +{ + if ( IsVisible() + && nRow == GetToken().nRow + && nCol >= GetToken().nCol && nCol < GetToken().nCol + GetToken().aText.getLength()) + return this; + else + { + size_t nNumSubNodes = GetNumSubNodes(); + for (size_t i = 0; i < nNumSubNodes; ++i) + { + const SmNode *pNode = GetSubNode(i); + + if (!pNode) + continue; + + const SmNode *pResult = pNode->FindTokenAt(nRow, nCol); + if (pResult) + return pResult; + } + } + + return nullptr; +} + + +const SmNode * SmNode::FindRectClosestTo(const Point &rPoint) const +{ + long nDist = LONG_MAX; + const SmNode *pResult = nullptr; + + if (IsVisible()) + pResult = this; + else + { + size_t nNumSubNodes = GetNumSubNodes(); + for (size_t i = 0; i < nNumSubNodes; ++i) + { + const SmNode *pNode = GetSubNode(i); + + if (!pNode) + continue; + + const SmNode *pFound = pNode->FindRectClosestTo(rPoint); + if (pFound) + { + long nTmp = pFound->OrientedDist(rPoint); + if (nTmp < nDist) + { + nDist = nTmp; + pResult = pFound; + + // quit immediately if 'rPoint' is inside the *should not + // overlap with other rectangles* part. + // This (partly) serves for getting the attributes in eg + // "bar overstrike a". + // ('nDist < 0' is used as *quick shot* to avoid evaluation of + // the following expression, where the result is already determined) + if (nDist < 0 && pFound->IsInsideRect(rPoint)) + break; + } + } + } + } + + return pResult; +} + +const SmNode * SmNode::FindNodeWithAccessibleIndex(sal_Int32 nAccIdx) const +{ + const SmNode *pResult = nullptr; + + sal_Int32 nIdx = GetAccessibleIndex(); + OUStringBuffer aTxt; + if (nIdx >= 0) + GetAccessibleText( aTxt ); // get text if used in following 'if' statement + + if (nIdx >= 0 + && nIdx <= nAccIdx && nAccIdx < nIdx + aTxt.getLength()) + pResult = this; + else + { + size_t nNumSubNodes = GetNumSubNodes(); + for (size_t i = 0; i < nNumSubNodes; ++i) + { + const SmNode *pNode = GetSubNode(i); + if (!pNode) + continue; + + pResult = pNode->FindNodeWithAccessibleIndex(nAccIdx); + if (pResult) + return pResult; + } + } + + return pResult; +} + + +SmStructureNode::~SmStructureNode() +{ + ForEachNonNull(this, std::default_delete<SmNode>()); +} + + +void SmStructureNode::ClearSubNodes() +{ + maSubNodes.clear(); +} + +void SmStructureNode::SetSubNodes(std::unique_ptr<SmNode> pFirst, std::unique_ptr<SmNode> pSecond, std::unique_ptr<SmNode> pThird) +{ + size_t nSize = pThird ? 3 : (pSecond ? 2 : (pFirst ? 1 : 0)); + maSubNodes.resize( nSize ); + if (pFirst) + maSubNodes[0] = pFirst.release(); + if (pSecond) + maSubNodes[1] = pSecond.release(); + if (pThird) + maSubNodes[2] = pThird.release(); + + ClaimPaternity(); +} + +void SmStructureNode::SetSubNodes(SmNodeArray&& rNodeArray) +{ + maSubNodes = std::move(rNodeArray); + ClaimPaternity(); +} + +bool SmStructureNode::IsVisible() const +{ + return false; +} + +size_t SmStructureNode::GetNumSubNodes() const +{ + return maSubNodes.size(); +} + +SmNode* SmStructureNode::GetSubNode(size_t nIndex) +{ + return maSubNodes[nIndex]; +} + +void SmStructureNode::GetAccessibleText( OUStringBuffer &rText ) const +{ + ForEachNonNull(const_cast<SmStructureNode *>(this), + [&rText](SmNode *pNode) + { + if (pNode->IsVisible()) + pNode->SetAccessibleIndex(rText.getLength()); + pNode->GetAccessibleText( rText ); + }); +} + +void SmStructureNode::ClaimPaternity() +{ + ForEachNonNull(this, [this](SmNode *pNode){pNode->SetParent(this);}); +} + +bool SmVisibleNode::IsVisible() const +{ + return true; +} + +size_t SmVisibleNode::GetNumSubNodes() const +{ + return 0; +} + +SmNode * SmVisibleNode::GetSubNode(size_t /*nIndex*/) +{ + return nullptr; +} + +void SmGraphicNode::GetAccessibleText( OUStringBuffer &rText ) const +{ + rText.append(GetToken().aText); +} + +void SmExpressionNode::CreateTextFromNode(OUStringBuffer &rText) +{ + size_t nSize = GetNumSubNodes(); + if (nSize > 1) + rText.append("{"); + for (size_t i = 0; i < nSize; ++i) + { + SmNode *pNode = GetSubNode(i); + if (pNode) + { + pNode->CreateTextFromNode(rText); + //Just a bit of foo to make unary +asd -asd +-asd -+asd look nice + if (pNode->GetType() == SmNodeType::Math) + if ((nSize != 2) || rText.isEmpty() || + (rText[rText.getLength() - 1] != '+' && rText[rText.getLength() - 1] != '-') ) + rText.append(" "); + } + } + + if (nSize > 1) + { + rText.stripEnd(' '); + rText.append("} "); + } +} + +void SmTableNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) + // arranges all subnodes in one column +{ + SmNode *pNode; + size_t nSize = GetNumSubNodes(); + + // make distance depend on font size + long nDist = +(rFormat.GetDistance(DIS_VERTICAL) + * GetFont().GetFontSize().Height()) / 100; + + if (nSize < 1) + return; + + // arrange subnodes and get maximum width of them + long nMaxWidth = 0, + nTmp; + for (size_t i = 0; i < nSize; ++i) + { + if (nullptr != (pNode = GetSubNode(i))) + { pNode->Arrange(rDev, rFormat); + if ((nTmp = pNode->GetItalicWidth()) > nMaxWidth) + nMaxWidth = nTmp; + } + } + + Point aPos; + SmRect::operator = (SmRect(nMaxWidth, 1)); + for (size_t i = 0; i < nSize; ++i) + { + if (nullptr != (pNode = GetSubNode(i))) + { const SmRect &rNodeRect = pNode->GetRect(); + const SmNode *pCoNode = pNode->GetLeftMost(); + RectHorAlign eHorAlign = pCoNode->GetRectHorAlign(); + + aPos = rNodeRect.AlignTo(*this, RectPos::Bottom, + eHorAlign, RectVerAlign::Baseline); + if (i) + aPos.AdjustY(nDist ); + pNode->MoveTo(aPos); + ExtendBy(rNodeRect, nSize > 1 ? RectCopyMBL::None : RectCopyMBL::Arg); + } + } + // #i972# + if (HasBaseline()) + mnFormulaBaseline = GetBaseline(); + else + { + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect aRect(aTmpDev, &rFormat, "a", GetFont().GetBorderWidth()); + mnFormulaBaseline = GetAlignM(); + // move from middle position by constant - distance + // between middle and baseline for single letter + mnFormulaBaseline += aRect.GetBaseline() - aRect.GetAlignM(); + } +} + +const SmNode * SmTableNode::GetLeftMost() const +{ + return this; +} + + +long SmTableNode::GetFormulaBaseline() const +{ + return mnFormulaBaseline; +} + + +/**************************************************************************/ + + +void SmLineNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + // Here we use the 'FNT_VARIABLE' font since it's ascent and descent in general fit better + // to the rest of the formula compared to the 'FNT_MATH' font. + GetFont() = rFormat.GetFont(FNT_VARIABLE); + Flags() |= FontChangeMask::Face; +} + + +/**************************************************************************/ + + +void SmLineNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) + // arranges all subnodes in one row with some extra space between +{ + SmNode *pNode; + size_t nSize = GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (nullptr != (pNode = GetSubNode(i))) + pNode->Arrange(rDev, rFormat); + } + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + if (nSize < 1) + { + // provide an empty rectangle with alignment parameters for the "current" + // font (in order to make "a^1 {}_2^3 a_4" work correct, that is, have the + // same sub-/supscript positions.) + //! be sure to use a character that has explicitly defined HiAttribut + //! line in rect.cxx such as 'a' in order to make 'vec a' look same to + //! 'vec {a}'. + SmRect::operator = (SmRect(aTmpDev, &rFormat, "a", + GetFont().GetBorderWidth())); + // make sure that the rectangle occupies (almost) no space + SetWidth(1); + SetItalicSpaces(0, 0); + return; + } + + // make distance depend on font size + long nDist = (rFormat.GetDistance(DIS_HORIZONTAL) * GetFont().GetFontSize().Height()) / 100; + if (!IsUseExtraSpaces()) + nDist = 0; + + Point aPos; + // copy the first node into LineNode and extend by the others + if (nullptr != (pNode = GetSubNode(0))) + SmRect::operator = (pNode->GetRect()); + + for (size_t i = 1; i < nSize; ++i) + { + if (nullptr != (pNode = GetSubNode(i))) + { + aPos = pNode->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + + // add horizontal space to the left for each but the first sub node + aPos.AdjustX(nDist ); + + pNode->MoveTo(aPos); + ExtendBy( *pNode, RectCopyMBL::Xor ); + } + } +} + + +/**************************************************************************/ + + +void SmExpressionNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) + // as 'SmLineNode::Arrange' but keeps alignment of leftmost subnode +{ + SmLineNode::Arrange(rDev, rFormat); + + // copy alignment of leftmost subnode if any + const SmNode *pNode = GetLeftMost(); + if (pNode) + SetRectHorAlign(pNode->GetRectHorAlign(), false); +} + + +/**************************************************************************/ + + +void SmUnHorNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + bool bIsPostfix = GetToken().eType == TFACT; + + SmNode *pNode0 = GetSubNode(0), + *pNode1 = GetSubNode(1); + SmNode *pOper = bIsPostfix ? pNode1 : pNode0, + *pBody = bIsPostfix ? pNode0 : pNode1; + assert(pOper); + assert(pBody); + + pOper->SetSize(Fraction (rFormat.GetRelSize(SIZ_OPERATOR), 100)); + pOper->Arrange(rDev, rFormat); + pBody->Arrange(rDev, rFormat); + + long nDist = (pOper->GetRect().GetWidth() * rFormat.GetDistance(DIS_HORIZONTAL)) / 100; + + SmRect::operator = (*pNode0); + + Point aPos = pNode1->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustX(nDist ); + pNode1->MoveTo(aPos); + ExtendBy(*pNode1, RectCopyMBL::Xor); +} + + +/**************************************************************************/ + +namespace { + +void lcl_GetHeightVerOffset(const SmRect &rRect, + long &rHeight, long &rVerOffset) + // calculate height and vertical offset of root sign suitable for 'rRect' +{ + rVerOffset = (rRect.GetBottom() - rRect.GetAlignB()) / 2; + rHeight = rRect.GetHeight() - rVerOffset; + + OSL_ENSURE(rHeight >= 0, "Sm : Ooops..."); + OSL_ENSURE(rVerOffset >= 0, "Sm : Ooops..."); +} + + +Point lcl_GetExtraPos(const SmRect &rRootSymbol, + const SmRect &rExtra) +{ + const Size &rSymSize = rRootSymbol.GetSize(); + + Point aPos = rRootSymbol.GetTopLeft() + + Point((rSymSize.Width() * 70) / 100, + (rSymSize.Height() * 52) / 100); + + // from this calculate topleft edge of 'rExtra' + aPos.AdjustX( -(rExtra.GetWidth() + rExtra.GetItalicRightSpace()) ); + aPos.AdjustY( -(rExtra.GetHeight()) ); + // if there's enough space move a bit less to the right + // examples: "nroot i a", "nroot j a" + // (it looks better if we don't use italic-spaces here) + long nX = rRootSymbol.GetLeft() + (rSymSize.Width() * 30) / 100; + if (aPos.X() > nX) + aPos.setX( nX ); + + return aPos; +} + +} + +void SmRootNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + //! pExtra needs to have the smaller index than pRootSym in order to + //! not to get the root symbol but the pExtra when clicking on it in the + //! GraphicWindow. (That is because of the simplicity of the algorithm + //! that finds the node corresponding to a mouseclick in the window.) + SmNode *pExtra = GetSubNode(0), + *pRootSym = GetSubNode(1), + *pBody = GetSubNode(2); + assert(pRootSym); + assert(pBody); + + pBody->Arrange(rDev, rFormat); + + long nHeight, + nVerOffset; + lcl_GetHeightVerOffset(*pBody, nHeight, nVerOffset); + nHeight += rFormat.GetDistance(DIS_ROOT) + * GetFont().GetFontSize().Height() / 100; + + // font specialist advised to change the width first + pRootSym->AdaptToY(rDev, nHeight); + pRootSym->AdaptToX(rDev, pBody->GetItalicWidth()); + + pRootSym->Arrange(rDev, rFormat); + + Point aPos = pRootSym->AlignTo(*pBody, RectPos::Left, RectHorAlign::Center, RectVerAlign::Baseline); + //! override calculated vertical position + aPos.setY( pRootSym->GetTop() + pBody->GetBottom() - pRootSym->GetBottom() ); + aPos.AdjustY( -nVerOffset ); + pRootSym->MoveTo(aPos); + + if (pExtra) + { pExtra->SetSize(Fraction(rFormat.GetRelSize(SIZ_INDEX), 100)); + pExtra->Arrange(rDev, rFormat); + + aPos = lcl_GetExtraPos(*pRootSym, *pExtra); + pExtra->MoveTo(aPos); + } + + SmRect::operator = (*pBody); + ExtendBy(*pRootSym, RectCopyMBL::This); + if (pExtra) + ExtendBy(*pExtra, RectCopyMBL::This, true); +} + + +void SmRootNode::CreateTextFromNode(OUStringBuffer &rText) +{ + SmNode *pExtra = GetSubNode(0); + if (pExtra) + { + rText.append("nroot "); + pExtra->CreateTextFromNode(rText); + } + else + rText.append("sqrt "); + + if (!pExtra && GetSubNode(2)->GetNumSubNodes() > 1) + rText.append("{ "); + + GetSubNode(2)->CreateTextFromNode(rText); + + if (!pExtra && GetSubNode(2)->GetNumSubNodes() > 1) + rText.append("} "); +} + +/**************************************************************************/ + + +void SmBinHorNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pLeft = LeftOperand(), + *pOper = Symbol(), + *pRight = RightOperand(); + assert(pLeft); + assert(pOper); + assert(pRight); + + pOper->SetSize(Fraction (rFormat.GetRelSize(SIZ_OPERATOR), 100)); + + pLeft ->Arrange(rDev, rFormat); + pOper ->Arrange(rDev, rFormat); + pRight->Arrange(rDev, rFormat); + + const SmRect &rOpRect = pOper->GetRect(); + + long nDist = (rOpRect.GetWidth() * + rFormat.GetDistance(DIS_HORIZONTAL)) / 100; + + SmRect::operator = (*pLeft); + + Point aPos; + aPos = pOper->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustX(nDist ); + pOper->MoveTo(aPos); + ExtendBy(*pOper, RectCopyMBL::Xor); + + aPos = pRight->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustX(nDist ); + + pRight->MoveTo(aPos); + ExtendBy(*pRight, RectCopyMBL::Xor); +} + + +/**************************************************************************/ + + +void SmBinVerNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pNum = GetSubNode(0), + *pLine = GetSubNode(1), + *pDenom = GetSubNode(2); + assert(pNum); + assert(pLine); + assert(pDenom); + + bool bIsTextmode = rFormat.IsTextmode(); + if (bIsTextmode) + { + Fraction aFraction(rFormat.GetRelSize(SIZ_INDEX), 100); + pNum ->SetSize(aFraction); + pLine ->SetSize(aFraction); + pDenom->SetSize(aFraction); + } + + pNum ->Arrange(rDev, rFormat); + pDenom->Arrange(rDev, rFormat); + + long nFontHeight = GetFont().GetFontSize().Height(), + nExtLen = nFontHeight * rFormat.GetDistance(DIS_FRACTION) / 100, + nThick = nFontHeight * rFormat.GetDistance(DIS_STROKEWIDTH) / 100, + nWidth = std::max(pNum->GetItalicWidth(), pDenom->GetItalicWidth()), + nNumDist = bIsTextmode ? 0 : + nFontHeight * rFormat.GetDistance(DIS_NUMERATOR) / 100, + nDenomDist = bIsTextmode ? 0 : + nFontHeight * rFormat.GetDistance(DIS_DENOMINATOR) / 100; + + // font specialist advised to change the width first + pLine->AdaptToY(rDev, nThick); + pLine->AdaptToX(rDev, nWidth + 2 * nExtLen); + pLine->Arrange(rDev, rFormat); + + // get horizontal alignment for numerator + const SmNode *pLM = pNum->GetLeftMost(); + RectHorAlign eHorAlign = pLM->GetRectHorAlign(); + + // move numerator to its position + Point aPos = pNum->AlignTo(*pLine, RectPos::Top, eHorAlign, RectVerAlign::Baseline); + aPos.AdjustY( -nNumDist ); + pNum->MoveTo(aPos); + + // get horizontal alignment for denominator + pLM = pDenom->GetLeftMost(); + eHorAlign = pLM->GetRectHorAlign(); + + // move denominator to its position + aPos = pDenom->AlignTo(*pLine, RectPos::Bottom, eHorAlign, RectVerAlign::Baseline); + aPos.AdjustY(nDenomDist ); + pDenom->MoveTo(aPos); + + SmRect::operator = (*pNum); + ExtendBy(*pDenom, RectCopyMBL::None).ExtendBy(*pLine, RectCopyMBL::None, pLine->GetCenterY()); +} + +void SmBinVerNode::CreateTextFromNode(OUStringBuffer &rText) +{ + SmNode *pNum = GetSubNode(0), + *pDenom = GetSubNode(2); + pNum->CreateTextFromNode(rText); + rText.append("over "); + pDenom->CreateTextFromNode(rText); +} + +const SmNode * SmBinVerNode::GetLeftMost() const +{ + return this; +} + + +namespace { + +/// @return value of the determinant formed by the two points +double Det(const Point &rHeading1, const Point &rHeading2) +{ + return rHeading1.X() * rHeading2.Y() - rHeading1.Y() * rHeading2.X(); +} + + +/// Is true iff the point 'rPoint1' belongs to the straight line through 'rPoint2' +/// and has the direction vector 'rHeading2' +bool IsPointInLine(const Point &rPoint1, + const Point &rPoint2, const Point &rHeading2) +{ + assert(rHeading2 != Point()); + + bool bRes = false; + static const double eps = 5.0 * DBL_EPSILON; + + double fLambda; + if (labs(rHeading2.X()) > labs(rHeading2.Y())) + { + fLambda = (rPoint1.X() - rPoint2.X()) / static_cast<double>(rHeading2.X()); + bRes = fabs(rPoint1.Y() - (rPoint2.Y() + fLambda * rHeading2.Y())) < eps; + } + else + { + fLambda = (rPoint1.Y() - rPoint2.Y()) / static_cast<double>(rHeading2.Y()); + bRes = fabs(rPoint1.X() - (rPoint2.X() + fLambda * rHeading2.X())) < eps; + } + + return bRes; +} + + +sal_uInt16 GetLineIntersectionPoint(Point &rResult, + const Point& rPoint1, const Point &rHeading1, + const Point& rPoint2, const Point &rHeading2) +{ + assert(rHeading1 != Point()); + assert(rHeading2 != Point()); + + sal_uInt16 nRes = 1; + static const double eps = 5.0 * DBL_EPSILON; + + // are the direction vectors linearly dependent? + double fDet = Det(rHeading1, rHeading2); + if (fabs(fDet) < eps) + { + nRes = IsPointInLine(rPoint1, rPoint2, rHeading2) ? USHRT_MAX : 0; + rResult = nRes ? rPoint1 : Point(); + } + else + { + // here we do not pay attention to the computational accuracy + // (that would be more complicated and is not really worth it in this case) + double fLambda = ( (rPoint1.Y() - rPoint2.Y()) * rHeading2.X() + - (rPoint1.X() - rPoint2.X()) * rHeading2.Y()) + / fDet; + rResult = Point(rPoint1.X() + static_cast<long>(fLambda * rHeading1.X()), + rPoint1.Y() + static_cast<long>(fLambda * rHeading1.Y())); + } + + return nRes; +} + +} + + +/// @return position and size of the diagonal line +/// premise: SmRect of the node defines the limitation(!) consequently it has to be known upfront +void SmBinDiagonalNode::GetOperPosSize(Point &rPos, Size &rSize, + const Point &rDiagPoint, double fAngleDeg) const + +{ + static const double fPi = 3.1415926535897932384626433; + double fAngleRad = fAngleDeg / 180.0 * fPi; + long nRectLeft = GetItalicLeft(), + nRectRight = GetItalicRight(), + nRectTop = GetTop(), + nRectBottom = GetBottom(); + Point aRightHdg (100, 0), + aDownHdg (0, 100), + aDiagHdg ( static_cast<long>(100.0 * cos(fAngleRad)), + static_cast<long>(-100.0 * sin(fAngleRad)) ); + + long nLeft, nRight, nTop, nBottom; // margins of the rectangle for the diagonal + Point aPoint; + if (IsAscending()) + { + // determine top right corner + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectTop), aRightHdg, + rDiagPoint, aDiagHdg); + // is there a point of intersection with the top border? + if (aPoint.X() <= nRectRight) + { + nRight = aPoint.X(); + nTop = nRectTop; + } + else + { + // there has to be a point of intersection with the right border! + GetLineIntersectionPoint(aPoint, + Point(nRectRight, nRectTop), aDownHdg, + rDiagPoint, aDiagHdg); + + nRight = nRectRight; + nTop = aPoint.Y(); + } + + // determine bottom left corner + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectBottom), aRightHdg, + rDiagPoint, aDiagHdg); + // is there a point of intersection with the bottom border? + if (aPoint.X() >= nRectLeft) + { + nLeft = aPoint.X(); + nBottom = nRectBottom; + } + else + { + // there has to be a point of intersection with the left border! + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectTop), aDownHdg, + rDiagPoint, aDiagHdg); + + nLeft = nRectLeft; + nBottom = aPoint.Y(); + } + } + else + { + // determine top left corner + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectTop), aRightHdg, + rDiagPoint, aDiagHdg); + // is there a point of intersection with the top border? + if (aPoint.X() >= nRectLeft) + { + nLeft = aPoint.X(); + nTop = nRectTop; + } + else + { + // there has to be a point of intersection with the left border! + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectTop), aDownHdg, + rDiagPoint, aDiagHdg); + + nLeft = nRectLeft; + nTop = aPoint.Y(); + } + + // determine bottom right corner + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectBottom), aRightHdg, + rDiagPoint, aDiagHdg); + // is there a point of intersection with the bottom border? + if (aPoint.X() <= nRectRight) + { + nRight = aPoint.X(); + nBottom = nRectBottom; + } + else + { + // there has to be a point of intersection with the right border! + GetLineIntersectionPoint(aPoint, + Point(nRectRight, nRectTop), aDownHdg, + rDiagPoint, aDiagHdg); + + nRight = nRectRight; + nBottom = aPoint.Y(); + } + } + + rSize = Size(nRight - nLeft + 1, nBottom - nTop + 1); + rPos.setX( nLeft ); + rPos.setY( nTop ); +} + + +void SmBinDiagonalNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + // Both arguments have to get into the SubNodes before the Operator so that clicking + // within the GraphicWindow sets the FormulaCursor correctly (cf. SmRootNode) + SmNode *pLeft = GetSubNode(0), + *pRight = GetSubNode(1), + *pLine = GetSubNode(2); + assert(pLeft); + assert(pRight); + assert(pLine && pLine->GetType() == SmNodeType::PolyLine); + + SmPolyLineNode *pOper = static_cast<SmPolyLineNode *>(pLine); + assert(pOper); + + //! some routines being called extract some info from the OutputDevice's + //! font (eg the space to be used for borders OR the font name(!!)). + //! Thus the font should reflect the needs and has to be set! + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + pLeft->Arrange(aTmpDev, rFormat); + pRight->Arrange(aTmpDev, rFormat); + + // determine implicitly the values (incl. the margin) of the diagonal line + pOper->Arrange(aTmpDev, rFormat); + + long nDelta = pOper->GetWidth() * 8 / 10; + + // determine TopLeft position from the right argument + Point aPos; + aPos.setX( pLeft->GetItalicRight() + nDelta + pRight->GetItalicLeftSpace() ); + if (IsAscending()) + aPos.setY( pLeft->GetBottom() + nDelta ); + else + aPos.setY( pLeft->GetTop() - nDelta - pRight->GetHeight() ); + + pRight->MoveTo(aPos); + + // determine new baseline + long nTmpBaseline = IsAscending() ? (pLeft->GetBottom() + pRight->GetTop()) / 2 + : (pLeft->GetTop() + pRight->GetBottom()) / 2; + Point aLogCenter ((pLeft->GetItalicRight() + pRight->GetItalicLeft()) / 2, + nTmpBaseline); + + SmRect::operator = (*pLeft); + ExtendBy(*pRight, RectCopyMBL::None); + + + // determine position and size of diagonal line + Size aTmpSize; + GetOperPosSize(aPos, aTmpSize, aLogCenter, IsAscending() ? 60.0 : -60.0); + + // font specialist advised to change the width first + pOper->AdaptToY(aTmpDev, aTmpSize.Height()); + pOper->AdaptToX(aTmpDev, aTmpSize.Width()); + // and make it active + pOper->Arrange(aTmpDev, rFormat); + + pOper->MoveTo(aPos); + + ExtendBy(*pOper, RectCopyMBL::None, nTmpBaseline); +} + + +/**************************************************************************/ + + +void SmSubSupNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + OSL_ENSURE(GetNumSubNodes() == 1 + SUBSUP_NUM_ENTRIES, + "Sm: wrong number of subnodes"); + + SmNode *pBody = GetBody(); + assert(pBody); + + long nOrigHeight = pBody->GetFont().GetFontSize().Height(); + + pBody->Arrange(rDev, rFormat); + + const SmRect &rBodyRect = pBody->GetRect(); + SmRect::operator = (rBodyRect); + + // line that separates sub- and supscript rectangles + long nDelimLine = SmFromTo(GetAlignB(), GetAlignT(), 0.4); + + Point aPos; + long nDelta, nDist; + + // iterate over all possible sub-/supscripts + SmRect aTmpRect (rBodyRect); + for (int i = 0; i < SUBSUP_NUM_ENTRIES; i++) + { + SmSubSup eSubSup = static_cast<SmSubSup>(i); + SmNode *pSubSup = GetSubSup(eSubSup); + + if (!pSubSup) + continue; + + // switch position of limits if we are in textmode + if (rFormat.IsTextmode() && (GetToken().nGroup & TG::Limit)) + switch (eSubSup) + { case CSUB: eSubSup = RSUB; break; + case CSUP: eSubSup = RSUP; break; + default: + break; + } + + // prevent sub-/supscripts from diminishing in size + // (as would be in "a_{1_{2_{3_4}}}") + if (GetFont().GetFontSize().Height() > rFormat.GetBaseSize().Height() / 3) + { + sal_uInt16 nIndex = (eSubSup == CSUB || eSubSup == CSUP) ? + SIZ_LIMITS : SIZ_INDEX; + Fraction aFraction ( rFormat.GetRelSize(nIndex), 100 ); + pSubSup->SetSize(aFraction); + } + + pSubSup->Arrange(rDev, rFormat); + + bool bIsTextmode = rFormat.IsTextmode(); + nDist = 0; + + //! be sure that CSUB, CSUP are handled before the other cases! + switch (eSubSup) + { case RSUB : + case LSUB : + if (!bIsTextmode) + nDist = nOrigHeight + * rFormat.GetDistance(DIS_SUBSCRIPT) / 100; + aPos = pSubSup->GetRect().AlignTo(aTmpRect, + eSubSup == LSUB ? RectPos::Left : RectPos::Right, + RectHorAlign::Center, RectVerAlign::Bottom); + aPos.AdjustY(nDist ); + nDelta = nDelimLine - aPos.Y(); + if (nDelta > 0) + aPos.AdjustY(nDelta ); + break; + case RSUP : + case LSUP : + if (!bIsTextmode) + nDist = nOrigHeight + * rFormat.GetDistance(DIS_SUPERSCRIPT) / 100; + aPos = pSubSup->GetRect().AlignTo(aTmpRect, + eSubSup == LSUP ? RectPos::Left : RectPos::Right, + RectHorAlign::Center, RectVerAlign::Top); + aPos.AdjustY( -nDist ); + nDelta = aPos.Y() + pSubSup->GetHeight() - nDelimLine; + if (nDelta > 0) + aPos.AdjustY( -nDelta ); + break; + case CSUB : + if (!bIsTextmode) + nDist = nOrigHeight + * rFormat.GetDistance(DIS_LOWERLIMIT) / 100; + aPos = pSubSup->GetRect().AlignTo(rBodyRect, RectPos::Bottom, + RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustY(nDist ); + break; + case CSUP : + if (!bIsTextmode) + nDist = nOrigHeight + * rFormat.GetDistance(DIS_UPPERLIMIT) / 100; + aPos = pSubSup->GetRect().AlignTo(rBodyRect, RectPos::Top, + RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustY( -nDist ); + break; + } + + pSubSup->MoveTo(aPos); + ExtendBy(*pSubSup, RectCopyMBL::This, true); + + // update rectangle to which RSUB, RSUP, LSUB, LSUP + // will be aligned to + if (eSubSup == CSUB || eSubSup == CSUP) + aTmpRect = *this; + } +} + +void SmSubSupNode::CreateTextFromNode(OUStringBuffer &rText) +{ + SmNode *pNode; + GetSubNode(0)->CreateTextFromNode(rText); + + if (nullptr != (pNode = GetSubNode(LSUB+1))) + { + rText.append("lsub "); + pNode->CreateTextFromNode(rText); + } + if (nullptr != (pNode = GetSubNode(LSUP+1))) + { + rText.append("lsup "); + pNode->CreateTextFromNode(rText); + } + if (nullptr != (pNode = GetSubNode(CSUB+1))) + { + rText.append("csub "); + pNode->CreateTextFromNode(rText); + } + if (nullptr != (pNode = GetSubNode(CSUP+1))) + { + rText.append("csup "); + pNode->CreateTextFromNode(rText); + } + if (nullptr != (pNode = GetSubNode(RSUB+1))) + { + rText.stripEnd(' '); + rText.append("_"); + pNode->CreateTextFromNode(rText); + } + if (nullptr != (pNode = GetSubNode(RSUP+1))) + { + rText.stripEnd(' '); + rText.append("^"); + pNode->CreateTextFromNode(rText); + } +} + + +/**************************************************************************/ + +void SmBraceNode::CreateTextFromNode(OUStringBuffer &rText) +{ + if (GetScaleMode() == SmScaleMode::Height) + rText.append("left "); + { + OUStringBuffer aStrBuf; + OpeningBrace()->CreateTextFromNode(aStrBuf); + OUString aStr = aStrBuf.makeStringAndClear(); + aStr = comphelper::string::strip(aStr, ' '); + aStr = comphelper::string::stripStart(aStr, '\\'); + if (!aStr.isEmpty()) + { + if (aStr == "divides") + rText.append("lline"); + else if (aStr == "parallel") + rText.append("ldline"); + else if (aStr == "<") + rText.append("langle"); + else + rText.append(aStr); + rText.append(" "); + } + else + rText.append("none "); + } + Body()->CreateTextFromNode(rText); + if (GetScaleMode() == SmScaleMode::Height) + rText.append("right "); + { + OUStringBuffer aStrBuf; + ClosingBrace()->CreateTextFromNode(aStrBuf); + OUString aStr = aStrBuf.makeStringAndClear(); + aStr = comphelper::string::strip(aStr, ' '); + aStr = comphelper::string::stripStart(aStr, '\\'); + if (!aStr.isEmpty()) + { + if (aStr == "divides") + rText.append("rline"); + else if (aStr == "parallel") + rText.append("rdline"); + else if (aStr == ">") + rText.append("rangle"); + else + rText.append(aStr); + rText.append(" "); + } + else + rText.append("none "); + } + rText.append(" "); +} + +void SmBraceNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pLeft = OpeningBrace(), + *pBody = Body(), + *pRight = ClosingBrace(); + assert(pLeft); + assert(pBody); + assert(pRight); + + pBody->Arrange(rDev, rFormat); + + bool bIsScaleNormal = rFormat.IsScaleNormalBrackets(), + bScale = pBody->GetHeight() > 0 && + (GetScaleMode() == SmScaleMode::Height || bIsScaleNormal), + bIsABS = GetToken().eType == TABS; + + long nFaceHeight = GetFont().GetFontSize().Height(); + + // determine oversize in % + sal_uInt16 nPerc = 0; + if (!bIsABS && bScale) + { // in case of oversize braces... + sal_uInt16 nIndex = GetScaleMode() == SmScaleMode::Height ? + DIS_BRACKETSIZE : DIS_NORMALBRACKETSIZE; + nPerc = rFormat.GetDistance(nIndex); + } + + // determine the height for the braces + long nBraceHeight; + if (bScale) + { + nBraceHeight = pBody->GetType() == SmNodeType::Bracebody ? + static_cast<SmBracebodyNode *>(pBody)->GetBodyHeight() + : pBody->GetHeight(); + nBraceHeight += 2 * (nBraceHeight * nPerc / 100); + } + else + nBraceHeight = nFaceHeight; + + // distance to the argument + nPerc = bIsABS ? 0 : rFormat.GetDistance(DIS_BRACKETSPACE); + long nDist = nFaceHeight * nPerc / 100; + + // if wanted, scale the braces to the wanted size + if (bScale) + { + Size aTmpSize (pLeft->GetFont().GetFontSize()); + OSL_ENSURE(pRight->GetFont().GetFontSize() == aTmpSize, + "Sm : different font sizes"); + aTmpSize.setWidth( std::min(nBraceHeight * 60 / 100, + rFormat.GetBaseSize().Height() * 3 / 2) ); + // correction factor since change from StarMath to OpenSymbol font + // because of the different font width in the FontMetric + aTmpSize.setWidth( aTmpSize.Width() * 182 ); + aTmpSize.setWidth( aTmpSize.Width() / 267 ); + + sal_Unicode cChar = pLeft->GetToken().cMathChar; + if (cChar != MS_LINE && cChar != MS_DLINE && + cChar != MS_VERTLINE && cChar != MS_DVERTLINE) + pLeft ->GetFont().SetSize(aTmpSize); + + cChar = pRight->GetToken().cMathChar; + if (cChar != MS_LINE && cChar != MS_DLINE && + cChar != MS_VERTLINE && cChar != MS_DVERTLINE) + pRight->GetFont().SetSize(aTmpSize); + + pLeft ->AdaptToY(rDev, nBraceHeight); + pRight->AdaptToY(rDev, nBraceHeight); + } + + pLeft ->Arrange(rDev, rFormat); + pRight->Arrange(rDev, rFormat); + + // required in order to make "\(a\) - (a) - left ( a right )" look alright + RectVerAlign eVerAlign = bScale ? RectVerAlign::CenterY : RectVerAlign::Baseline; + + Point aPos; + aPos = pLeft->AlignTo(*pBody, RectPos::Left, RectHorAlign::Center, eVerAlign); + aPos.AdjustX( -nDist ); + pLeft->MoveTo(aPos); + + aPos = pRight->AlignTo(*pBody, RectPos::Right, RectHorAlign::Center, eVerAlign); + aPos.AdjustX(nDist ); + pRight->MoveTo(aPos); + + SmRect::operator = (*pBody); + ExtendBy(*pLeft, RectCopyMBL::This).ExtendBy(*pRight, RectCopyMBL::This); +} + + +/**************************************************************************/ + + +void SmBracebodyNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + size_t nNumSubNodes = GetNumSubNodes(); + if (nNumSubNodes == 0) + return; + + // arrange arguments + for (size_t i = 0; i < nNumSubNodes; i += 2) + GetSubNode(i)->Arrange(rDev, rFormat); + + // build reference rectangle with necessary info for vertical alignment + SmRect aRefRect (*GetSubNode(0)); + for (size_t i = 0; i < nNumSubNodes; i += 2) + { + SmRect aTmpRect (*GetSubNode(i)); + Point aPos = aTmpRect.AlignTo(aRefRect, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + aTmpRect.MoveTo(aPos); + aRefRect.ExtendBy(aTmpRect, RectCopyMBL::Xor); + } + + mnBodyHeight = aRefRect.GetHeight(); + + // scale separators to required height and arrange them + bool bScale = GetScaleMode() == SmScaleMode::Height || rFormat.IsScaleNormalBrackets(); + long nHeight = bScale ? aRefRect.GetHeight() : GetFont().GetFontSize().Height(); + sal_uInt16 nIndex = GetScaleMode() == SmScaleMode::Height ? + DIS_BRACKETSIZE : DIS_NORMALBRACKETSIZE; + sal_uInt16 nPerc = rFormat.GetDistance(nIndex); + if (bScale) + nHeight += 2 * (nHeight * nPerc / 100); + for (size_t i = 1; i < nNumSubNodes; i += 2) + { + SmNode *pNode = GetSubNode(i); + pNode->AdaptToY(rDev, nHeight); + pNode->Arrange(rDev, rFormat); + } + + // horizontal distance between argument and brackets or separators + long nDist = GetFont().GetFontSize().Height() + * rFormat.GetDistance(DIS_BRACKETSPACE) / 100; + + SmNode *pLeft = GetSubNode(0); + SmRect::operator = (*pLeft); + for (size_t i = 1; i < nNumSubNodes; ++i) + { + bool bIsSeparator = i % 2 != 0; + RectVerAlign eVerAlign = bIsSeparator ? RectVerAlign::CenterY : RectVerAlign::Baseline; + + SmNode *pRight = GetSubNode(i); + Point aPosX = pRight->AlignTo(*pLeft, RectPos::Right, RectHorAlign::Center, eVerAlign), + aPosY = pRight->AlignTo(aRefRect, RectPos::Right, RectHorAlign::Center, eVerAlign); + aPosX.AdjustX(nDist ); + + pRight->MoveTo(Point(aPosX.X(), aPosY.Y())); + ExtendBy(*pRight, bIsSeparator ? RectCopyMBL::This : RectCopyMBL::Xor); + + pLeft = pRight; + } +} + + +/**************************************************************************/ + + +void SmVerticalBraceNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pBody = Body(), + *pBrace = Brace(), + *pScript = Script(); + assert(pBody); + assert(pBrace); + assert(pScript); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + pBody->Arrange(aTmpDev, rFormat); + + // size is the same as for limits for this part + pScript->SetSize( Fraction( rFormat.GetRelSize(SIZ_LIMITS), 100 ) ); + // braces are a bit taller than usually + pBrace ->SetSize( Fraction(3, 2) ); + + long nItalicWidth = pBody->GetItalicWidth(); + if (nItalicWidth > 0) + pBrace->AdaptToX(aTmpDev, nItalicWidth); + + pBrace ->Arrange(aTmpDev, rFormat); + pScript->Arrange(aTmpDev, rFormat); + + // determine the relative position and the distances between each other + RectPos eRectPos; + long nFontHeight = pBody->GetFont().GetFontSize().Height(); + long nDistBody = nFontHeight * rFormat.GetDistance(DIS_ORNAMENTSIZE), + nDistScript = nFontHeight; + if (GetToken().eType == TOVERBRACE) + { + eRectPos = RectPos::Top; + nDistBody = - nDistBody; + nDistScript *= - rFormat.GetDistance(DIS_UPPERLIMIT); + } + else // TUNDERBRACE + { + eRectPos = RectPos::Bottom; + nDistScript *= + rFormat.GetDistance(DIS_LOWERLIMIT); + } + nDistBody /= 100; + nDistScript /= 100; + + Point aPos = pBrace->AlignTo(*pBody, eRectPos, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustY(nDistBody ); + pBrace->MoveTo(aPos); + + aPos = pScript->AlignTo(*pBrace, eRectPos, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustY(nDistScript ); + pScript->MoveTo(aPos); + + SmRect::operator = (*pBody); + ExtendBy(*pBrace, RectCopyMBL::This).ExtendBy(*pScript, RectCopyMBL::This); +} + + +/**************************************************************************/ + + +SmNode * SmOperNode::GetSymbol() +{ + SmNode *pNode = GetSubNode(0); + assert(pNode); + + if (pNode->GetType() == SmNodeType::SubSup) + pNode = static_cast<SmSubSupNode *>(pNode)->GetBody(); + + OSL_ENSURE(pNode, "Sm: NULL pointer!"); + return pNode; +} + + +long SmOperNode::CalcSymbolHeight(const SmNode &rSymbol, + const SmFormat &rFormat) const + // returns the font height to be used for operator-symbol +{ + long nHeight = GetFont().GetFontSize().Height(); + + SmTokenType eTmpType = GetToken().eType; + if (eTmpType == TLIM || eTmpType == TLIMINF || eTmpType == TLIMSUP) + return nHeight; + + if (!rFormat.IsTextmode()) + { + // set minimum size () + nHeight += (nHeight * 20) / 100; + + nHeight += nHeight + * rFormat.GetDistance(DIS_OPERATORSIZE) / 100; + nHeight = nHeight * 686 / 845; + } + + // correct user-defined symbols to match height of sum from used font + if (rSymbol.GetToken().eType == TSPECIAL) + nHeight = nHeight * 845 / 686; + + return nHeight; +} + + +void SmOperNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pOper = GetSubNode(0); + SmNode *pBody = GetSubNode(1); + + assert(pOper); + assert(pBody); + + SmNode *pSymbol = GetSymbol(); + pSymbol->SetSize(Fraction(CalcSymbolHeight(*pSymbol, rFormat), + pSymbol->GetFont().GetFontSize().Height())); + + pBody->Arrange(rDev, rFormat); + bool bDynamicallySized = false; + if (pSymbol->GetToken().eType == TINTD) + { + long nBodyHeight = pBody->GetHeight(); + long nFontHeight = pSymbol->GetFont().GetFontSize().Height(); + if (nFontHeight < nBodyHeight) + { + pSymbol->SetSize(Fraction(nBodyHeight, nFontHeight)); + bDynamicallySized = true; + } + } + pOper->Arrange(rDev, rFormat); + + long nOrigHeight = GetFont().GetFontSize().Height(), + nDist = nOrigHeight + * rFormat.GetDistance(DIS_OPERATORSPACE) / 100; + + Point aPos = pOper->AlignTo(*pBody, RectPos::Left, RectHorAlign::Center, bDynamicallySized ? RectVerAlign::CenterY : RectVerAlign::Mid); + aPos.AdjustX( -nDist ); + pOper->MoveTo(aPos); + + SmRect::operator = (*pBody); + ExtendBy(*pOper, RectCopyMBL::This); +} + + +/**************************************************************************/ + + +void SmAlignNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) + // set alignment within the entire subtree (including current node) +{ + assert(GetNumSubNodes() == 1); + + SmNode *pNode = GetSubNode(0); + assert(pNode); + + RectHorAlign eHorAlign = RectHorAlign::Center; + switch (GetToken().eType) + { + case TALIGNL: eHorAlign = RectHorAlign::Left; break; + case TALIGNC: eHorAlign = RectHorAlign::Center; break; + case TALIGNR: eHorAlign = RectHorAlign::Right; break; + default: + break; + } + SetRectHorAlign(eHorAlign); + + pNode->Arrange(rDev, rFormat); + + SmRect::operator = (pNode->GetRect()); +} + + +/**************************************************************************/ + + +void SmAttributNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pAttr = Attribute(), + *pBody = Body(); + assert(pBody); + assert(pAttr); + + pBody->Arrange(rDev, rFormat); + + if (GetScaleMode() == SmScaleMode::Width) + pAttr->AdaptToX(rDev, pBody->GetItalicWidth()); + pAttr->Arrange(rDev, rFormat); + + // get relative position of attribute + RectVerAlign eVerAlign; + long nDist = 0; + switch (GetToken().eType) + { case TUNDERLINE : + eVerAlign = RectVerAlign::AttributeLo; + break; + case TOVERSTRIKE : + eVerAlign = RectVerAlign::AttributeMid; + break; + default : + eVerAlign = RectVerAlign::AttributeHi; + if (pBody->GetType() == SmNodeType::Attribut) + nDist = GetFont().GetFontSize().Height() + * rFormat.GetDistance(DIS_ORNAMENTSPACE) / 100; + } + Point aPos = pAttr->AlignTo(*pBody, RectPos::Attribute, RectHorAlign::Center, eVerAlign); + aPos.AdjustY( -nDist ); + pAttr->MoveTo(aPos); + + SmRect::operator = (*pBody); + ExtendBy(*pAttr, RectCopyMBL::This, true); +} + +void SmFontNode::CreateTextFromNode(OUStringBuffer &rText) +{ + rText.append("{"); + sal_Int32 nc,r,g,b; + + switch (GetToken().eType) + { + case TBOLD: + rText.append("bold "); + break; + case TNBOLD: + rText.append("nbold "); + break; + case TITALIC: + rText.append("italic "); + break; + case TNITALIC: + rText.append("nitalic "); + break; + case TPHANTOM: + rText.append("phantom "); + break; + case TSIZE: + { + rText.append("size "); + switch (meSizeType) + { + case FontSizeType::PLUS: + rText.append("+"); + break; + case FontSizeType::MINUS: + rText.append("-"); + break; + case FontSizeType::MULTIPLY: + rText.append("*"); + break; + case FontSizeType::DIVIDE: + rText.append("/"); + break; + case FontSizeType::ABSOLUT: + default: + break; + } + rText.append(::rtl::math::doubleToUString( + static_cast<double>(maFontSize), + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, '.', true)); + rText.append(" "); + } + break; + case TBLACK: + rText.append("color black "); + break; + case TWHITE: + rText.append("color white "); + break; + case TRED: + rText.append("color red "); + break; + case TGREEN: + rText.append("color green "); + break; + case TBLUE: + rText.append("color blue "); + break; + case TCYAN: + rText.append("color cyan "); + break; + case TMAGENTA: + rText.append("color magenta "); + break; + case TYELLOW: + rText.append("color yellow "); + break; + case TTEAL: + rText.append("color teal "); + break; + case TSILVER: + rText.append("color silver "); + break; + case TGRAY: + rText.append("color gray "); + break; + case TMAROON: + rText.append("color maroon "); + break; + case TPURPLE: + rText.append("color purple "); + break; + case TLIME: + rText.append("color lime "); + break; + case TOLIVE: + rText.append("color olive "); + break; + case TNAVY: + rText.append("color navy "); + break; + case TAQUA: + rText.append("color aqua "); + break; + case TFUCHSIA: + rText.append("color fuchsia "); + break; + case TRGB: + rText.append("color rgb "); + nc = GetToken().aText.toInt32(); + b = nc % 256; + nc /= 256; + g = nc % 256; + nc /= 256; + r = nc % 256; + rText.append(r); + rText.append(" "); + rText.append(g); + rText.append(" "); + rText.append(b); + rText.append(" "); + break; + case TSANS: + rText.append("font sans "); + break; + case TSERIF: + rText.append("font serif "); + break; + case TFIXED: + rText.append("font fixed "); + break; + default: + break; + } + if (GetNumSubNodes() > 1) + GetSubNode(1)->CreateTextFromNode(rText); + + rText.stripEnd(' '); + rText.append("} "); +} + +void SmFontNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + //! prepare subnodes first + SmNode::Prepare(rFormat, rDocShell, nDepth); + + int nFnt = -1; + switch (GetToken().eType) + { + case TFIXED: nFnt = FNT_FIXED; break; + case TSANS: nFnt = FNT_SANS; break; + case TSERIF: nFnt = FNT_SERIF; break; + default: + break; + } + if (nFnt != -1) + { GetFont() = rFormat.GetFont( sal::static_int_cast< sal_uInt16 >(nFnt) ); + SetFont(GetFont()); + } + + //! prevent overwrites of this font by 'Arrange' or 'SetFont' calls of + //! other font nodes (those with lower depth in the tree) + Flags() |= FontChangeMask::Face; +} + +void SmFontNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pNode = GetSubNode(1); + assert(pNode); + sal_Int32 nc; + Color col_perso_rgb_color (0); + + switch (GetToken().eType) + { case TSIZE : + pNode->SetFontSize(maFontSize, meSizeType); + break; + case TSANS : + case TSERIF : + case TFIXED : + pNode->SetFont(GetFont()); + break; + case TUNKNOWN : break; // no assertion on "font <?> <?>" + + case TPHANTOM : SetPhantom(true); break; + case TBOLD : SetAttribut(FontAttribute::Bold); break; + case TITALIC : SetAttribut(FontAttribute::Italic); break; + case TNBOLD : ClearAttribut(FontAttribute::Bold); break; + case TNITALIC : ClearAttribut(FontAttribute::Italic); break; + + case TBLACK : SetColor(COL_BLACK); break; + case TWHITE : SetColor(COL_WHITE); break; + case TRED : SetColor(COL_LIGHTRED); break; + case TGREEN : SetColor(COL_GREEN); break; + case TBLUE : SetColor(COL_LIGHTBLUE); break; + case TCYAN : SetColor(COL_LIGHTCYAN); break; // as in Calc + case TMAGENTA : SetColor(COL_LIGHTMAGENTA); break; // as in Calc + case TYELLOW : SetColor(COL_YELLOW); break; + case TTEAL : SetColor(COL_CYAN); break; + case TSILVER : SetColor(COL_LIGHTGRAY); break; + case TGRAY : SetColor(COL_GRAY); break; + case TMAROON : SetColor(COL_RED); break; + case TPURPLE : SetColor(COL_MAGENTA); break; + case TLIME : SetColor(COL_LIGHTGREEN); break; + case TOLIVE : SetColor(COL_BROWN); break; + case TNAVY : SetColor(COL_BLUE); break; + case TAQUA : SetColor(COL_LIGHTCYAN); break; + case TFUCHSIA : SetColor(COL_LIGHTMAGENTA); break; + case TRGB : + nc = GetToken().aText.toInt32(); + col_perso_rgb_color.SetBlue(nc % 256); + nc /= 256; + col_perso_rgb_color.SetGreen(nc % 256); + nc /= 256; + col_perso_rgb_color.SetRed(nc % 256); + SetColor(col_perso_rgb_color); + break; + + default: + SAL_WARN("starmath", "unknown case"); + } + + pNode->Arrange(rDev, rFormat); + + SmRect::operator = (pNode->GetRect()); +} + + +void SmFontNode::SetSizeParameter(const Fraction& rValue, FontSizeType eType) +{ + meSizeType = eType; + maFontSize = rValue; +} + + +/**************************************************************************/ + + +SmPolyLineNode::SmPolyLineNode(const SmToken &rNodeToken) + : SmGraphicNode(SmNodeType::PolyLine, rNodeToken) + , maPoly(2) + , maToSize() + , mnWidth(0) +{ +} + + +void SmPolyLineNode::AdaptToX(OutputDevice &/*rDev*/, sal_uLong nNewWidth) +{ + maToSize.setWidth( nNewWidth ); +} + + +void SmPolyLineNode::AdaptToY(OutputDevice &/*rDev*/, sal_uLong nNewHeight) +{ + GetFont().FreezeBorderWidth(); + maToSize.setHeight( nNewHeight ); +} + + +void SmPolyLineNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + //! some routines being called extract some info from the OutputDevice's + //! font (eg the space to be used for borders OR the font name(!!)). + //! Thus the font should reflect the needs and has to be set! + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + long nBorderwidth = GetFont().GetBorderWidth(); + + // create polygon using both endpoints + assert(maPoly.GetSize() == 2); + Point aPointA, aPointB; + if (GetToken().eType == TWIDESLASH) + { + aPointA.setX( nBorderwidth ); + aPointA.setY( maToSize.Height() - nBorderwidth ); + aPointB.setX( maToSize.Width() - nBorderwidth ); + aPointB.setY( nBorderwidth ); + } + else + { + OSL_ENSURE(GetToken().eType == TWIDEBACKSLASH, "Sm : unexpected token"); + aPointA.setX( nBorderwidth ); + aPointA.setY( nBorderwidth ); + aPointB.setX( maToSize.Width() - nBorderwidth ); + aPointB.setY( maToSize.Height() - nBorderwidth ); + } + maPoly.SetPoint(aPointA, 0); + maPoly.SetPoint(aPointB, 1); + + long nThick = GetFont().GetFontSize().Height() + * rFormat.GetDistance(DIS_STROKEWIDTH) / 100; + mnWidth = nThick + 2 * nBorderwidth; + + SmRect::operator = (SmRect(maToSize.Width(), maToSize.Height())); +} + + +/**************************************************************************/ + +void SmRootSymbolNode::AdaptToX(OutputDevice &/*rDev*/, sal_uLong nWidth) +{ + mnBodyWidth = nWidth; +} + + +void SmRootSymbolNode::AdaptToY(OutputDevice &rDev, sal_uLong nHeight) +{ + // some additional length so that the horizontal + // bar will be positioned above the argument + SmMathSymbolNode::AdaptToY(rDev, nHeight + nHeight / 10); +} + + +/**************************************************************************/ + + +void SmRectangleNode::AdaptToX(OutputDevice &/*rDev*/, sal_uLong nWidth) +{ + maToSize.setWidth( nWidth ); +} + + +void SmRectangleNode::AdaptToY(OutputDevice &/*rDev*/, sal_uLong nHeight) +{ + GetFont().FreezeBorderWidth(); + maToSize.setHeight( nHeight ); +} + + +void SmRectangleNode::Arrange(OutputDevice &rDev, const SmFormat &/*rFormat*/) +{ + long nFontHeight = GetFont().GetFontSize().Height(); + long nWidth = maToSize.Width(), + nHeight = maToSize.Height(); + if (nHeight == 0) + nHeight = nFontHeight / 30; + if (nWidth == 0) + nWidth = nFontHeight / 3; + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + // add some borderspace + sal_uLong nTmpBorderWidth = GetFont().GetBorderWidth(); + nHeight += 2 * nTmpBorderWidth; + + //! use this method in order to have 'SmRect::HasAlignInfo() == true' + //! and thus having the attribute-fences updated in 'SmRect::ExtendBy' + SmRect::operator = (SmRect(nWidth, nHeight)); +} + + +/**************************************************************************/ + + +SmTextNode::SmTextNode( SmNodeType eNodeType, const SmToken &rNodeToken, sal_uInt16 nFontDescP ) + : SmVisibleNode(eNodeType, rNodeToken) + , mnFontDesc(nFontDescP) + , mnSelectionStart(0) + , mnSelectionEnd(0) +{ +} + +SmTextNode::SmTextNode( const SmToken &rNodeToken, sal_uInt16 nFontDescP ) + : SmVisibleNode(SmNodeType::Text, rNodeToken) + , mnFontDesc(nFontDescP) + , mnSelectionStart(0) + , mnSelectionEnd(0) +{ +} + +void SmTextNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + // default setting for horizontal alignment of nodes with TTEXT + // content is as alignl (cannot be done in Arrange since it would + // override the settings made by an SmAlignNode before) + if (TTEXT == GetToken().eType) + SetRectHorAlign( RectHorAlign::Left ); + + maText = GetToken().aText; + GetFont() = rFormat.GetFont(GetFontDesc()); + + if (IsItalic( GetFont() )) + Attributes() |= FontAttribute::Italic; + if (IsBold( GetFont() )) + Attributes() |= FontAttribute::Bold; + + // special handling for ':' where it is a token on its own and is likely + // to be used for mathematical notations. (E.g. a:b = 2:3) + // In that case it should not be displayed in italic. + if (GetToken().aText.getLength() == 1 && GetToken().aText[0] == ':') + Attributes() &= ~FontAttribute::Italic; +}; + + +void SmTextNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + sal_uInt16 nSizeDesc = GetFontDesc() == FNT_FUNCTION ? + SIZ_FUNCTION : SIZ_TEXT; + GetFont() *= Fraction (rFormat.GetRelSize(nSizeDesc), 100); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, maText, GetFont().GetBorderWidth())); +} + +void SmTextNode::CreateTextFromNode(OUStringBuffer &rText) +{ + bool bQuoted=false; + if (GetToken().eType == TTEXT) + { + rText.append("\""); + bQuoted=true; + } + else + { + SmParser aParseTest; + auto pTable = aParseTest.Parse(GetToken().aText); + assert(pTable->GetType() == SmNodeType::Table); + bQuoted=true; + if (pTable->GetNumSubNodes() == 1) + { + SmNode *pResult = pTable->GetSubNode(0); + if ( (pResult->GetType() == SmNodeType::Line) && + (pResult->GetNumSubNodes() == 1) ) + { + pResult = pResult->GetSubNode(0); + if (pResult->GetType() == SmNodeType::Text) + bQuoted=false; + } + } + + if ((GetToken().eType == TIDENT) && (GetFontDesc() == FNT_FUNCTION)) + { + //Search for existing functions and remove extraneous keyword + rText.append("func "); + } + else if (bQuoted) + rText.append("italic "); + + if (bQuoted) + rText.append("\""); + + } + + rText.append(GetToken().aText); + + if (bQuoted) + rText.append("\""); + rText.append(" "); +} + +void SmTextNode::GetAccessibleText( OUStringBuffer &rText ) const +{ + rText.append(maText); +} + +void SmTextNode::AdjustFontDesc() +{ + if (GetToken().eType == TTEXT) + mnFontDesc = FNT_TEXT; + else if(GetToken().eType == TFUNC) + mnFontDesc = FNT_FUNCTION; + else { + SmTokenType nTok; + const SmTokenTableEntry *pEntry = SmParser::GetTokenTableEntry( maText ); + if (pEntry && pEntry->nGroup == TG::Function) { + nTok = pEntry->eType; + mnFontDesc = FNT_FUNCTION; + } else { + sal_Unicode firstChar = maText[0]; + if( ('0' <= firstChar && firstChar <= '9') || firstChar == '.' || firstChar == ',') { + mnFontDesc = FNT_NUMBER; + nTok = TNUMBER; + } else if (maText.getLength() > 1) { + mnFontDesc = FNT_VARIABLE; + nTok = TIDENT; + } else { + mnFontDesc = FNT_VARIABLE; + nTok = TCHARACTER; + } + } + SmToken tok = GetToken(); + tok.eType = nTok; + SetToken(tok); + } +} + +sal_Unicode SmTextNode::ConvertSymbolToUnicode(sal_Unicode nIn) +{ + //Find the best match in accepted unicode for our private area symbols + static const sal_Unicode aStarMathPrivateToUnicode[] = + { + 0x2030, 0xF613, 0xF612, 0x002B, 0x003C, 0x003E, 0xE425, 0xE421, 0xE088, 0x2208, + 0x0192, 0x2026, 0x2192, 0x221A, 0x221A, 0x221A, 0xE090, 0x005E, 0x02C7, 0x02D8, + 0x00B4, 0x0060, 0x02DC, 0x00AF, 0x0362, 0xE099, 0xE09A, 0x20DB, 0xE09C, 0xE09D, + 0x0028, 0x0029, 0x2220, 0x22AF, 0xE0A2, 0xE0A3, 0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, + 0x002F, 0x005C, 0x274F, 0xE0AB, 0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03A0, + 0x03a3, 0x03a5, 0x03a6, 0x03a8, 0x03A9, 0x03B1, 0x03B2, 0x03b3, 0x03b4, 0x03b5, + 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8, 0x03c9, 0x03b5, + 0x03d1, 0x03d6, 0xE0D2, 0x03db, 0x2118, 0x2202, 0x2129, 0xE0D7, 0xE0D8, 0x22A4, + 0xE0DA, 0x2190, 0x2191, 0x2193 + }; + if ((nIn >= 0xE080) && (nIn <= 0xE0DD)) + nIn = aStarMathPrivateToUnicode[nIn-0xE080]; + + //For whatever unicode glyph that equation editor doesn't ship with that + //we have a possible match we can munge it to. + switch (nIn) + { + case 0x2223: + nIn = '|'; + break; + default: + break; + } + + return nIn; +} + +/**************************************************************************/ + +void SmMatrixNode::CreateTextFromNode(OUStringBuffer &rText) +{ + rText.append("matrix {"); + for (size_t i = 0; i < mnNumRows; ++i) + { + for (size_t j = 0; j < mnNumCols; ++j) + { + SmNode *pNode = GetSubNode(i * mnNumCols + j); + if (pNode) + pNode->CreateTextFromNode(rText); + if (j != mnNumCols - 1U) + rText.append("# "); + } + if (i != mnNumRows - 1U) + rText.append("## "); + } + rText.stripEnd(' '); + rText.append("} "); +} + +void SmMatrixNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pNode; + + // initialize array that is to hold the maximum widths of all + // elements (subnodes) in that column. + std::vector<long> aColWidth(mnNumCols); + + // arrange subnodes and calculate the above arrays contents + size_t nNodes = GetNumSubNodes(); + for (size_t i = 0; i < nNodes; ++i) + { + size_t nIdx = nNodes - 1 - i; + if (nullptr != (pNode = GetSubNode(nIdx))) + { + pNode->Arrange(rDev, rFormat); + int nCol = nIdx % mnNumCols; + aColWidth[nCol] = std::max(aColWidth[nCol], pNode->GetItalicWidth()); + } + } + + // norm distance from which the following two are calculated + const long nNormDist = 3 * GetFont().GetFontSize().Height(); + + // define horizontal and vertical minimal distances that separate + // the elements + long nHorDist = nNormDist * rFormat.GetDistance(DIS_MATRIXCOL) / 100, + nVerDist = nNormDist * rFormat.GetDistance(DIS_MATRIXROW) / 100; + + // build array that holds the leftmost position for each column + std::vector<long> aColLeft(mnNumCols); + long nX = 0; + for (size_t j = 0; j < mnNumCols; ++j) + { + aColLeft[j] = nX; + nX += aColWidth[j] + nHorDist; + } + + SmRect::operator = (SmRect()); + for (size_t i = 0; i < mnNumRows; ++i) + { + Point aPos; + SmRect aLineRect; + for (size_t j = 0; j < mnNumCols; ++j) + { + SmNode *pTmpNode = GetSubNode(i * mnNumCols + j); + assert(pTmpNode); + + const SmRect &rNodeRect = pTmpNode->GetRect(); + + // align all baselines in that row if possible + aPos = rNodeRect.AlignTo(aLineRect, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + + // get horizontal alignment + const SmNode *pCoNode = pTmpNode->GetLeftMost(); + RectHorAlign eHorAlign = pCoNode->GetRectHorAlign(); + + // calculate horizontal position of element depending on column + // and horizontal alignment + switch (eHorAlign) + { case RectHorAlign::Left: + aPos.setX( aColLeft[j] ); + break; + case RectHorAlign::Center: + aPos.setX( rNodeRect.GetLeft() + aColLeft[j] + + aColWidth[j] / 2 + - rNodeRect.GetItalicCenterX() ); + break; + case RectHorAlign::Right: + aPos.setX( aColLeft[j] + + aColWidth[j] - rNodeRect.GetItalicWidth() ); + break; + default: + assert(false); + } + + pTmpNode->MoveTo(aPos); + aLineRect.ExtendBy(rNodeRect, RectCopyMBL::Xor); + } + + aPos = aLineRect.AlignTo(*this, RectPos::Bottom, RectHorAlign::Center, RectVerAlign::Baseline); + if (i > 0) + aPos.AdjustY(nVerDist ); + + // move 'aLineRect' and rectangles in that line to final position + Point aDelta(0, // since horizontal alignment is already done + aPos.Y() - aLineRect.GetTop()); + aLineRect.Move(aDelta); + for (size_t j = 0; j < mnNumCols; ++j) + { + if (nullptr != (pNode = GetSubNode(i * mnNumCols + j))) + pNode->Move(aDelta); + } + + ExtendBy(aLineRect, RectCopyMBL::None); + } +} + + +void SmMatrixNode::SetRowCol(sal_uInt16 nMatrixRows, sal_uInt16 nMatrixCols) +{ + mnNumRows = nMatrixRows; + mnNumCols = nMatrixCols; +} + + +const SmNode * SmMatrixNode::GetLeftMost() const +{ + return this; +} + + +/**************************************************************************/ + + +SmMathSymbolNode::SmMathSymbolNode(const SmToken &rNodeToken) +: SmSpecialNode(SmNodeType::Math, rNodeToken, FNT_MATH) +{ + sal_Unicode cChar = GetToken().cMathChar; + if (u'\0' != cChar) + SetText(OUString(cChar)); +} + +void SmMathSymbolNode::AdaptToX(OutputDevice &rDev, sal_uLong nWidth) +{ + // Since there is no function to do this, we try to approximate it: + Size aFntSize (GetFont().GetFontSize()); + + //! however the result is a bit better with 'nWidth' as initial font width + aFntSize.setWidth( nWidth ); + GetFont().SetSize(aFntSize); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + // get denominator of error factor for width + long nTmpBorderWidth = GetFont().GetBorderWidth(); + long nDenom = SmRect(aTmpDev, nullptr, GetText(), nTmpBorderWidth).GetItalicWidth(); + + // scale fontwidth with this error factor + aFntSize.setWidth( aFntSize.Width() * nWidth ); + aFntSize.setWidth( aFntSize.Width() / ( nDenom ? nDenom : 1) ); + + GetFont().SetSize(aFntSize); +} + +void SmMathSymbolNode::AdaptToY(OutputDevice &rDev, sal_uLong nHeight) +{ + GetFont().FreezeBorderWidth(); + Size aFntSize (GetFont().GetFontSize()); + + // Since we only want to scale the height, we might have + // to determine the font width in order to keep it + if (aFntSize.Width() == 0) + { + rDev.Push(PushFlags::FONT | PushFlags::MAPMODE); + rDev.SetFont(GetFont()); + aFntSize.setWidth( rDev.GetFontMetric().GetFontSize().Width() ); + rDev.Pop(); + } + OSL_ENSURE(aFntSize.Width() != 0, "Sm: "); + + //! however the result is a bit better with 'nHeight' as initial + //! font height + aFntSize.setHeight( nHeight ); + GetFont().SetSize(aFntSize); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + // get denominator of error factor for height + long nTmpBorderWidth = GetFont().GetBorderWidth(); + long nDenom = 0; + if (!GetText().isEmpty()) + nDenom = SmRect(aTmpDev, nullptr, GetText(), nTmpBorderWidth).GetHeight(); + + // scale fontwidth with this error factor + aFntSize.setHeight( aFntSize.Height() * nHeight ); + aFntSize.setHeight( aFntSize.Height() / ( nDenom ? nDenom : 1) ); + + GetFont().SetSize(aFntSize); +} + + +void SmMathSymbolNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + GetFont() = rFormat.GetFont(GetFontDesc()); + // use same font size as is used for variables + GetFont().SetSize( rFormat.GetFont( FNT_VARIABLE ).GetFontSize() ); + + OSL_ENSURE(GetFont().GetCharSet() == RTL_TEXTENCODING_SYMBOL || + GetFont().GetCharSet() == RTL_TEXTENCODING_UNICODE, + "wrong charset for character from StarMath/OpenSymbol font"); + + Flags() |= FontChangeMask::Face | FontChangeMask::Italic; +}; + + +void SmMathSymbolNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + const OUString &rText = GetText(); + + if (rText.isEmpty() || rText[0] == '\0') + { SmRect::operator = (SmRect()); + return; + } + + PrepareAttributes(); + + GetFont() *= Fraction (rFormat.GetRelSize(SIZ_TEXT), 100); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, rText, GetFont().GetBorderWidth())); +} + +void SmMathSymbolNode::CreateTextFromNode(OUStringBuffer &rText) +{ + sal_Unicode cChar = GetToken().cMathChar; + if (cChar == MS_INT && GetScaleMode() == SmScaleMode::Height) + rText.append("intd "); + else + MathType::LookupChar(cChar, rText, 3); +} + +void SmRectangleNode::CreateTextFromNode(OUStringBuffer &rText) +{ + switch (GetToken().eType) + { + case TUNDERLINE: + rText.append("underline "); + break; + case TOVERLINE: + rText.append("overline "); + break; + case TOVERSTRIKE: + rText.append("overstrike "); + break; + default: + break; + } +} + +void SmAttributNode::CreateTextFromNode(OUStringBuffer &rText) +{ + SmNode *pNode; + assert(GetNumSubNodes() == 2); + rText.append("{"); + sal_Unicode nLast=0; + if (nullptr != (pNode = Attribute())) + { + OUStringBuffer aStr; + pNode->CreateTextFromNode(aStr); + if (aStr.getLength() > 1) + rText.append(aStr); + else + { + nLast = aStr[0]; + switch (nLast) + { + case MS_BAR: // MACRON + rText.append("overline "); + break; + case MS_DOT: // DOT ABOVE + rText.append("dot "); + break; + case 0x2dc: // SMALL TILDE + rText.append("widetilde "); + break; + case MS_DDOT: // DIAERESIS + rText.append("ddot "); + break; + case 0xE082: + break; + case 0xE09B: + case MS_DDDOT: // COMBINING THREE DOTS ABOVE + rText.append("dddot "); + break; + case MS_ACUTE: // ACUTE ACCENT + case MS_COMBACUTE: // COMBINING ACUTE ACCENT + rText.append("acute "); + break; + case MS_GRAVE: // GRAVE ACCENT + case MS_COMBGRAVE: // COMBINING GRAVE ACCENT + rText.append("grave "); + break; + case MS_CHECK: // CARON + case MS_COMBCHECK: // COMBINING CARON + rText.append("check "); + break; + case MS_BREVE: // BREVE + case MS_COMBBREVE: // COMBINING BREVE + rText.append("breve "); + break; + case MS_CIRCLE: // RING ABOVE + case MS_COMBCIRCLE: // COMBINING RING ABOVE + rText.append("circle "); + break; + case MS_RIGHTARROW: // RIGHTWARDS ARROW + case MS_VEC: // COMBINING RIGHT ARROW ABOVE + rText.append("vec "); + break; + case MS_HARPOON: // COMBINING RIGHT HARPOON ABOVE + rText.append("harpoon "); + break; + case MS_TILDE: // TILDE + case MS_COMBTILDE: // COMBINING TILDE + rText.append("tilde "); + break; + case MS_HAT: // CIRCUMFLEX ACCENT + case MS_COMBHAT: // COMBINING CIRCUMFLEX ACCENT + rText.append("hat "); + break; + case MS_COMBBAR: // COMBINING MACRON + rText.append("bar "); + break; + default: + rText.append(OUStringChar(nLast)); + break; + } + } + } + + if (nullptr != (pNode = Body())) + pNode->CreateTextFromNode(rText); + + rText.stripEnd(' '); + + if (nLast == 0xE082) + rText.append(" overbrace {}"); + + rText.append("} "); +} + +/**************************************************************************/ + +static bool lcl_IsFromGreekSymbolSet( const OUString &rTokenText ) +{ + bool bRes = false; + + // valid symbol name needs to have a '%' at pos 0 and at least an additional char + if (rTokenText.getLength() > 2 && rTokenText[0] == u'%') + { + OUString aName( rTokenText.copy(1) ); + SmSym *pSymbol = SM_MOD()->GetSymbolManager().GetSymbolByName( aName ); + if (pSymbol && SmLocalizedSymbolData::GetExportSymbolSetName(pSymbol->GetSymbolSetName()) == "Greek") + bRes = true; + } + + return bRes; +} + + +SmSpecialNode::SmSpecialNode(SmNodeType eNodeType, const SmToken &rNodeToken, sal_uInt16 _nFontDesc) + : SmTextNode(eNodeType, rNodeToken, _nFontDesc) + , mbIsFromGreekSymbolSet(lcl_IsFromGreekSymbolSet( rNodeToken.aText )) +{ +} + + +SmSpecialNode::SmSpecialNode(const SmToken &rNodeToken) + : SmTextNode(SmNodeType::Special, rNodeToken, FNT_MATH) // default Font isn't always correct! + , mbIsFromGreekSymbolSet(lcl_IsFromGreekSymbolSet( rNodeToken.aText )) +{ +} + + +void SmSpecialNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + const SmSym *pSym; + SmModule *pp = SM_MOD(); + + OUString aName(GetToken().aText.copy(1)); + if (nullptr != (pSym = pp->GetSymbolManager().GetSymbolByName( aName ))) + { + sal_UCS4 cChar = pSym->GetCharacter(); + OUString aTmp( &cChar, 1 ); + SetText( aTmp ); + GetFont() = pSym->GetFace(); + } + else + { + SetText( GetToken().aText ); + GetFont() = rFormat.GetFont(FNT_VARIABLE); + } + // use same font size as is used for variables + GetFont().SetSize( rFormat.GetFont( FNT_VARIABLE ).GetFontSize() ); + + // Actually only WEIGHT_NORMAL and WEIGHT_BOLD should occur... However, the sms-file also + // contains e.g. 'WEIGHT_ULTRALIGHT'. Consequently, compare here with '>' instead of '!='. + // (In the long term the necessity for 'PrepareAttribut' and thus also for this here should be dropped) + + //! see also SmFontStyles::GetStyleName + if (IsItalic( GetFont() )) + SetAttribut(FontAttribute::Italic); + if (IsBold( GetFont() )) + SetAttribut(FontAttribute::Bold); + + Flags() |= FontChangeMask::Face; + + if (!mbIsFromGreekSymbolSet) + return; + + OSL_ENSURE( GetText().getLength() == 1, "a symbol should only consist of 1 char!" ); + bool bItalic = false; + sal_Int16 nStyle = rFormat.GetGreekCharStyle(); + OSL_ENSURE( nStyle >= 0 && nStyle <= 2, "unexpected value for GreekCharStyle" ); + if (nStyle == 1) + bItalic = true; + else if (nStyle == 2) + { + const OUString& rTmp(GetText()); + if (!rTmp.isEmpty()) + { + static const sal_Unicode cUppercaseAlpha = 0x0391; + static const sal_Unicode cUppercaseOmega = 0x03A9; + sal_Unicode cChar = rTmp[0]; + // uppercase letters should be straight and lowercase letters italic + bItalic = cUppercaseAlpha > cChar || cChar > cUppercaseOmega; + } + } + + if (bItalic) + Attributes() |= FontAttribute::Italic; + else + Attributes() &= ~FontAttribute::Italic; +}; + + +void SmSpecialNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, GetText(), GetFont().GetBorderWidth())); +} + +/**************************************************************************/ + + +void SmGlyphSpecialNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, GetText(), + GetFont().GetBorderWidth()).AsGlyphRect()); +} + + +/**************************************************************************/ + + +void SmPlaceNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + GetFont().SetColor(COL_GRAY); + Flags() |= FontChangeMask::Color | FontChangeMask::Face | FontChangeMask::Italic; +}; + + +void SmPlaceNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, GetText(), GetFont().GetBorderWidth())); +} + + +/**************************************************************************/ + + +void SmErrorNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + GetFont().SetColor(COL_RED); + Flags() |= FontChangeMask::Phantom | FontChangeMask::Bold | FontChangeMask::Italic + | FontChangeMask::Color | FontChangeMask::Face | FontChangeMask::Size; +} + + +void SmErrorNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + const OUString &rText = GetText(); + SmRect::operator = (SmRect(aTmpDev, &rFormat, rText, GetFont().GetBorderWidth())); +} + +/**************************************************************************/ + +void SmBlankNode::IncreaseBy(const SmToken &rToken, sal_uInt32 nMultiplyBy) +{ + switch(rToken.eType) + { + case TBLANK: mnNum += (4 * nMultiplyBy); break; + case TSBLANK: mnNum += (1 * nMultiplyBy); break; + default: + break; + } +} + +void SmBlankNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + // Here it need/should not be the StarMath font, so that for the character + // used in Arrange a normal (non-clipped) rectangle is generated + GetFont() = rFormat.GetFont(FNT_VARIABLE); + + Flags() |= FontChangeMask::Face | FontChangeMask::Bold | FontChangeMask::Italic; +} + + +void SmBlankNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + // make distance depend on the font height + // (so that it increases when scaling (e.g. size *2 {a ~ b}) + long nDist = GetFont().GetFontSize().Height() / 10, + nSpace = mnNum * nDist; + + // get a SmRect with Baseline and all the bells and whistles + SmRect::operator = (SmRect(aTmpDev, &rFormat, OUString(' '), + GetFont().GetBorderWidth())); + + // and resize it to the requested size + SetItalicSpaces(0, 0); + SetWidth(nSpace); +} + +void SmBlankNode::CreateTextFromNode(OUStringBuffer &rText) +{ + if (mnNum <= 0) + return; + sal_uInt16 nWide = mnNum / 4; + sal_uInt16 nNarrow = mnNum % 4; + for (sal_uInt16 i = 0; i < nWide; i++) + rText.append("~"); + for (sal_uInt16 i = 0; i < nNarrow; i++) + rText.append("`"); + rText.append(" "); +} + + +/**************************************************************************/ +//Implementation of all accept methods for SmVisitor + +void SmTableNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBraceNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBracebodyNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmOperNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmAlignNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmAttributNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmFontNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmUnHorNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBinHorNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBinVerNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBinDiagonalNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmSubSupNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmMatrixNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmPlaceNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmTextNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmSpecialNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmGlyphSpecialNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmMathSymbolNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBlankNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmErrorNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmLineNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmExpressionNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmPolyLineNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmRootNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmRootSymbolNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmRectangleNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmVerticalBraceNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ooxmlexport.cxx b/starmath/source/ooxmlexport.cxx new file mode 100644 index 000000000..fff9cd7c5 --- /dev/null +++ b/starmath/source/ooxmlexport.cxx @@ -0,0 +1,600 @@ +/* -*- 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 "ooxmlexport.hxx" +#include <node.hxx> + +#include <oox/token/tokens.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <oox/mathml/export.hxx> + +using namespace oox; +using namespace oox::core; + +SmOoxmlExport::SmOoxmlExport(const SmNode *const pIn, OoxmlVersion const v, + drawingml::DocumentType const documentType) +: SmWordExportBase( pIn ) +, version( v ) +, m_DocumentType(documentType) +{ +} + +void SmOoxmlExport::ConvertFromStarMath( const ::sax_fastparser::FSHelperPtr& serializer, const sal_Int8 nAlign ) +{ + if( m_pTree == nullptr ) + return; + m_pSerializer = serializer; + + //Formula alignment situations: + // + // 1)Inline(as before): + // + // <m:oMath> + // <m:r> ... </m:r> + // </m:oMath> + // + // 2)Aligned: + // + // <m:oMathPara> + // <m:oMathParaPr> + // <m:jc m:val="left|right|center"> + // </m:oMathParaPr> + // <m:oMath> + // <m:r> ... </m:r> + // </m:oMath> + // </m:oMathPara> + + if (nAlign != FormulaExportBase::eFormulaAlign::INLINE) + { + m_pSerializer->startElementNS(XML_m, XML_oMathPara, + FSNS(XML_xmlns, XML_m), "http://schemas.openxmlformats.org/officeDocument/2006/math"); + m_pSerializer->startElementNS(XML_m, XML_oMathParaPr); + if (nAlign == FormulaExportBase::eFormulaAlign::CENTER) + m_pSerializer->singleElementNS(XML_m, XML_jc, FSNS(XML_m, XML_val), "center"); + if (nAlign == FormulaExportBase::eFormulaAlign::GROUPEDCENTER) + m_pSerializer->singleElementNS(XML_m, XML_jc, FSNS(XML_m, XML_val), "center"); + if (nAlign == FormulaExportBase::eFormulaAlign::LEFT) + m_pSerializer->singleElementNS(XML_m, XML_jc, FSNS(XML_m, XML_val), "left"); + if (nAlign == FormulaExportBase::eFormulaAlign::RIGHT) + m_pSerializer->singleElementNS(XML_m, XML_jc, FSNS(XML_m, XML_val), "right"); + m_pSerializer->endElementNS(XML_m, XML_oMathParaPr); + m_pSerializer->startElementNS(XML_m, XML_oMath); + HandleNode(m_pTree, 0); + m_pSerializer->endElementNS(XML_m, XML_oMath); + m_pSerializer->endElementNS(XML_m, XML_oMathPara); + } + else //else, inline as was before + { + m_pSerializer->startElementNS(XML_m, XML_oMath, + FSNS(XML_xmlns, XML_m), "http://schemas.openxmlformats.org/officeDocument/2006/math"); + HandleNode( m_pTree, 0 ); + m_pSerializer->endElementNS( XML_m, XML_oMath ); + } +} + +// NOTE: This is still work in progress and unfinished, but it already covers a good +// part of the ooxml math stuff. + +void SmOoxmlExport::HandleVerticalStack( const SmNode* pNode, int nLevel ) +{ + m_pSerializer->startElementNS(XML_m, XML_eqArr); + int size = pNode->GetNumSubNodes(); + for( int i = 0; + i < size; + ++i ) + { + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->GetSubNode( i ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + } + m_pSerializer->endElementNS( XML_m, XML_eqArr ); +} + +void SmOoxmlExport::HandleText( const SmNode* pNode, int /*nLevel*/) +{ + m_pSerializer->startElementNS(XML_m, XML_r); + + if( pNode->GetToken().eType == TTEXT ) // literal text (in quotes) + { + m_pSerializer->startElementNS(XML_m, XML_rPr); + m_pSerializer->singleElementNS(XML_m, XML_lit); + m_pSerializer->singleElementNS(XML_m, XML_nor); + m_pSerializer->endElementNS( XML_m, XML_rPr ); + } + if (drawingml::DOCUMENT_DOCX == m_DocumentType && ECMA_DIALECT == version) + { // HACK: MSOffice2007 does not import characters properly unless this font is explicitly given + m_pSerializer->startElementNS(XML_w, XML_rPr); + m_pSerializer->singleElementNS( XML_w, XML_rFonts, FSNS( XML_w, XML_ascii ), "Cambria Math", + FSNS( XML_w, XML_hAnsi ), "Cambria Math" ); + m_pSerializer->endElementNS( XML_w, XML_rPr ); + } + m_pSerializer->startElementNS(XML_m, XML_t, FSNS(XML_xml, XML_space), "preserve"); + const SmTextNode* pTemp = static_cast<const SmTextNode* >(pNode); + SAL_INFO( "starmath.ooxml", "Text:" << pTemp->GetText()); + OUStringBuffer buf(pTemp->GetText()); + for(sal_Int32 i=0;i<pTemp->GetText().getLength();i++) + { +#if 0 + if ((nPendingAttributes) && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + *pS << sal_uInt8(0x22); //char, with attributes right + //after the character + } + else + *pS << sal_uInt8(CHAR); + + sal_uInt8 nFace = 0x1; + if (pNode->GetFont().GetItalic() == ITALIC_NORMAL) + nFace = 0x3; + else if (pNode->GetFont().GetWeight() == WEIGHT_BOLD) + nFace = 0x7; + *pS << sal_uInt8(nFace+128); //typeface +#endif + buf[i] = SmTextNode::ConvertSymbolToUnicode(buf[i]); +#if 0 + //Mathtype can only have these sort of character + //attributes on a single character, starmath can put them + //anywhere, when the entity involved is a text run this is + //a large effort to place the character attribute on the + //central mathtype character so that it does pretty much + //what the user probably has in mind. The attributes + //filled in here are dummy ones which are replaced in the + //ATTRIBUT handler if a suitable location for the + //attributes was found here. Unfortunately it is + //possible for starmath to place character attributes on + //entities which cannot occur in mathtype e.g. a Summation + //symbol so these attributes may be lost + if ((nPendingAttributes) && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + *pS << sal_uInt8(EMBEL); + while (nPendingAttributes) + { + *pS << sal_uInt8(2); + //wedge the attributes in here and clear + //the pending stack + nPendingAttributes--; + } + nInsertion=pS->Tell(); + *pS << sal_uInt8(END); //end embel + *pS << sal_uInt8(END); //end embel + } +#endif + } + m_pSerializer->writeEscaped(buf.makeStringAndClear()); + m_pSerializer->endElementNS( XML_m, XML_t ); + m_pSerializer->endElementNS( XML_m, XML_r ); +} + +void SmOoxmlExport::HandleFractions( const SmNode* pNode, int nLevel, const char* type ) +{ + m_pSerializer->startElementNS(XML_m, XML_f); + if( type != nullptr ) + { + m_pSerializer->startElementNS(XML_m, XML_fPr); + m_pSerializer->singleElementNS(XML_m, XML_type, FSNS(XML_m, XML_val), type); + m_pSerializer->endElementNS( XML_m, XML_fPr ); + } + assert( pNode->GetNumSubNodes() == 3 ); + m_pSerializer->startElementNS(XML_m, XML_num); + HandleNode( pNode->GetSubNode( 0 ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_num ); + m_pSerializer->startElementNS(XML_m, XML_den); + HandleNode( pNode->GetSubNode( 2 ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_den ); + m_pSerializer->endElementNS( XML_m, XML_f ); +} + +void SmOoxmlExport::HandleAttribute( const SmAttributNode* pNode, int nLevel ) +{ + switch( pNode->Attribute()->GetToken().eType ) + { + case TCHECK: + case TACUTE: + case TGRAVE: + case TBREVE: + case TCIRCLE: + case TVEC: + case TTILDE: + case THAT: + case TDOT: + case TDDOT: + case TDDDOT: + case TWIDETILDE: + case TWIDEHAT: + case TWIDEHARPOON: + case TWIDEVEC: + case TBAR: + { + m_pSerializer->startElementNS(XML_m, XML_acc); + m_pSerializer->startElementNS(XML_m, XML_accPr); + OString value = OUStringToOString( + OUString( pNode->Attribute()->GetToken().cMathChar ), RTL_TEXTENCODING_UTF8 ); + m_pSerializer->singleElementNS(XML_m, XML_chr, FSNS(XML_m, XML_val), value); + m_pSerializer->endElementNS( XML_m, XML_accPr ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_acc ); + break; + } + case TOVERLINE: + case TUNDERLINE: + m_pSerializer->startElementNS(XML_m, XML_bar); + m_pSerializer->startElementNS(XML_m, XML_barPr); + m_pSerializer->singleElementNS( XML_m, XML_pos, FSNS( XML_m, XML_val ), + ( pNode->Attribute()->GetToken().eType == TUNDERLINE ) ? "bot" : "top" ); + m_pSerializer->endElementNS( XML_m, XML_barPr ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_bar ); + break; + case TOVERSTRIKE: + m_pSerializer->startElementNS(XML_m, XML_borderBox); + m_pSerializer->startElementNS(XML_m, XML_borderBoxPr); + m_pSerializer->singleElementNS(XML_m, XML_hideTop, FSNS(XML_m, XML_val), "1"); + m_pSerializer->singleElementNS(XML_m, XML_hideBot, FSNS(XML_m, XML_val), "1"); + m_pSerializer->singleElementNS(XML_m, XML_hideLeft, FSNS(XML_m, XML_val), "1"); + m_pSerializer->singleElementNS(XML_m, XML_hideRight, FSNS(XML_m, XML_val), "1"); + m_pSerializer->singleElementNS(XML_m, XML_strikeH, FSNS(XML_m, XML_val), "1"); + m_pSerializer->endElementNS( XML_m, XML_borderBoxPr ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_borderBox ); + break; + default: + HandleAllSubNodes( pNode, nLevel ); + break; + } +} + +void SmOoxmlExport::HandleRoot( const SmRootNode* pNode, int nLevel ) +{ + m_pSerializer->startElementNS(XML_m, XML_rad); + if( const SmNode* argument = pNode->Argument()) + { + m_pSerializer->startElementNS(XML_m, XML_deg); + HandleNode( argument, nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_deg ); + } + else + { + m_pSerializer->startElementNS(XML_m, XML_radPr); + m_pSerializer->singleElementNS(XML_m, XML_degHide, FSNS(XML_m, XML_val), "1"); + m_pSerializer->endElementNS( XML_m, XML_radPr ); + m_pSerializer->singleElementNS(XML_m, XML_deg); // empty but present + } + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_rad ); +} + +static OString mathSymbolToString( const SmNode* node ) +{ + assert( node->GetType() == SmNodeType::Math || node->GetType() == SmNodeType::MathIdent ); + const SmTextNode* txtnode = static_cast< const SmTextNode* >( node ); + assert( txtnode->GetText().getLength() == 1 ); + sal_Unicode chr = SmTextNode::ConvertSymbolToUnicode( txtnode->GetText()[0] ); + return OUStringToOString( OUString( chr ), RTL_TEXTENCODING_UTF8 ); +} + +void SmOoxmlExport::HandleOperator( const SmOperNode* pNode, int nLevel ) +{ + SAL_INFO( "starmath.ooxml", "Operator: " << int( pNode->GetToken().eType )); + switch( pNode->GetToken().eType ) + { + case TINT: + case TINTD: + case TIINT: + case TIIINT: + case TLINT: + case TLLINT: + case TLLLINT: + case TPROD: + case TCOPROD: + case TSUM: + { + const SmSubSupNode* subsup = pNode->GetSubNode( 0 )->GetType() == SmNodeType::SubSup + ? static_cast< const SmSubSupNode* >( pNode->GetSubNode( 0 )) : nullptr; + const SmNode* operation = subsup != nullptr ? subsup->GetBody() : pNode->GetSubNode( 0 ); + m_pSerializer->startElementNS(XML_m, XML_nary); + m_pSerializer->startElementNS(XML_m, XML_naryPr); + m_pSerializer->singleElementNS( XML_m, XML_chr, + FSNS( XML_m, XML_val ), mathSymbolToString(operation) ); + if( subsup == nullptr || subsup->GetSubSup( CSUB ) == nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_subHide, FSNS(XML_m, XML_val), "1"); + if( subsup == nullptr || subsup->GetSubSup( CSUP ) == nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_supHide, FSNS(XML_m, XML_val), "1"); + m_pSerializer->endElementNS( XML_m, XML_naryPr ); + if( subsup == nullptr || subsup->GetSubSup( CSUB ) == nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_sub); + else + { + m_pSerializer->startElementNS(XML_m, XML_sub); + HandleNode( subsup->GetSubSup( CSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sub ); + } + if( subsup == nullptr || subsup->GetSubSup( CSUP ) == nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_sup); + else + { + m_pSerializer->startElementNS(XML_m, XML_sup); + HandleNode( subsup->GetSubSup( CSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sup ); + } + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->GetSubNode( 1 ), nLevel + 1 ); // body + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_nary ); + break; + } + case TLIM: + m_pSerializer->startElementNS(XML_m, XML_func); + m_pSerializer->startElementNS(XML_m, XML_fName); + m_pSerializer->startElementNS(XML_m, XML_limLow); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->GetSymbol(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_lim); + if( const SmSubSupNode* subsup = pNode->GetSubNode( 0 )->GetType() == SmNodeType::SubSup + ? static_cast< const SmSubSupNode* >( pNode->GetSubNode( 0 )) : nullptr ) + { + if( subsup->GetSubSup( CSUB ) != nullptr ) + HandleNode( subsup->GetSubSup( CSUB ), nLevel + 1 ); + } + m_pSerializer->endElementNS( XML_m, XML_lim ); + m_pSerializer->endElementNS( XML_m, XML_limLow ); + m_pSerializer->endElementNS( XML_m, XML_fName ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->GetSubNode( 1 ), nLevel + 1 ); // body + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_func ); + break; + default: + SAL_WARN("starmath.ooxml", "Unhandled operation"); + HandleAllSubNodes( pNode, nLevel ); + break; + } +} + +void SmOoxmlExport::HandleSubSupScriptInternal( const SmSubSupNode* pNode, int nLevel, int flags ) +{ +// docx supports only a certain combination of sub/super scripts, but LO can have any, +// so try to merge it using several tags if necessary + if( flags == 0 ) // none + return; + if(( flags & ( 1 << RSUP | 1 << RSUB )) == ( 1 << RSUP | 1 << RSUB )) + { // m:sSubSup + m_pSerializer->startElementNS(XML_m, XML_sSubSup); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << RSUP | 1 << RSUB ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_sub); + HandleNode( pNode->GetSubSup( RSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sub ); + m_pSerializer->startElementNS(XML_m, XML_sup); + HandleNode( pNode->GetSubSup( RSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sup ); + m_pSerializer->endElementNS( XML_m, XML_sSubSup ); + } + else if(( flags & ( 1 << RSUB )) == 1 << RSUB ) + { // m:sSub + m_pSerializer->startElementNS(XML_m, XML_sSub); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << RSUB ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_sub); + HandleNode( pNode->GetSubSup( RSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sub ); + m_pSerializer->endElementNS( XML_m, XML_sSub ); + } + else if(( flags & ( 1 << RSUP )) == 1 << RSUP ) + { // m:sSup + m_pSerializer->startElementNS(XML_m, XML_sSup); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << RSUP ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_sup); + HandleNode( pNode->GetSubSup( RSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sup ); + m_pSerializer->endElementNS( XML_m, XML_sSup ); + } + else if(( flags & ( 1 << LSUP | 1 << LSUB )) == ( 1 << LSUP | 1 << LSUB )) + { // m:sPre + m_pSerializer->startElementNS(XML_m, XML_sPre); + m_pSerializer->startElementNS(XML_m, XML_sub); + HandleNode( pNode->GetSubSup( LSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sub ); + m_pSerializer->startElementNS(XML_m, XML_sup); + HandleNode( pNode->GetSubSup( LSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sup ); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << LSUP | 1 << LSUB ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_sPre ); + } + else if(( flags & ( 1 << CSUB )) == ( 1 << CSUB )) + { // m:limLow looks like a good element for central superscript + m_pSerializer->startElementNS(XML_m, XML_limLow); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << CSUB ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_lim); + HandleNode( pNode->GetSubSup( CSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_lim ); + m_pSerializer->endElementNS( XML_m, XML_limLow ); + } + else if(( flags & ( 1 << CSUP )) == ( 1 << CSUP )) + { // m:limUpp looks like a good element for central superscript + m_pSerializer->startElementNS(XML_m, XML_limUpp); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << CSUP ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_lim); + HandleNode( pNode->GetSubSup( CSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_lim ); + m_pSerializer->endElementNS( XML_m, XML_limUpp ); + } + else + { + SAL_WARN("starmath.ooxml", "Unhandled sub/sup combination"); + // TODO do not do anything, this should be probably an assert() + // HandleAllSubNodes( pNode, nLevel ); + } +} + +void SmOoxmlExport::HandleMatrix( const SmMatrixNode* pNode, int nLevel ) +{ + m_pSerializer->startElementNS(XML_m, XML_m); + for (size_t row = 0; row < pNode->GetNumRows(); ++row) + { + m_pSerializer->startElementNS(XML_m, XML_mr); + for (size_t col = 0; col < pNode->GetNumCols(); ++col) + { + m_pSerializer->startElementNS(XML_m, XML_e); + if( const SmNode* node = pNode->GetSubNode( row * pNode->GetNumCols() + col )) + HandleNode( node, nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + } + m_pSerializer->endElementNS( XML_m, XML_mr ); + } + m_pSerializer->endElementNS( XML_m, XML_m ); +} + +void SmOoxmlExport::HandleBrace( const SmBraceNode* pNode, int nLevel ) +{ + m_pSerializer->startElementNS(XML_m, XML_d); + m_pSerializer->startElementNS(XML_m, XML_dPr); + + //check if the node has an opening brace + if( TNONE == pNode->OpeningBrace()->GetToken().eType ) + m_pSerializer->singleElementNS(XML_m, XML_begChr, FSNS(XML_m, XML_val), ""); + else + m_pSerializer->singleElementNS( XML_m, XML_begChr, + FSNS( XML_m, XML_val ), mathSymbolToString( pNode->OpeningBrace()) ); + + std::vector< const SmNode* > subnodes; + if( pNode->Body()->GetType() == SmNodeType::Bracebody ) + { + const SmBracebodyNode* body = static_cast< const SmBracebodyNode* >( pNode->Body()); + bool separatorWritten = false; // assume all separators are the same + for (size_t i = 0; i < body->GetNumSubNodes(); ++i) + { + const SmNode* subnode = body->GetSubNode( i ); + if (subnode->GetType() == SmNodeType::Math || subnode->GetType() == SmNodeType::MathIdent) + { // do not write, but write what separator it is + const SmMathSymbolNode* math = static_cast< const SmMathSymbolNode* >( subnode ); + if( !separatorWritten ) + { + m_pSerializer->singleElementNS( XML_m, XML_sepChr, + FSNS( XML_m, XML_val ), mathSymbolToString(math) ); + separatorWritten = true; + } + } + else + subnodes.push_back( subnode ); + } + } + else + subnodes.push_back( pNode->Body()); + + if( TNONE == pNode->ClosingBrace()->GetToken().eType ) + m_pSerializer->singleElementNS(XML_m, XML_endChr, FSNS(XML_m, XML_val), ""); + else + m_pSerializer->singleElementNS( XML_m, XML_endChr, + FSNS( XML_m, XML_val ), mathSymbolToString(pNode->ClosingBrace()) ); + + m_pSerializer->endElementNS( XML_m, XML_dPr ); + for(const SmNode* subnode : subnodes) + { + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( subnode, nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + } + m_pSerializer->endElementNS( XML_m, XML_d ); +} + +void SmOoxmlExport::HandleVerticalBrace( const SmVerticalBraceNode* pNode, int nLevel ) +{ + SAL_INFO( "starmath.ooxml", "Vertical: " << int( pNode->GetToken().eType )); + switch( pNode->GetToken().eType ) + { + case TOVERBRACE: + case TUNDERBRACE: + { + bool top = ( pNode->GetToken().eType == TOVERBRACE ); + m_pSerializer->startElementNS(XML_m, top ? XML_limUpp : XML_limLow); + m_pSerializer->startElementNS(XML_m, XML_e); + m_pSerializer->startElementNS(XML_m, XML_groupChr); + m_pSerializer->startElementNS(XML_m, XML_groupChrPr); + m_pSerializer->singleElementNS( XML_m, XML_chr, + FSNS( XML_m, XML_val ), mathSymbolToString(pNode->Brace()) ); + // TODO not sure if pos and vertJc are correct + m_pSerializer->singleElementNS( XML_m, XML_pos, + FSNS( XML_m, XML_val ), top ? "top" : "bot" ); + m_pSerializer->singleElementNS(XML_m, XML_vertJc, FSNS(XML_m, XML_val), + top ? "bot" : "top"); + m_pSerializer->endElementNS( XML_m, XML_groupChrPr ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_groupChr ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_lim); + HandleNode( pNode->Script(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_lim ); + m_pSerializer->endElementNS( XML_m, top ? XML_limUpp : XML_limLow ); + break; + } + default: + SAL_WARN("starmath.ooxml", "Unhandled vertical brace"); + HandleAllSubNodes( pNode, nLevel ); + break; + } +} + +void SmOoxmlExport::HandleBlank() +{ + m_pSerializer->startElementNS(XML_m, XML_r); + m_pSerializer->startElementNS(XML_m, XML_t, FSNS(XML_xml, XML_space), "preserve"); + m_pSerializer->write( " " ); + m_pSerializer->endElementNS( XML_m, XML_t ); + m_pSerializer->endElementNS( XML_m, XML_r ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ooxmlexport.hxx b/starmath/source/ooxmlexport.hxx new file mode 100644 index 000000000..fec33ab8e --- /dev/null +++ b/starmath/source/ooxmlexport.hxx @@ -0,0 +1,49 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_STARMATH_SOURCE_OOXMLEXPORT_HXX +#define INCLUDED_STARMATH_SOURCE_OOXMLEXPORT_HXX + +#include "wordexportbase.hxx" + +#include <sax/fshelper.hxx> +#include <oox/core/filterbase.hxx> +#include <oox/export/utils.hxx> + +/** + Class implementing writing of formulas to OOXML. + */ +class SmOoxmlExport : public SmWordExportBase +{ +public: + SmOoxmlExport(const SmNode* pIn, oox::core::OoxmlVersion version, + oox::drawingml::DocumentType documentType); + void ConvertFromStarMath( const ::sax_fastparser::FSHelperPtr& m_pSerializer, const sal_Int8 ); +private: + void HandleVerticalStack( const SmNode* pNode, int nLevel ) override; + void HandleText( const SmNode* pNode, int nLevel ) override; + void HandleFractions( const SmNode* pNode, int nLevel, const char* type ) override; + void HandleRoot( const SmRootNode* pNode, int nLevel ) override; + void HandleAttribute( const SmAttributNode* pNode, int nLevel ) override; + void HandleOperator( const SmOperNode* pNode, int nLevel ) override; + void HandleSubSupScriptInternal( const SmSubSupNode* pNode, int nLevel, int flags ) override; + void HandleMatrix( const SmMatrixNode* pNode, int nLevel ) override; + void HandleBrace( const SmBraceNode* pNode, int nLevel ) override; + void HandleVerticalBrace( const SmVerticalBraceNode* pNode, int nLevel ) override; + void HandleBlank() override; + ::sax_fastparser::FSHelperPtr m_pSerializer; + oox::core::OoxmlVersion version; + /// needed to determine markup for nested run properties + oox::drawingml::DocumentType const m_DocumentType; +}; + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ooxmlimport.cxx b/starmath/source/ooxmlimport.cxx new file mode 100644 index 000000000..034919787 --- /dev/null +++ b/starmath/source/ooxmlimport.cxx @@ -0,0 +1,681 @@ +/* -*- 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 "ooxmlimport.hxx" +#include <types.hxx> + +#include <oox/mathml/importutils.hxx> +#include <oox/token/namespaces.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +using namespace oox::formulaimport; + +/* +The primary internal data structure for the formula is the text representation +(the SmNode tree is built from it), so read data must be converted into this format. +*/ + +#define OPENING( token ) XML_STREAM_OPENING( token ) +#define CLOSING( token ) XML_STREAM_CLOSING( token ) + +// TODO create IS_OPENING(), IS_CLOSING() instead of doing 'next == OPENING( next )' ? + +SmOoxmlImport::SmOoxmlImport( oox::formulaimport::XmlStream& s ) + : m_rStream( s ) +{ +} + +OUString SmOoxmlImport::ConvertToStarMath() +{ + return handleStream(); +} + +// "toplevel" of reading, there will be oMath (if there was oMathPara, that was +// up to the parent component to handle) + +// NOT complete +OUString SmOoxmlImport::handleStream() +{ + m_rStream.ensureOpeningTag( M_TOKEN( oMath )); + OUStringBuffer ret; + while( !m_rStream.atEnd() && m_rStream.currentToken() != CLOSING( M_TOKEN( oMath ))) + { + // strictly speaking, it is not OMathArg here, but currently supported + // functionality is the same like OMathArg, in the future this may need improving + OUString item = readOMathArg( M_TOKEN( oMath )); + if( item.isEmpty()) + continue; + if( !ret.isEmpty()) + ret.append(" "); + ret.append(item); + } + m_rStream.ensureClosingTag( M_TOKEN( oMath )); + // Placeholders are written out as nothing (i.e. nothing inside e.g. the <e> element), + // which will result in "{}" in the formula text. Fix this up. + OUString ret2 = ret.makeStringAndClear().replaceAll( "{}", "<?>" ); + // And as a result, empty parts of the formula that are not placeholders are written out + // as a single space, so fix that up too. + ret2 = ret2.replaceAll( "{ }", "{}" ); + SAL_INFO( "starmath.ooxml", "Formula: " << ret2 ); + return ret2; +} + +OUString SmOoxmlImport::readOMathArg( int stoptoken ) +{ + OUStringBuffer ret; + while( !m_rStream.atEnd() && m_rStream.currentToken() != CLOSING( stoptoken )) + { + if( !ret.isEmpty()) + ret.append(" "); + switch( m_rStream.currentToken()) + { + case OPENING( M_TOKEN( acc )): + ret.append(handleAcc()); + break; + case OPENING( M_TOKEN( bar )): + ret.append(handleBar()); + break; + case OPENING( M_TOKEN( box )): + ret.append(handleBox()); + break; + case OPENING( M_TOKEN( borderBox )): + ret.append(handleBorderBox()); + break; + case OPENING( M_TOKEN( d )): + ret.append(handleD()); + break; + case OPENING( M_TOKEN( eqArr )): + ret.append(handleEqArr()); + break; + case OPENING( M_TOKEN( f )): + ret.append(handleF()); + break; + case OPENING( M_TOKEN( func )): + ret.append(handleFunc()); + break; + case OPENING( M_TOKEN( limLow )): + ret.append(handleLimLowUpp( LimLow )); + break; + case OPENING( M_TOKEN( limUpp )): + ret.append(handleLimLowUpp( LimUpp )); + break; + case OPENING( M_TOKEN( groupChr )): + ret.append(handleGroupChr()); + break; + case OPENING( M_TOKEN( m )): + ret.append(handleM()); + break; + case OPENING( M_TOKEN( nary )): + ret.append(handleNary()); + break; + case OPENING( M_TOKEN( r )): + ret.append(handleR()); + break; + case OPENING( M_TOKEN( rad )): + ret.append(handleRad()); + break; + case OPENING( M_TOKEN( sPre )): + ret.append(handleSpre()); + break; + case OPENING( M_TOKEN( sSub )): + ret.append(handleSsub()); + break; + case OPENING( M_TOKEN( sSubSup )): + ret.append(handleSsubsup()); + break; + case OPENING( M_TOKEN( sSup )): + ret.append(handleSsup()); + break; + default: + m_rStream.handleUnexpectedTag(); + break; + } + } + return ret.makeStringAndClear(); +} + +OUString SmOoxmlImport::readOMathArgInElement( int token ) +{ + m_rStream.ensureOpeningTag( token ); + OUString ret = readOMathArg( token ); + m_rStream.ensureClosingTag( token ); + return ret; +} + +OUString SmOoxmlImport::handleAcc() +{ + m_rStream.ensureOpeningTag( M_TOKEN( acc )); + sal_Unicode accChr = 0x302; + if( XmlStream::Tag accPr = m_rStream.checkOpeningTag( M_TOKEN( accPr ))) + { + if( XmlStream::Tag chr = m_rStream.checkOpeningTag( M_TOKEN( chr ))) + { + accChr = chr.attribute( M_TOKEN( val ), accChr ); + m_rStream.ensureClosingTag( M_TOKEN( chr )); + } + m_rStream.ensureClosingTag( M_TOKEN( accPr )); + } + // see aTokenTable in parse.cxx + OUString acc; + switch( accChr ) + { + case MS_BAR: + case MS_COMBBAR: + acc = "bar"; + break; + case MS_CHECK: + case MS_COMBCHECK: + acc = "check"; + break; + case MS_ACUTE: + case MS_COMBACUTE: + acc = "acute"; + break; + case MS_COMBOVERLINE: + acc = "overline"; + break; + case MS_GRAVE: + case MS_COMBGRAVE: + acc = "grave"; + break; + case MS_BREVE: + case MS_COMBBREVE: + acc = "breve"; + break; + case MS_CIRCLE: + case MS_COMBCIRCLE: + acc = "circle"; + break; + case MS_RIGHTARROW: + case MS_VEC: + // prefer wide variants for these 3, .docx can't seem to differentiate + // between e.g. 'vec' and 'widevec', if whatever the accent is above is short, this + // shouldn't matter, but short above a longer expression doesn't look right + acc = "widevec"; + break; + case MS_HARPOON: + acc = "wideharpoon"; + break; + case MS_TILDE: + case MS_COMBTILDE: + acc = "widetilde"; + break; + case MS_HAT: + case MS_COMBHAT: + acc = "widehat"; + break; + case MS_DOT: + case MS_COMBDOT: + acc = "dot"; + break; + case MS_DDOT: + case MS_COMBDDOT: + acc = "ddot"; + break; + case MS_DDDOT: + acc = "dddot"; + break; + default: + acc = "acute"; + SAL_WARN( "starmath.ooxml", "Unknown m:chr in m:acc \'" << OUString(accChr) << "\'" ); + break; + } + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( acc )); + return acc + " {" + e + "}"; +} + +OUString SmOoxmlImport::handleBar() +{ + m_rStream.ensureOpeningTag( M_TOKEN( bar )); + enum pos_t { top, bot } topbot = bot; + if( m_rStream.checkOpeningTag( M_TOKEN( barPr ))) + { + if( XmlStream::Tag pos = m_rStream.checkOpeningTag( M_TOKEN( pos ))) + { + if( pos.attribute( M_TOKEN( val )) == "top" ) + topbot = top; + else if( pos.attribute( M_TOKEN( val )) == "bot" ) + topbot = bot; + m_rStream.ensureClosingTag( M_TOKEN( pos )); + } + m_rStream.ensureClosingTag( M_TOKEN( barPr )); + } + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( bar )); + if( topbot == top ) + return "overline {" + e + "}"; + else + return "underline {" + e + "}"; +} + +OUString SmOoxmlImport::handleBox() +{ + // there does not seem to be functionality in LO to actually implement this + // (or is there), but at least read in the contents instead of ignoring them + m_rStream.ensureOpeningTag( M_TOKEN( box )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( box )); + return e; +} + + +OUString SmOoxmlImport::handleBorderBox() +{ + m_rStream.ensureOpeningTag( M_TOKEN( borderBox )); + bool isStrikeH = false; + if( m_rStream.checkOpeningTag( M_TOKEN( borderBoxPr ))) + { + if( XmlStream::Tag strikeH = m_rStream.checkOpeningTag( M_TOKEN( strikeH ))) + { + if( strikeH.attribute( M_TOKEN( val ), false )) + isStrikeH = true; + m_rStream.ensureClosingTag( M_TOKEN( strikeH )); + } + m_rStream.ensureClosingTag( M_TOKEN( borderBoxPr )); + } + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( borderBox )); + if( isStrikeH ) + return "overstrike {" + e + "}"; + // LO does not seem to implement anything for handling the other cases + return e; +} + +OUString SmOoxmlImport::handleD() +{ + m_rStream.ensureOpeningTag( M_TOKEN( d )); + OUString opening = "("; + OUString closing = ")"; + OUString separator = "|"; + if( XmlStream::Tag dPr = m_rStream.checkOpeningTag( M_TOKEN( dPr ))) + { + if( XmlStream::Tag begChr = m_rStream.checkOpeningTag( M_TOKEN( begChr ))) + { + opening = begChr.attribute( M_TOKEN( val ), opening ); + m_rStream.ensureClosingTag( M_TOKEN( begChr )); + } + if( XmlStream::Tag sepChr = m_rStream.checkOpeningTag( M_TOKEN( sepChr ))) + { + separator = sepChr.attribute( M_TOKEN( val ), separator ); + m_rStream.ensureClosingTag( M_TOKEN( sepChr )); + } + if( XmlStream::Tag endChr = m_rStream.checkOpeningTag( M_TOKEN( endChr ))) + { + closing = endChr.attribute( M_TOKEN( val ), closing ); + m_rStream.ensureClosingTag( M_TOKEN( endChr )); + } + m_rStream.ensureClosingTag( M_TOKEN( dPr )); + } + if( opening == "{" ) + opening = "left lbrace "; + if( closing == "}" ) + closing = " right rbrace"; + if( opening == u"\u27e6" ) + opening = "left ldbracket "; + if( closing == u"\u27e7" ) + closing = " right rdbracket"; + if( opening == "|" ) + opening = "left lline "; + if( closing == "|" ) + closing = " right rline"; + if (opening == OUStringChar(MS_DLINE) + || opening == OUStringChar(MS_DVERTLINE)) + opening = "left ldline "; + if (closing == OUStringChar(MS_DLINE) + || closing == OUStringChar(MS_DVERTLINE)) + closing = " right rdline"; + if (opening == OUStringChar(MS_LANGLE) + || opening == OUStringChar(MS_LMATHANGLE)) + opening = "left langle "; + if (closing == OUStringChar(MS_RANGLE) + || closing == OUStringChar(MS_RMATHANGLE)) + closing = " right rangle"; + // use scalable brackets (the explicit "left" or "right") + if( opening == "(" || opening == "[" ) + opening = "left " + opening; + if( closing == ")" || closing == "]" ) + closing = " right " + closing; + if( separator == "|" ) // plain "|" would be actually "V" (logical or) + separator = " mline "; + if( opening.isEmpty()) + opening = "left none "; + if( closing.isEmpty()) + closing = " right none"; + OUStringBuffer ret; + ret.append( opening ); + bool first = true; + while( m_rStream.findTag( OPENING( M_TOKEN( e )))) + { + if( !first ) + ret.append( separator ); + first = false; + ret.append( readOMathArgInElement( M_TOKEN( e ))); + } + ret.append( closing ); + m_rStream.ensureClosingTag( M_TOKEN( d )); + return ret.makeStringAndClear(); +} + +OUString SmOoxmlImport::handleEqArr() +{ + m_rStream.ensureOpeningTag( M_TOKEN( eqArr )); + OUStringBuffer ret; + do + { // there must be at least one m:e + if( !ret.isEmpty()) + ret.append("#"); + ret.append(" "); + ret.append(readOMathArgInElement( M_TOKEN( e ))); + ret.append(" "); + } while( !m_rStream.atEnd() && m_rStream.findTag( OPENING( M_TOKEN( e )))); + m_rStream.ensureClosingTag( M_TOKEN( eqArr )); + return "stack {" + ret.makeStringAndClear() + "}"; +} + +OUString SmOoxmlImport::handleF() +{ + m_rStream.ensureOpeningTag( M_TOKEN( f )); + enum operation_t { bar, lin, noBar } operation = bar; + if( m_rStream.checkOpeningTag( M_TOKEN( fPr ))) + { + if( XmlStream::Tag type = m_rStream.checkOpeningTag( M_TOKEN( type ))) + { + if( type.attribute( M_TOKEN( val )) == "bar" ) + operation = bar; + else if( type.attribute( M_TOKEN( val )) == "lin" ) + operation = lin; + else if( type.attribute( M_TOKEN( val )) == "noBar" ) + operation = noBar; + m_rStream.ensureClosingTag( M_TOKEN( type )); + } + m_rStream.ensureClosingTag( M_TOKEN( fPr )); + } + OUString num = readOMathArgInElement( M_TOKEN( num )); + OUString den = readOMathArgInElement( M_TOKEN( den )); + m_rStream.ensureClosingTag( M_TOKEN( f )); + if( operation == bar ) + return "{" + num + "} over {" + den + "}"; + else if( operation == lin ) + return "{" + num + "} / {" + den + "}"; + else // noBar + { + return "binom {" + num + "} {" + den + "}"; + } +} + +OUString SmOoxmlImport::handleFunc() +{ +//lim from{x rightarrow 1} x + m_rStream.ensureOpeningTag( M_TOKEN( func )); + OUString fname = readOMathArgInElement( M_TOKEN( fName )); + // fix the various functions + if( fname.startsWith( "lim csub {" )) + fname = "lim from {" + fname.copy( 10 ); + OUString ret = fname + " {" + readOMathArgInElement( M_TOKEN( e )) + "}"; + m_rStream.ensureClosingTag( M_TOKEN( func )); + return ret; +} + +OUString SmOoxmlImport::handleLimLowUpp( LimLowUpp_t limlowupp ) +{ + int token = limlowupp == LimLow ? M_TOKEN( limLow ) : M_TOKEN( limUpp ); + m_rStream.ensureOpeningTag( token ); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString lim = readOMathArgInElement( M_TOKEN( lim )); + m_rStream.ensureClosingTag( token ); + // fix up overbrace/underbrace (use { }, as {} will be converted to a placeholder) + if( limlowupp == LimUpp && e.endsWith( " overbrace { }" )) + return e.copy( 0, e.getLength() - 2 ) + lim + "}"; + if( limlowupp == LimLow && e.endsWith( " underbrace { }" )) + return e.copy( 0, e.getLength() - 2 ) + lim + "}"; + return e + + ( limlowupp == LimLow ? OUStringLiteral( " csub {" ) : OUStringLiteral( " csup {" )) + + lim + "}"; +} + +OUString SmOoxmlImport::handleGroupChr() +{ + m_rStream.ensureOpeningTag( M_TOKEN( groupChr )); + sal_Unicode chr = 0x23df; + enum pos_t { top, bot } pos = bot; + if( m_rStream.checkOpeningTag( M_TOKEN( groupChrPr ))) + { + if( XmlStream::Tag chrTag = m_rStream.checkOpeningTag( M_TOKEN( chr ))) + { + chr = chrTag.attribute( M_TOKEN( val ), chr ); + m_rStream.ensureClosingTag( M_TOKEN( chr )); + } + if( XmlStream::Tag posTag = m_rStream.checkOpeningTag( M_TOKEN( pos ))) + { + if( posTag.attribute( M_TOKEN( val ), OUString( "bot" )) == "top" ) + pos = top; + m_rStream.ensureClosingTag( M_TOKEN( pos )); + } + m_rStream.ensureClosingTag( M_TOKEN( groupChrPr )); + } + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( groupChr )); + if( pos == top && chr == u'\x23de') + return "{" + e + "} overbrace { }"; + if( pos == bot && chr == u'\x23df') + return "{" + e + "} underbrace { }"; + if( pos == top ) + return "{" + e + "} csup {" + OUStringChar( chr ) + "}"; + else + return "{" + e + "} csub {" + OUStringChar( chr ) + "}"; +} + +OUString SmOoxmlImport::handleM() +{ + m_rStream.ensureOpeningTag( M_TOKEN( m )); + OUStringBuffer allrows; + do // there must be at least one m:mr + { + m_rStream.ensureOpeningTag( M_TOKEN( mr )); + OUStringBuffer row; + do // there must be at least one m:e + { + if( !row.isEmpty()) + row.append(" # "); + row.append(readOMathArgInElement( M_TOKEN( e ))); + } while( !m_rStream.atEnd() && m_rStream.findTag( OPENING( M_TOKEN( e )))); + if( !allrows.isEmpty()) + allrows.append(" ## "); + allrows.append(row); + m_rStream.ensureClosingTag( M_TOKEN( mr )); + } while( !m_rStream.atEnd() && m_rStream.findTag( OPENING( M_TOKEN( mr )))); + m_rStream.ensureClosingTag( M_TOKEN( m )); + return "matrix {" + allrows.makeStringAndClear() + "}"; +} + +OUString SmOoxmlImport::handleNary() +{ + m_rStream.ensureOpeningTag( M_TOKEN( nary )); + sal_Unicode chr = 0x222b; + bool subHide = false; + bool supHide = false; + if( m_rStream.checkOpeningTag( M_TOKEN( naryPr ))) + { + if( XmlStream::Tag chrTag = m_rStream.checkOpeningTag( M_TOKEN( chr ))) + { + chr = chrTag.attribute( M_TOKEN( val ), chr ); + m_rStream.ensureClosingTag( M_TOKEN( chr )); + } + if( XmlStream::Tag subHideTag = m_rStream.checkOpeningTag( M_TOKEN( subHide ))) + { + subHide = subHideTag.attribute( M_TOKEN( val ), subHide ); + m_rStream.ensureClosingTag( M_TOKEN( subHide )); + } + if( XmlStream::Tag supHideTag = m_rStream.checkOpeningTag( M_TOKEN( supHide ))) + { + supHide = supHideTag.attribute( M_TOKEN( val ), supHide ); + m_rStream.ensureClosingTag( M_TOKEN( supHide )); + } + m_rStream.ensureClosingTag( M_TOKEN( naryPr )); + } + OUString sub = readOMathArgInElement( M_TOKEN( sub )); + OUString sup = readOMathArgInElement( M_TOKEN( sup )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString ret; + switch( chr ) + { + case MS_INT: + ret = "int"; + break; + case MS_IINT: + ret = "iint"; + break; + case MS_IIINT: + ret = "iiint"; + break; + case MS_LINT: + ret = "lint"; + break; + case MS_LLINT: + ret = "llint"; + break; + case MS_LLLINT: + ret = "lllint"; + break; + case MS_PROD: + ret = "prod"; + break; + case MS_COPROD: + ret = "coprod"; + break; + case MS_SUM: + ret = "sum"; + break; + default: + SAL_WARN( "starmath.ooxml", "Unknown m:nary chr \'" << OUString(chr) << "\'" ); + break; + } + if( !subHide ) + ret += " from {" + sub + "}"; + if( !supHide ) + ret += " to {" + sup + "}"; + ret += " {" + e + "}"; + m_rStream.ensureClosingTag( M_TOKEN( nary )); + return ret; +} + +// NOT complete +OUString SmOoxmlImport::handleR() +{ + m_rStream.ensureOpeningTag( M_TOKEN( r )); + bool normal = false; + bool literal = false; + if( XmlStream::Tag rPr = m_rStream.checkOpeningTag( M_TOKEN( rPr ))) + { + if( XmlStream::Tag litTag = m_rStream.checkOpeningTag( M_TOKEN( lit ))) + { + literal = litTag.attribute( M_TOKEN( val ), true ); + m_rStream.ensureClosingTag( M_TOKEN( lit )); + } + if( XmlStream::Tag norTag = m_rStream.checkOpeningTag( M_TOKEN( nor ))) + { + normal = norTag.attribute( M_TOKEN( val ), true ); + m_rStream.ensureClosingTag( M_TOKEN( nor )); + } + m_rStream.ensureClosingTag( M_TOKEN( rPr )); + } + OUStringBuffer text; + while( !m_rStream.atEnd() && m_rStream.currentToken() != CLOSING( m_rStream.currentToken())) + { + switch( m_rStream.currentToken()) + { + case OPENING( M_TOKEN( t )): + { + XmlStream::Tag rtag = m_rStream.ensureOpeningTag( M_TOKEN( t )); + if( rtag.attribute( OOX_TOKEN( xml, space )) != "preserve" ) + text.append(rtag.text.trim()); + else + text.append(rtag.text); + m_rStream.ensureClosingTag( M_TOKEN( t )); + break; + } + default: + m_rStream.handleUnexpectedTag(); + break; + } + } + m_rStream.ensureClosingTag( M_TOKEN( r )); + if( normal || literal ) + { + text.insert(0, "\""); + text.append("\""); + } + return text.makeStringAndClear().replaceAll("{", "\\{").replaceAll("}", "\\}"); +} + +OUString SmOoxmlImport::handleRad() +{ + m_rStream.ensureOpeningTag( M_TOKEN( rad )); + bool degHide = false; + if( m_rStream.checkOpeningTag( M_TOKEN( radPr ))) + { + if( XmlStream::Tag degHideTag = m_rStream.checkOpeningTag( M_TOKEN( degHide ))) + { + degHide = degHideTag.attribute( M_TOKEN( val ), degHide ); + m_rStream.ensureClosingTag( M_TOKEN( degHide )); + } + m_rStream.ensureClosingTag( M_TOKEN( radPr )); + } + OUString deg = readOMathArgInElement( M_TOKEN( deg )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( rad )); + if( degHide ) + return "sqrt {" + e + "}"; + else + return "nroot {" + deg + "} {" + e + "}"; +} + +OUString SmOoxmlImport::handleSpre() +{ + m_rStream.ensureOpeningTag( M_TOKEN( sPre )); + OUString sub = readOMathArgInElement( M_TOKEN( sub )); + OUString sup = readOMathArgInElement( M_TOKEN( sup )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( sPre )); + return "{" + e + "} lsub {" + sub + "} lsup {" + sup + "}"; +} + +OUString SmOoxmlImport::handleSsub() +{ + m_rStream.ensureOpeningTag( M_TOKEN( sSub )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString sub = readOMathArgInElement( M_TOKEN( sub )); + m_rStream.ensureClosingTag( M_TOKEN( sSub )); + return "{" + e + "} rsub {" + sub + "}"; +} + +OUString SmOoxmlImport::handleSsubsup() +{ + m_rStream.ensureOpeningTag( M_TOKEN( sSubSup )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString sub = readOMathArgInElement( M_TOKEN( sub )); + OUString sup = readOMathArgInElement( M_TOKEN( sup )); + m_rStream.ensureClosingTag( M_TOKEN( sSubSup )); + return "{" + e + "} rsub {" + sub + "} rsup {" + sup + "}"; +} + +OUString SmOoxmlImport::handleSsup() +{ + m_rStream.ensureOpeningTag( M_TOKEN( sSup )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString sup = readOMathArgInElement( M_TOKEN( sup )); + m_rStream.ensureClosingTag( M_TOKEN( sSup )); + return "{" + e + "} ^ {" + sup + "}"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ooxmlimport.hxx b/starmath/source/ooxmlimport.hxx new file mode 100644 index 000000000..5fc7a6a33 --- /dev/null +++ b/starmath/source/ooxmlimport.hxx @@ -0,0 +1,54 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_STARMATH_SOURCE_OOXMLIMPORT_HXX +#define INCLUDED_STARMATH_SOURCE_OOXMLIMPORT_HXX + +#include <rtl/ustring.hxx> + +namespace oox::formulaimport { class XmlStream; } +/** + Class implementing reading of formulas from OOXML. The toplevel element is expected + to be oMath (handle oMathPara outside of this code). + */ +class SmOoxmlImport +{ +public: + explicit SmOoxmlImport( oox::formulaimport::XmlStream& stream ); + OUString ConvertToStarMath(); +private: + OUString handleStream(); + OUString handleAcc(); + OUString handleBar(); + OUString handleBox(); + OUString handleBorderBox(); + OUString handleD(); + OUString handleEqArr(); + OUString handleF(); + OUString handleFunc(); + enum LimLowUpp_t { LimLow, LimUpp }; + OUString handleLimLowUpp( LimLowUpp_t limlowupp ); + OUString handleGroupChr(); + OUString handleM(); + OUString handleNary(); + OUString handleR(); + OUString handleRad(); + OUString handleSpre(); + OUString handleSsub(); + OUString handleSsubsup(); + OUString handleSsup(); + OUString readOMathArg( int stoptoken ); + OUString readOMathArgInElement( int token ); + + oox::formulaimport::XmlStream& m_rStream; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/parse.cxx b/starmath/source/parse.cxx new file mode 100644 index 000000000..67512e4a4 --- /dev/null +++ b/starmath/source/parse.cxx @@ -0,0 +1,2502 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <com/sun/star/i18n/UnicodeType.hpp> +#include <com/sun/star/i18n/KParseTokens.hpp> +#include <com/sun/star/i18n/KParseType.hpp> +#include <i18nlangtag/lang.h> +#include <tools/lineend.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/syslocale.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <rtl/character.hxx> +#include <node.hxx> +#include <parse.hxx> +#include <strings.hrc> +#include <smmod.hxx> +#include "cfgitem.hxx" +#include <cassert> +#include <stack> + +using namespace ::com::sun::star::i18n; + + +SmToken::SmToken() + : eType(TUNKNOWN) + , cMathChar('\0') + , nGroup(TG::NONE) + , nLevel(0) + , nRow(0) + , nCol(0) +{ +} + +SmToken::SmToken(SmTokenType eTokenType, + sal_Unicode cMath, + const char* pText, + TG nTokenGroup, + sal_uInt16 nTokenLevel) + : aText(OUString::createFromAscii(pText)) + , eType(eTokenType) + , cMathChar(cMath) + , nGroup(nTokenGroup) + , nLevel(nTokenLevel) + , nRow(0) + , nCol(0) +{ +} + +//Definition of math keywords +static const SmTokenTableEntry aTokenTable[] = +{ + { "abs", TABS, '\0', TG::UnOper, 13 }, + { "acute", TACUTE, MS_ACUTE, TG::Attribute, 5 }, + { "aleph" , TALEPH, MS_ALEPH, TG::Standalone, 5 }, + { "alignb", TALIGNC, '\0', TG::Align, 0}, + { "alignc", TALIGNC, '\0', TG::Align, 0}, + { "alignl", TALIGNL, '\0', TG::Align, 0}, + { "alignm", TALIGNC, '\0', TG::Align, 0}, + { "alignr", TALIGNR, '\0', TG::Align, 0}, + { "alignt", TALIGNC, '\0', TG::Align, 0}, + { "and", TAND, MS_AND, TG::Product, 0}, + { "approx", TAPPROX, MS_APPROX, TG::Relation, 0}, + { "aqua", TAQUA, '\0', TG::Color, 0}, + { "arccos", TACOS, '\0', TG::Function, 5}, + { "arccot", TACOT, '\0', TG::Function, 5}, + { "arcosh", TACOSH, '\0', TG::Function, 5 }, + { "arcoth", TACOTH, '\0', TG::Function, 5 }, + { "arcsin", TASIN, '\0', TG::Function, 5}, + { "arctan", TATAN, '\0', TG::Function, 5}, + { "arsinh", TASINH, '\0', TG::Function, 5}, + { "artanh", TATANH, '\0', TG::Function, 5}, + { "backepsilon" , TBACKEPSILON, MS_BACKEPSILON, TG::Standalone, 5}, + { "bar", TBAR, MS_BAR, TG::Attribute, 5}, + { "binom", TBINOM, '\0', TG::NONE, 5 }, + { "black", TBLACK, '\0', TG::Color, 0}, + { "blue", TBLUE, '\0', TG::Color, 0}, + { "bold", TBOLD, '\0', TG::FontAttr, 5}, + { "boper", TBOPER, '\0', TG::Product, 0}, + { "breve", TBREVE, MS_BREVE, TG::Attribute, 5}, + { "bslash", TBACKSLASH, MS_BACKSLASH, TG::Product, 0 }, + { "cdot", TCDOT, MS_CDOT, TG::Product, 0}, + { "check", TCHECK, MS_CHECK, TG::Attribute, 5}, + { "circ" , TCIRC, MS_CIRC, TG::Standalone, 5}, + { "circle", TCIRCLE, MS_CIRCLE, TG::Attribute, 5}, + { "color", TCOLOR, '\0', TG::FontAttr, 5}, + { "coprod", TCOPROD, MS_COPROD, TG::Oper, 5}, + { "cos", TCOS, '\0', TG::Function, 5}, + { "cosh", TCOSH, '\0', TG::Function, 5}, + { "cot", TCOT, '\0', TG::Function, 5}, + { "coth", TCOTH, '\0', TG::Function, 5}, + { "csub", TCSUB, '\0', TG::Power, 0}, + { "csup", TCSUP, '\0', TG::Power, 0}, + { "cyan", TCYAN, '\0', TG::Color, 0}, + { "dddot", TDDDOT, MS_DDDOT, TG::Attribute, 5}, + { "ddot", TDDOT, MS_DDOT, TG::Attribute, 5}, + { "def", TDEF, MS_DEF, TG::Relation, 0}, + { "div", TDIV, MS_DIV, TG::Product, 0}, + { "divides", TDIVIDES, MS_LINE, TG::Relation, 0}, + { "dlarrow" , TDLARROW, MS_DLARROW, TG::Standalone, 5}, + { "dlrarrow" , TDLRARROW, MS_DLRARROW, TG::Standalone, 5}, + { "dot", TDOT, MS_DOT, TG::Attribute, 5}, + { "dotsaxis", TDOTSAXIS, MS_DOTSAXIS, TG::Standalone, 5}, // 5 to continue expression + { "dotsdiag", TDOTSDIAG, MS_DOTSUP, TG::Standalone, 5}, + { "dotsdown", TDOTSDOWN, MS_DOTSDOWN, TG::Standalone, 5}, + { "dotslow", TDOTSLOW, MS_DOTSLOW, TG::Standalone, 5}, + { "dotsup", TDOTSUP, MS_DOTSUP, TG::Standalone, 5}, + { "dotsvert", TDOTSVERT, MS_DOTSVERT, TG::Standalone, 5}, + { "downarrow" , TDOWNARROW, MS_DOWNARROW, TG::Standalone, 5}, + { "drarrow" , TDRARROW, MS_DRARROW, TG::Standalone, 5}, + { "emptyset" , TEMPTYSET, MS_EMPTYSET, TG::Standalone, 5}, + { "equiv", TEQUIV, MS_EQUIV, TG::Relation, 0}, + { "exists", TEXISTS, MS_EXISTS, TG::Standalone, 5}, + { "exp", TEXP, '\0', TG::Function, 5}, + { "fact", TFACT, MS_FACT, TG::UnOper, 5}, + { "fixed", TFIXED, '\0', TG::Font, 0}, + { "font", TFONT, '\0', TG::FontAttr, 5}, + { "forall", TFORALL, MS_FORALL, TG::Standalone, 5}, + { "from", TFROM, '\0', TG::Limit, 0}, + { "fuchsia", TFUCHSIA, '\0', TG::Color, 0}, + { "func", TFUNC, '\0', TG::Function, 5}, + { "ge", TGE, MS_GE, TG::Relation, 0}, + { "geslant", TGESLANT, MS_GESLANT, TG::Relation, 0 }, + { "gg", TGG, MS_GG, TG::Relation, 0}, + { "grave", TGRAVE, MS_GRAVE, TG::Attribute, 5}, + { "gray", TGRAY, '\0', TG::Color, 0}, + { "green", TGREEN, '\0', TG::Color, 0}, + { "gt", TGT, MS_GT, TG::Relation, 0}, + { "harpoon", THARPOON, MS_HARPOON, TG::Attribute, 5}, + { "hat", THAT, MS_HAT, TG::Attribute, 5}, + { "hbar" , THBAR, MS_HBAR, TG::Standalone, 5}, + { "iiint", TIIINT, MS_IIINT, TG::Oper, 5}, + { "iint", TIINT, MS_IINT, TG::Oper, 5}, + { "im" , TIM, MS_IM, TG::Standalone, 5 }, + { "in", TIN, MS_IN, TG::Relation, 0}, + { "infinity" , TINFINITY, MS_INFINITY, TG::Standalone, 5}, + { "infty" , TINFINITY, MS_INFINITY, TG::Standalone, 5}, + { "int", TINT, MS_INT, TG::Oper, 5}, + { "intd", TINTD, MS_INT, TG::Oper, 5}, + { "intersection", TINTERSECT, MS_INTERSECT, TG::Product, 0}, + { "ital", TITALIC, '\0', TG::FontAttr, 5}, + { "italic", TITALIC, '\0', TG::FontAttr, 5}, + { "lambdabar" , TLAMBDABAR, MS_LAMBDABAR, TG::Standalone, 5}, + { "langle", TLANGLE, MS_LMATHANGLE, TG::LBrace, 5}, + { "laplace", TLAPLACE, MS_LAPLACE, TG::Standalone, 5}, + { "lbrace", TLBRACE, MS_LBRACE, TG::LBrace, 5}, + { "lceil", TLCEIL, MS_LCEIL, TG::LBrace, 5}, + { "ldbracket", TLDBRACKET, MS_LDBRACKET, TG::LBrace, 5}, + { "ldline", TLDLINE, MS_DVERTLINE, TG::LBrace, 5}, + { "le", TLE, MS_LE, TG::Relation, 0}, + { "left", TLEFT, '\0', TG::NONE, 5}, + { "leftarrow" , TLEFTARROW, MS_LEFTARROW, TG::Standalone, 5}, + { "leslant", TLESLANT, MS_LESLANT, TG::Relation, 0 }, + { "lfloor", TLFLOOR, MS_LFLOOR, TG::LBrace, 5}, + { "lim", TLIM, '\0', TG::Oper, 5}, + { "lime", TLIME, '\0', TG::Color, 0}, + { "liminf", TLIMINF, '\0', TG::Oper, 5}, + { "limsup", TLIMSUP, '\0', TG::Oper, 5}, + { "lint", TLINT, MS_LINT, TG::Oper, 5}, + { "ll", TLL, MS_LL, TG::Relation, 0}, + { "lline", TLLINE, MS_VERTLINE, TG::LBrace, 5}, + { "llint", TLLINT, MS_LLINT, TG::Oper, 5}, + { "lllint", TLLLINT, MS_LLLINT, TG::Oper, 5}, + { "ln", TLN, '\0', TG::Function, 5}, + { "log", TLOG, '\0', TG::Function, 5}, + { "lsub", TLSUB, '\0', TG::Power, 0}, + { "lsup", TLSUP, '\0', TG::Power, 0}, + { "lt", TLT, MS_LT, TG::Relation, 0}, + { "magenta", TMAGENTA, '\0', TG::Color, 0}, + { "maroon", TMAROON, '\0', TG::Color, 0}, + { "matrix", TMATRIX, '\0', TG::NONE, 5}, + { "minusplus", TMINUSPLUS, MS_MINUSPLUS, TG::UnOper | TG::Sum, 5}, + { "mline", TMLINE, MS_VERTLINE, TG::NONE, 0}, //! not in TG::RBrace, Level 0 + { "nabla", TNABLA, MS_NABLA, TG::Standalone, 5}, + { "navy", TNAVY, '\0', TG::Color, 0}, + { "nbold", TNBOLD, '\0', TG::FontAttr, 5}, + { "ndivides", TNDIVIDES, MS_NDIVIDES, TG::Relation, 0}, + { "neg", TNEG, MS_NEG, TG::UnOper, 5 }, + { "neq", TNEQ, MS_NEQ, TG::Relation, 0}, + { "newline", TNEWLINE, '\0', TG::NONE, 0}, + { "ni", TNI, MS_NI, TG::Relation, 0}, + { "nitalic", TNITALIC, '\0', TG::FontAttr, 5}, + { "none", TNONE, '\0', TG::LBrace | TG::RBrace, 0}, + { "nospace", TNOSPACE, '\0', TG::Standalone, 5}, + { "notexists", TNOTEXISTS, MS_NOTEXISTS, TG::Standalone, 5}, + { "notin", TNOTIN, MS_NOTIN, TG::Relation, 0}, + { "nprec", TNOTPRECEDES, MS_NOTPRECEDES, TG::Relation, 0 }, + { "nroot", TNROOT, MS_SQRT, TG::UnOper, 5}, + { "nsubset", TNSUBSET, MS_NSUBSET, TG::Relation, 0 }, + { "nsubseteq", TNSUBSETEQ, MS_NSUBSETEQ, TG::Relation, 0 }, + { "nsucc", TNOTSUCCEEDS, MS_NOTSUCCEEDS, TG::Relation, 0 }, + { "nsupset", TNSUPSET, MS_NSUPSET, TG::Relation, 0 }, + { "nsupseteq", TNSUPSETEQ, MS_NSUPSETEQ, TG::Relation, 0 }, + { "odivide", TODIVIDE, MS_ODIVIDE, TG::Product, 0}, + { "odot", TODOT, MS_ODOT, TG::Product, 0}, + { "olive", TOLIVE, '\0', TG::Color, 0}, + { "ominus", TOMINUS, MS_OMINUS, TG::Sum, 0}, + { "oper", TOPER, '\0', TG::Oper, 5}, + { "oplus", TOPLUS, MS_OPLUS, TG::Sum, 0}, + { "or", TOR, MS_OR, TG::Sum, 0}, + { "ortho", TORTHO, MS_ORTHO, TG::Relation, 0}, + { "otimes", TOTIMES, MS_OTIMES, TG::Product, 0}, + { "over", TOVER, '\0', TG::Product, 0}, + { "overbrace", TOVERBRACE, MS_OVERBRACE, TG::Product, 5}, + { "overline", TOVERLINE, '\0', TG::Attribute, 5}, + { "overstrike", TOVERSTRIKE, '\0', TG::Attribute, 5}, + { "owns", TNI, MS_NI, TG::Relation, 0}, + { "parallel", TPARALLEL, MS_DLINE, TG::Relation, 0}, + { "partial", TPARTIAL, MS_PARTIAL, TG::Standalone, 5 }, + { "phantom", TPHANTOM, '\0', TG::FontAttr, 5}, + { "plusminus", TPLUSMINUS, MS_PLUSMINUS, TG::UnOper | TG::Sum, 5}, + { "prec", TPRECEDES, MS_PRECEDES, TG::Relation, 0 }, + { "preccurlyeq", TPRECEDESEQUAL, MS_PRECEDESEQUAL, TG::Relation, 0 }, + { "precsim", TPRECEDESEQUIV, MS_PRECEDESEQUIV, TG::Relation, 0 }, + { "prod", TPROD, MS_PROD, TG::Oper, 5}, + { "prop", TPROP, MS_PROP, TG::Relation, 0}, + { "purple", TPURPLE, '\0', TG::Color, 0}, + { "rangle", TRANGLE, MS_RMATHANGLE, TG::RBrace, 0}, //! 0 to terminate expression + { "rbrace", TRBRACE, MS_RBRACE, TG::RBrace, 0}, + { "rceil", TRCEIL, MS_RCEIL, TG::RBrace, 0}, + { "rdbracket", TRDBRACKET, MS_RDBRACKET, TG::RBrace, 0}, + { "rdline", TRDLINE, MS_DVERTLINE, TG::RBrace, 0}, + { "re" , TRE, MS_RE, TG::Standalone, 5 }, + { "red", TRED, '\0', TG::Color, 0}, + { "rfloor", TRFLOOR, MS_RFLOOR, TG::RBrace, 0}, //! 0 to terminate expression + { "rgb", TRGB, '\0', TG::Color, 0}, + { "right", TRIGHT, '\0', TG::NONE, 0}, + { "rightarrow" , TRIGHTARROW, MS_RIGHTARROW, TG::Standalone, 5}, + { "rline", TRLINE, MS_VERTLINE, TG::RBrace, 0}, //! 0 to terminate expression + { "rsub", TRSUB, '\0', TG::Power, 0}, + { "rsup", TRSUP, '\0', TG::Power, 0}, + { "sans", TSANS, '\0', TG::Font, 0}, + { "serif", TSERIF, '\0', TG::Font, 0}, + { "setC" , TSETC, MS_SETC, TG::Standalone, 5}, + { "setminus", TBACKSLASH, MS_BACKSLASH, TG::Product, 0 }, + { "setN" , TSETN, MS_SETN, TG::Standalone, 5}, + { "setQ" , TSETQ, MS_SETQ, TG::Standalone, 5}, + { "setR" , TSETR, MS_SETR, TG::Standalone, 5}, + { "setZ" , TSETZ, MS_SETZ, TG::Standalone, 5}, + { "silver", TSILVER, '\0', TG::Color, 0}, + { "sim", TSIM, MS_SIM, TG::Relation, 0}, + { "simeq", TSIMEQ, MS_SIMEQ, TG::Relation, 0}, + { "sin", TSIN, '\0', TG::Function, 5}, + { "sinh", TSINH, '\0', TG::Function, 5}, + { "size", TSIZE, '\0', TG::FontAttr, 5}, + { "slash", TSLASH, MS_SLASH, TG::Product, 0 }, + { "sqrt", TSQRT, MS_SQRT, TG::UnOper, 5}, + { "stack", TSTACK, '\0', TG::NONE, 5}, + { "sub", TRSUB, '\0', TG::Power, 0}, + { "subset", TSUBSET, MS_SUBSET, TG::Relation, 0}, + { "subseteq", TSUBSETEQ, MS_SUBSETEQ, TG::Relation, 0}, + { "succ", TSUCCEEDS, MS_SUCCEEDS, TG::Relation, 0 }, + { "succcurlyeq", TSUCCEEDSEQUAL, MS_SUCCEEDSEQUAL, TG::Relation, 0 }, + { "succsim", TSUCCEEDSEQUIV, MS_SUCCEEDSEQUIV, TG::Relation, 0 }, + { "sum", TSUM, MS_SUM, TG::Oper, 5}, + { "sup", TRSUP, '\0', TG::Power, 0}, + { "supset", TSUPSET, MS_SUPSET, TG::Relation, 0}, + { "supseteq", TSUPSETEQ, MS_SUPSETEQ, TG::Relation, 0}, + { "tan", TTAN, '\0', TG::Function, 5}, + { "tanh", TTANH, '\0', TG::Function, 5}, + { "teal", TTEAL, '\0', TG::Color, 0}, + { "tilde", TTILDE, MS_TILDE, TG::Attribute, 5}, + { "times", TTIMES, MS_TIMES, TG::Product, 0}, + { "to", TTO, '\0', TG::Limit, 0}, + { "toward", TTOWARD, MS_RIGHTARROW, TG::Relation, 0}, + { "transl", TTRANSL, MS_TRANSL, TG::Relation, 0}, + { "transr", TTRANSR, MS_TRANSR, TG::Relation, 0}, + { "underbrace", TUNDERBRACE, MS_UNDERBRACE, TG::Product, 5}, + { "underline", TUNDERLINE, '\0', TG::Attribute, 5}, + { "union", TUNION, MS_UNION, TG::Sum, 0}, + { "uoper", TUOPER, '\0', TG::UnOper, 5}, + { "uparrow" , TUPARROW, MS_UPARROW, TG::Standalone, 5}, + { "vec", TVEC, MS_VEC, TG::Attribute, 5}, + { "white", TWHITE, '\0', TG::Color, 0}, + { "widebslash", TWIDEBACKSLASH, MS_BACKSLASH, TG::Product, 0 }, + { "wideharpoon", TWIDEHARPOON, MS_HARPOON, TG::Attribute, 5}, + { "widehat", TWIDEHAT, MS_HAT, TG::Attribute, 5}, + { "wideslash", TWIDESLASH, MS_SLASH, TG::Product, 0 }, + { "widetilde", TWIDETILDE, MS_TILDE, TG::Attribute, 5}, + { "widevec", TWIDEVEC, MS_VEC, TG::Attribute, 5}, + { "wp" , TWP, MS_WP, TG::Standalone, 5}, + { "yellow", TYELLOW, '\0', TG::Color, 0} +}; + +//Checks if keyword is in the list by SmTokenTableEntry. +#if !defined NDEBUG +static bool sortCompare(const SmTokenTableEntry & lhs, const SmTokenTableEntry & rhs) +{ + return OUString::createFromAscii(lhs.pIdent).compareToIgnoreAsciiCase(OUString::createFromAscii(rhs.pIdent)) < 0; +} +#endif + +//Checks if keyword is in the list. +static bool findCompare(const SmTokenTableEntry & lhs, const OUString & s) +{ + return s.compareToIgnoreAsciiCaseAscii(lhs.pIdent) > 0; +} + +//Returns the SmTokenTableEntry for a keyword +const SmTokenTableEntry * SmParser::GetTokenTableEntry( const OUString &rName ) +{ + static bool bSortKeyWords = false; // Flag: RTF-token table has been sorted. + if( !bSortKeyWords ) //First time sorts it. + { + assert( std::is_sorted( std::begin(aTokenTable), std::end(aTokenTable), sortCompare ) ); + bSortKeyWords = true; + } + + if (rName.isEmpty())return nullptr; //avoid null pointer exceptions + + //Looks for the first keyword after or equal to rName in alphabetical order. + auto findIter = std::lower_bound( std::begin(aTokenTable), std::end(aTokenTable), rName, findCompare ); + if ( findIter != std::end(aTokenTable) && rName.equalsIgnoreAsciiCaseAscii( findIter->pIdent ))return &*findIter; //check is equal + + return nullptr; //not found +} + +namespace { + +bool IsDelimiter( const OUString &rTxt, sal_Int32 nPos ) + // returns 'true' iff cChar is '\0' or a delimiter +{ + assert(nPos <= rTxt.getLength()); //index out of range + + if (nPos == rTxt.getLength())return true; //This is EOF + + sal_Unicode cChar = rTxt[nPos]; + + // check if 'cChar' is in the delimiter table + static const sal_Unicode aDelimiterTable[] = + { + ' ', '{', '}', '(', ')', '\t', '\n', '\r', '+', '-', + '*', '/', '=', '[', ']', '^', '_', '#', + '%', '>', '<', '&', '|', '\\', '"', '~', '`' + };//reordered by usage (by eye) for nanoseconds saving. + + //checks the array + for (auto const &cDelimiter : aDelimiterTable) + { + if (cDelimiter == cChar)return true; + } + + //special chars support + sal_Int16 nTypJp = SM_MOD()->GetSysLocale().GetCharClass().getType( rTxt, nPos ); + return ( nTypJp == css::i18n::UnicodeType::SPACE_SEPARATOR || + nTypJp == css::i18n::UnicodeType::CONTROL); +} + +}//end namespace + +//Text replace onto m_aBufferString +void SmParser::Replace( sal_Int32 nPos, sal_Int32 nLen, const OUString &rText ) +{ + assert( nPos + nLen <= m_aBufferString.getLength() ); //checks if length allows text replace + + m_aBufferString = m_aBufferString.replaceAt( nPos, nLen, rText ); //replace and reindex + sal_Int32 nChg = rText.getLength() - nLen; + m_nBufferIndex = m_nBufferIndex + nChg; + m_nTokenIndex = m_nTokenIndex + nChg; +} + +void SmParser::NextToken() //Central part of the parser +{ + // First character may be any alphabetic + static const sal_Int32 coStartFlags = + KParseTokens::ANY_LETTER | + KParseTokens::IGNORE_LEADING_WS; + + // Continuing characters may be any alphabetic + static const sal_Int32 coContFlags = + (coStartFlags & ~KParseTokens::IGNORE_LEADING_WS) + | KParseTokens::TWO_DOUBLE_QUOTES_BREAK_STRING; + + // user-defined char continuing characters may be any alphanumeric or dot. + static const sal_Int32 coUserDefinedCharContFlags = + KParseTokens::ANY_LETTER_OR_NUMBER | + KParseTokens::ASC_DOT | + KParseTokens::TWO_DOUBLE_QUOTES_BREAK_STRING; + + // First character for numbers, may be any numeric or dot + static const sal_Int32 coNumStartFlags = + KParseTokens::ASC_DIGIT | + KParseTokens::ASC_DOT | + KParseTokens::IGNORE_LEADING_WS; + + // Continuing characters for numbers, may be any numeric or dot. + // tdf#127873: additionally accept ',' comma group separator as too many + // existing documents unwittingly may have used that as decimal separator + // in such locales (though it never was as this is always the en-US locale + // and the group separator is only parsed away). + static const sal_Int32 coNumContFlags = + (coNumStartFlags & ~KParseTokens::IGNORE_LEADING_WS) | + KParseTokens::GROUP_SEPARATOR_IN_NUMBER; + + sal_Int32 nBufLen = m_aBufferString.getLength(); + ParseResult aRes; + sal_Int32 nRealStart; + bool bCont; + do + { + // skip white spaces + while (UnicodeType::SPACE_SEPARATOR == + m_pSysCC->getType( m_aBufferString, m_nBufferIndex )) + ++m_nBufferIndex; + + // Try to parse a number in a locale-independent manner using + // '.' as decimal separator. + // See https://bz.apache.org/ooo/show_bug.cgi?id=45779 + aRes = m_aNumCC.parsePredefinedToken(KParseType::ASC_NUMBER, + m_aBufferString, m_nBufferIndex, + coNumStartFlags, "", + coNumContFlags, ""); + + if (aRes.TokenType == 0) + { + // Try again with the default token parsing. + aRes = m_pSysCC->parseAnyToken(m_aBufferString, m_nBufferIndex, + coStartFlags, "", + coContFlags, ""); + } + + nRealStart = m_nBufferIndex + aRes.LeadingWhiteSpace; + m_nBufferIndex = nRealStart; + + bCont = false; + if ( aRes.TokenType == 0 && + nRealStart < nBufLen && + '\n' == m_aBufferString[ nRealStart ] ) + { + // keep data needed for tokens row and col entry up to date + ++m_nRow; + m_nBufferIndex = m_nColOff = nRealStart + 1; + bCont = true; + } + else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) + { + if (nRealStart + 2 <= nBufLen && m_aBufferString.match("%%", nRealStart)) + { + //SkipComment + m_nBufferIndex = nRealStart + 2; + while (m_nBufferIndex < nBufLen && + '\n' != m_aBufferString[ m_nBufferIndex ]) + ++m_nBufferIndex; + bCont = true; + } + } + + } while (bCont); + + // set index of current token + m_nTokenIndex = m_nBufferIndex; + + m_aCurToken.nRow = m_nRow; + m_aCurToken.nCol = nRealStart - m_nColOff + 1; + + bool bHandled = true; + if (nRealStart >= nBufLen) + { + m_aCurToken.eType = TEND; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 0; + m_aCurToken.aText.clear(); + } + else if (aRes.TokenType & KParseType::ANY_NUMBER) + { + assert(aRes.EndPos > 0); + if ( m_aBufferString[aRes.EndPos-1] == ',' && + aRes.EndPos < nBufLen && + m_pSysCC->getType( m_aBufferString, aRes.EndPos ) != UnicodeType::SPACE_SEPARATOR ) + { + // Comma followed by a non-space char is unlikely for decimal/thousands separator. + --aRes.EndPos; + } + sal_Int32 n = aRes.EndPos - nRealStart; + assert(n >= 0); + m_aCurToken.eType = TNUMBER; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = m_aBufferString.copy( nRealStart, n ); + + SAL_WARN_IF( !IsDelimiter( m_aBufferString, aRes.EndPos ), "starmath", "identifier really finished? (compatibility!)" ); + } + else if (aRes.TokenType & KParseType::DOUBLE_QUOTE_STRING) + { + m_aCurToken.eType = TTEXT; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = aRes.DequotedNameOrString; + m_aCurToken.nRow = m_nRow; + m_aCurToken.nCol = nRealStart - m_nColOff + 2; + } + else if (aRes.TokenType & KParseType::IDENTNAME) + { + sal_Int32 n = aRes.EndPos - nRealStart; + assert(n >= 0); + OUString aName( m_aBufferString.copy( nRealStart, n ) ); + const SmTokenTableEntry *pEntry = GetTokenTableEntry( aName ); + + if (pEntry) + { + m_aCurToken.eType = pEntry->eType; + m_aCurToken.cMathChar = pEntry->cMathChar; + m_aCurToken.nGroup = pEntry->nGroup; + m_aCurToken.nLevel = pEntry->nLevel; + m_aCurToken.aText = OUString::createFromAscii( pEntry->pIdent ); + } + else + { + m_aCurToken.eType = TIDENT; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = aName; + + SAL_WARN_IF(!IsDelimiter(m_aBufferString, aRes.EndPos),"starmath", "identifier really finished? (compatibility!)"); + } + } + else if (aRes.TokenType == 0 && '_' == m_aBufferString[ nRealStart ]) + { + m_aCurToken.eType = TRSUB; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::Power; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "_"; + + aRes.EndPos = nRealStart + 1; + } + else if (aRes.TokenType & KParseType::BOOLEAN) + { + sal_Int32 &rnEndPos = aRes.EndPos; + if (rnEndPos - nRealStart <= 2) + { + sal_Unicode ch = m_aBufferString[ nRealStart ]; + switch (ch) + { + case '<': + { + if (m_aBufferString.match("<<", nRealStart)) + { + m_aCurToken.eType = TLL; + m_aCurToken.cMathChar = MS_LL; + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "<<"; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("<=", nRealStart)) + { + m_aCurToken.eType = TLE; + m_aCurToken.cMathChar = MS_LE; + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "<="; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("<-", nRealStart)) + { + m_aCurToken.eType = TLEFTARROW; + m_aCurToken.cMathChar = MS_LEFTARROW; + m_aCurToken.nGroup = TG::Standalone; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "<-"; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("<>", nRealStart)) + { + m_aCurToken.eType = TNEQ; + m_aCurToken.cMathChar = MS_NEQ; + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "<>"; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("<?>", nRealStart)) + { + m_aCurToken.eType = TPLACE; + m_aCurToken.cMathChar = MS_PLACE; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "<?>"; + + rnEndPos = nRealStart + 3; + } + else + { + m_aCurToken.eType = TLT; + m_aCurToken.cMathChar = MS_LT; + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "<"; + } + } + break; + case '>': + { + if (m_aBufferString.match(">=", nRealStart)) + { + m_aCurToken.eType = TGE; + m_aCurToken.cMathChar = MS_GE; + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = ">="; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match(">>", nRealStart)) + { + m_aCurToken.eType = TGG; + m_aCurToken.cMathChar = MS_GG; + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = ">>"; + + rnEndPos = nRealStart + 2; + } + else + { + m_aCurToken.eType = TGT; + m_aCurToken.cMathChar = MS_GT; + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = ">"; + } + } + break; + default: + bHandled = false; + } + } + } + else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) + { + sal_Int32 &rnEndPos = aRes.EndPos; + if (rnEndPos - nRealStart == 1) + { + sal_Unicode ch = m_aBufferString[ nRealStart ]; + switch (ch) + { + case '%': + { + //! modifies aRes.EndPos + + OSL_ENSURE( rnEndPos >= nBufLen || + '%' != m_aBufferString[ rnEndPos ], + "unexpected comment start" ); + + // get identifier of user-defined character + ParseResult aTmpRes = m_pSysCC->parseAnyToken( + m_aBufferString, rnEndPos, + KParseTokens::ANY_LETTER, + "", + coUserDefinedCharContFlags, + "" ); + + sal_Int32 nTmpStart = rnEndPos + aTmpRes.LeadingWhiteSpace; + + // default setting for the case that no identifier + // i.e. a valid symbol-name is following the '%' + // character + m_aCurToken.eType = TTEXT; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText ="%"; + m_aCurToken.nRow = m_nRow; + m_aCurToken.nCol = nTmpStart - m_nColOff; + + if (aTmpRes.TokenType & KParseType::IDENTNAME) + { + + sal_Int32 n = aTmpRes.EndPos - nTmpStart; + m_aCurToken.eType = TSPECIAL; + m_aCurToken.aText = m_aBufferString.copy( nTmpStart-1, n+1 ); + + OSL_ENSURE( aTmpRes.EndPos > rnEndPos, + "empty identifier" ); + if (aTmpRes.EndPos > rnEndPos) + rnEndPos = aTmpRes.EndPos; + else + ++rnEndPos; + } + + // if no symbol-name was found we start-over with + // finding the next token right after the '%' sign. + // I.e. we leave rnEndPos unmodified. + } + break; + case '[': + { + m_aCurToken.eType = TLBRACKET; + m_aCurToken.cMathChar = MS_LBRACKET; + m_aCurToken.nGroup = TG::LBrace; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "["; + } + break; + case '\\': + { + m_aCurToken.eType = TESCAPE; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "\\"; + } + break; + case ']': + { + m_aCurToken.eType = TRBRACKET; + m_aCurToken.cMathChar = MS_RBRACKET; + m_aCurToken.nGroup = TG::RBrace; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "]"; + } + break; + case '^': + { + m_aCurToken.eType = TRSUP; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::Power; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "^"; + } + break; + case '`': + { + m_aCurToken.eType = TSBLANK; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::Blank; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "`"; + } + break; + case '{': + { + m_aCurToken.eType = TLGROUP; + m_aCurToken.cMathChar = MS_LBRACE; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "{"; + } + break; + case '|': + { + m_aCurToken.eType = TOR; + m_aCurToken.cMathChar = MS_OR; + m_aCurToken.nGroup = TG::Sum; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "|"; + } + break; + case '}': + { + m_aCurToken.eType = TRGROUP; + m_aCurToken.cMathChar = MS_RBRACE; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "}"; + } + break; + case '~': + { + m_aCurToken.eType = TBLANK; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::Blank; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "~"; + } + break; + case '#': + { + if (m_aBufferString.match("##", nRealStart)) + { + m_aCurToken.eType = TDPOUND; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "##"; + + rnEndPos = nRealStart + 2; + } + else + { + m_aCurToken.eType = TPOUND; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "#"; + } + } + break; + case '&': + { + m_aCurToken.eType = TAND; + m_aCurToken.cMathChar = MS_AND; + m_aCurToken.nGroup = TG::Product; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "&"; + } + break; + case '(': + { + m_aCurToken.eType = TLPARENT; + m_aCurToken.cMathChar = MS_LPARENT; + m_aCurToken.nGroup = TG::LBrace; + m_aCurToken.nLevel = 5; //! 0 to continue expression + m_aCurToken.aText = "("; + } + break; + case ')': + { + m_aCurToken.eType = TRPARENT; + m_aCurToken.cMathChar = MS_RPARENT; + m_aCurToken.nGroup = TG::RBrace; + m_aCurToken.nLevel = 0; //! 0 to terminate expression + m_aCurToken.aText = ")"; + } + break; + case '*': + { + m_aCurToken.eType = TMULTIPLY; + m_aCurToken.cMathChar = MS_MULTIPLY; + m_aCurToken.nGroup = TG::Product; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "*"; + } + break; + case '+': + { + if (m_aBufferString.match("+-", nRealStart)) + { + m_aCurToken.eType = TPLUSMINUS; + m_aCurToken.cMathChar = MS_PLUSMINUS; + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "+-"; + + rnEndPos = nRealStart + 2; + } + else + { + m_aCurToken.eType = TPLUS; + m_aCurToken.cMathChar = MS_PLUS; + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "+"; + } + } + break; + case '-': + { + if (m_aBufferString.match("-+", nRealStart)) + { + m_aCurToken.eType = TMINUSPLUS; + m_aCurToken.cMathChar = MS_MINUSPLUS; + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "-+"; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("->", nRealStart)) + { + m_aCurToken.eType = TRIGHTARROW; + m_aCurToken.cMathChar = MS_RIGHTARROW; + m_aCurToken.nGroup = TG::Standalone; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "->"; + + rnEndPos = nRealStart + 2; + } + else + { + m_aCurToken.eType = TMINUS; + m_aCurToken.cMathChar = MS_MINUS; + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "-"; + } + } + break; + case '.': + { + // Only one character? Then it can't be a number. + if (m_nBufferIndex < m_aBufferString.getLength() - 1) + { + // for compatibility with SO5.2 + // texts like .34 ...56 ... h ...78..90 + // will be treated as numbers + m_aCurToken.eType = TNUMBER; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + + sal_Int32 nTxtStart = m_nBufferIndex; + sal_Unicode cChar; + // if the equation ends with dot(.) then increment m_nBufferIndex till end of string only + do + { + cChar = m_aBufferString[ ++m_nBufferIndex ]; + } + while ( (cChar == '.' || rtl::isAsciiDigit( cChar )) && + ( m_nBufferIndex < m_aBufferString.getLength() - 1 ) ); + + m_aCurToken.aText = m_aBufferString.copy( nTxtStart, m_nBufferIndex - nTxtStart ); + aRes.EndPos = m_nBufferIndex; + } + else + bHandled = false; + } + break; + case '/': + { + m_aCurToken.eType = TDIVIDEBY; + m_aCurToken.cMathChar = MS_SLASH; + m_aCurToken.nGroup = TG::Product; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "/"; + } + break; + case '=': + { + m_aCurToken.eType = TASSIGN; + m_aCurToken.cMathChar = MS_ASSIGN; + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "="; + } + break; + default: + bHandled = false; + } + } + } + else + bHandled = false; + + if (!bHandled) + { + m_aCurToken.eType = TCHARACTER; + m_aCurToken.cMathChar = '\0'; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + + // tdf#129372: we may have to deal with surrogate pairs + // (see https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates) + // in this case, we must read 2 sal_Unicode instead of 1 + int nOffset(rtl::isSurrogate(m_aBufferString[nRealStart])? 2 : 1); + m_aCurToken.aText = m_aBufferString.copy( nRealStart, nOffset ); + + aRes.EndPos = nRealStart + nOffset; + } + + if (TEND != m_aCurToken.eType) + m_nBufferIndex = aRes.EndPos; +} + +namespace +{ + SmNodeArray buildNodeArray(std::vector<std::unique_ptr<SmNode>>& rSubNodes) + { + SmNodeArray aSubArray(rSubNodes.size()); + for (size_t i = 0; i < rSubNodes.size(); ++i) + aSubArray[i] = rSubNodes[i].release(); + return aSubArray; + } +} //end namespace + +// grammar + +std::unique_ptr<SmTableNode> SmParser::DoTable() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + std::vector<std::unique_ptr<SmNode>> aLineArray; + aLineArray.push_back(DoLine()); + while (m_aCurToken.eType == TNEWLINE) + { + NextToken(); + aLineArray.push_back(DoLine()); + } + assert(m_aCurToken.eType == TEND); + std::unique_ptr<SmTableNode> xSNode(new SmTableNode(m_aCurToken)); + xSNode->SetSubNodes(buildNodeArray(aLineArray)); + return xSNode; +} + +std::unique_ptr<SmNode> SmParser::DoAlign(bool bUseExtraSpaces) + // parse alignment info (if any), then go on with rest of expression +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + std::unique_ptr<SmStructureNode> xSNode; + + if (TokenInGroup(TG::Align)) + { + xSNode.reset(new SmAlignNode(m_aCurToken)); + + NextToken(); + + // allow for just one align statement in 5.0 + if (TokenInGroup(TG::Align)) + return DoError(SmParseError::DoubleAlign); + } + + auto pNode = DoExpression(bUseExtraSpaces); + + if (xSNode) + { + xSNode->SetSubNode(0, pNode.release()); + return xSNode; + } + return pNode; +} + +// Postcondition: m_aCurToken.eType == TEND || m_aCurToken.eType == TNEWLINE +std::unique_ptr<SmNode> SmParser::DoLine() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + std::vector<std::unique_ptr<SmNode>> ExpressionArray; + + // start with single expression that may have an alignment statement + // (and go on with expressions that must not have alignment + // statements in 'while' loop below. See also 'Expression()'.) + if (m_aCurToken.eType != TEND && m_aCurToken.eType != TNEWLINE) + ExpressionArray.push_back(DoAlign()); + + while (m_aCurToken.eType != TEND && m_aCurToken.eType != TNEWLINE) + ExpressionArray.push_back(DoExpression()); + + //If there's no expression, add an empty one. + //this is to avoid a formula tree without any caret + //positions, in visual formula editor. + if(ExpressionArray.empty()) + { + SmToken aTok; + aTok.eType = TNEWLINE; + ExpressionArray.emplace_back(std::unique_ptr<SmNode>(new SmExpressionNode(aTok))); + } + + auto xSNode = std::make_unique<SmLineNode>(m_aCurToken); + xSNode->SetSubNodes(buildNodeArray(ExpressionArray)); + return xSNode; +} + +std::unique_ptr<SmNode> SmParser::DoExpression(bool bUseExtraSpaces) +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + std::vector<std::unique_ptr<SmNode>> RelationArray; + RelationArray.push_back(DoRelation()); + while (m_aCurToken.nLevel >= 4) + RelationArray.push_back(DoRelation()); + + if (RelationArray.size() > 1) + { + std::unique_ptr<SmExpressionNode> xSNode(new SmExpressionNode(m_aCurToken)); + xSNode->SetSubNodes(buildNodeArray(RelationArray)); + xSNode->SetUseExtraSpaces(bUseExtraSpaces); + return xSNode; + } + else + { + // This expression has only one node so just push this node. + return std::move(RelationArray[0]); + } +} + +std::unique_ptr<SmNode> SmParser::DoRelation() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + auto xFirst = DoSum(); + while (TokenInGroup(TG::Relation)) + { + std::unique_ptr<SmStructureNode> xSNode(new SmBinHorNode(m_aCurToken)); + auto xSecond = DoOpSubSup(); + auto xThird = DoSum(); + xSNode->SetSubNodes(std::move(xFirst), std::move(xSecond), std::move(xThird)); + xFirst = std::move(xSNode); + } + return xFirst; +} + +std::unique_ptr<SmNode> SmParser::DoSum() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + auto xFirst = DoProduct(); + while (TokenInGroup(TG::Sum)) + { + std::unique_ptr<SmStructureNode> xSNode(new SmBinHorNode(m_aCurToken)); + auto xSecond = DoOpSubSup(); + auto xThird = DoProduct(); + xSNode->SetSubNodes(std::move(xFirst), std::move(xSecond), std::move(xThird)); + xFirst = std::move(xSNode); + } + return xFirst; +} + +std::unique_ptr<SmNode> SmParser::DoProduct() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + auto xFirst = DoPower(); + + int nDepthLimit = 0; + + while (TokenInGroup(TG::Product)) + { + //this linear loop builds a recursive structure, if it gets + //too deep then later processing, e.g. releasing the tree, + //can exhaust stack + if (nDepthLimit > DEPTH_LIMIT) + throw std::range_error("parser depth limit"); + + std::unique_ptr<SmStructureNode> xSNode; + std::unique_ptr<SmNode> xOper; + bool bSwitchArgs = false; + + SmTokenType eType = m_aCurToken.eType; + switch (eType) + { + case TOVER: + xSNode.reset(new SmBinVerNode(m_aCurToken)); + xOper.reset(new SmRectangleNode(m_aCurToken)); + NextToken(); + break; + + case TBOPER: + xSNode.reset(new SmBinHorNode(m_aCurToken)); + + NextToken(); + + //Let the glyph node know it's a binary operation + m_aCurToken.eType = TBOPER; + m_aCurToken.nGroup = TG::Product; + xOper = DoGlyphSpecial(); + break; + + case TOVERBRACE : + case TUNDERBRACE : + xSNode.reset(new SmVerticalBraceNode(m_aCurToken)); + xOper.reset(new SmMathSymbolNode(m_aCurToken)); + + NextToken(); + break; + + case TWIDEBACKSLASH: + case TWIDESLASH: + { + SmBinDiagonalNode *pSTmp = new SmBinDiagonalNode(m_aCurToken); + pSTmp->SetAscending(eType == TWIDESLASH); + xSNode.reset(pSTmp); + + xOper.reset(new SmPolyLineNode(m_aCurToken)); + NextToken(); + + bSwitchArgs = true; + break; + } + + default: + xSNode.reset(new SmBinHorNode(m_aCurToken)); + + xOper = DoOpSubSup(); + } + + auto xArg = DoPower(); + + if (bSwitchArgs) + { + //! vgl siehe SmBinDiagonalNode::Arrange + xSNode->SetSubNodes(std::move(xFirst), std::move(xArg), std::move(xOper)); + } + else + { + xSNode->SetSubNodes(std::move(xFirst), std::move(xOper), std::move(xArg)); + } + xFirst = std::move(xSNode); + ++nDepthLimit; + } + return xFirst; +} + +std::unique_ptr<SmNode> SmParser::DoSubSup(TG nActiveGroup, SmNode *pGivenNode) +{ + std::unique_ptr<SmNode> xGivenNode(pGivenNode); + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(nActiveGroup == TG::Power || nActiveGroup == TG::Limit); + assert(m_aCurToken.nGroup == nActiveGroup); + + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(m_aCurToken)); + //! Of course 'm_aCurToken' is just the first sub-/supscript token. + //! It should be of no further interest. The positions of the + //! sub-/supscripts will be identified by the corresponding subnodes + //! index in the 'aSubNodes' array (enum value from 'SmSubSup'). + + pNode->SetUseLimits(nActiveGroup == TG::Limit); + + // initialize subnodes array + std::vector<std::unique_ptr<SmNode>> aSubNodes(1 + SUBSUP_NUM_ENTRIES); + aSubNodes[0] = std::move(xGivenNode); + + // process all sub-/supscripts + int nIndex = 0; + while (TokenInGroup(nActiveGroup)) + { + SmTokenType eType (m_aCurToken.eType); + + switch (eType) + { + case TRSUB : nIndex = static_cast<int>(RSUB); break; + case TRSUP : nIndex = static_cast<int>(RSUP); break; + case TFROM : + case TCSUB : nIndex = static_cast<int>(CSUB); break; + case TTO : + case TCSUP : nIndex = static_cast<int>(CSUP); break; + case TLSUB : nIndex = static_cast<int>(LSUB); break; + case TLSUP : nIndex = static_cast<int>(LSUP); break; + default : + SAL_WARN( "starmath", "unknown case"); + } + nIndex++; + assert(1 <= nIndex && nIndex <= SUBSUP_NUM_ENTRIES); + + std::unique_ptr<SmNode> xENode; + if (aSubNodes[nIndex]) // if already occupied at earlier iteration + { + // forget the earlier one, remember an error instead + aSubNodes[nIndex].reset(); + xENode = DoError(SmParseError::DoubleSubsupscript); // this also skips current token. + } + else + { + // skip sub-/supscript token + NextToken(); + } + + // get sub-/supscript node + // (even when we saw a double-sub/supscript error in the above + // in order to minimize mess and continue parsing.) + std::unique_ptr<SmNode> xSNode; + if (eType == TFROM || eType == TTO) + { + // parse limits in old 4.0 and 5.0 style + xSNode = DoRelation(); + } + else + xSNode = DoTerm(true); + + aSubNodes[nIndex] = std::move(xENode ? xENode : xSNode); + } + + pNode->SetSubNodes(buildNodeArray(aSubNodes)); + return pNode; +} + +std::unique_ptr<SmNode> SmParser::DoOpSubSup() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + // get operator symbol + auto pNode = std::make_unique<SmMathSymbolNode>(m_aCurToken); + // skip operator token + NextToken(); + // get sub- supscripts if any + if (m_aCurToken.nGroup == TG::Power) + return DoSubSup(TG::Power, pNode.release()); + return pNode; +} + +std::unique_ptr<SmNode> SmParser::DoPower() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + // get body for sub- supscripts on top of stack + std::unique_ptr<SmNode> xNode(DoTerm(false)); + + if (m_aCurToken.nGroup == TG::Power) + return DoSubSup(TG::Power, xNode.release()); + return xNode; +} + +std::unique_ptr<SmBlankNode> SmParser::DoBlank() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(TokenInGroup(TG::Blank)); + std::unique_ptr<SmBlankNode> pBlankNode(new SmBlankNode(m_aCurToken)); + + do + { + pBlankNode->IncreaseBy(m_aCurToken); + NextToken(); + } + while (TokenInGroup(TG::Blank)); + + // Ignore trailing spaces, if corresponding option is set + if ( m_aCurToken.eType == TNEWLINE || + (m_aCurToken.eType == TEND && !utl::ConfigManager::IsFuzzing() && SM_MOD()->GetConfig()->IsIgnoreSpacesRight()) ) + { + pBlankNode->Clear(); + } + return pBlankNode; +} + +std::unique_ptr<SmNode> SmParser::DoTerm(bool bGroupNumberIdent) +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + switch (m_aCurToken.eType) + { + case TESCAPE : + return DoEscape(); + + case TNOSPACE : + case TLGROUP : + { + bool bNoSpace = m_aCurToken.eType == TNOSPACE; + if (bNoSpace) + NextToken(); + if (m_aCurToken.eType != TLGROUP) + return DoTerm(false); // nospace is no longer concerned + + NextToken(); + + // allow for empty group + if (m_aCurToken.eType == TRGROUP) + { + std::unique_ptr<SmStructureNode> xSNode(new SmExpressionNode(m_aCurToken)); + xSNode->SetSubNodes(nullptr, nullptr); + + NextToken(); + return std::unique_ptr<SmNode>(xSNode.release()); + } + + auto pNode = DoAlign(!bNoSpace); + if (m_aCurToken.eType == TRGROUP) { + NextToken(); + return pNode; + } + auto xSNode = std::make_unique<SmExpressionNode>(m_aCurToken); + std::unique_ptr<SmNode> xError(DoError(SmParseError::RgroupExpected)); + xSNode->SetSubNodes(std::move(pNode), std::move(xError)); + return std::unique_ptr<SmNode>(xSNode.release()); + } + + case TLEFT : + return DoBrace(); + + case TBLANK : + case TSBLANK : + return DoBlank(); + + case TTEXT : + { + auto pNode = std::make_unique<SmTextNode>(m_aCurToken, FNT_TEXT); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + case TCHARACTER : + { + auto pNode = std::make_unique<SmTextNode>(m_aCurToken, FNT_VARIABLE); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + case TIDENT : + case TNUMBER : + { + auto pTextNode = std::make_unique<SmTextNode>(m_aCurToken, + m_aCurToken.eType == TNUMBER ? + FNT_NUMBER : + FNT_VARIABLE); + if (!bGroupNumberIdent) + { + NextToken(); + return std::unique_ptr<SmNode>(pTextNode.release()); + } + std::vector<std::unique_ptr<SmNode>> aNodes; + // Some people want to be able to write "x_2n" for "x_{2n}" + // although e.g. LaTeX or AsciiMath interpret that as "x_2 n". + // The tokenizer skips whitespaces so we need some additional + // work to distinguish from "x_2 n". + // See https://bz.apache.org/ooo/show_bug.cgi?id=11752 and + // https://bugs.libreoffice.org/show_bug.cgi?id=55853 + sal_Int32 nBufLen = m_aBufferString.getLength(); + + // We need to be careful to call NextToken() only after having + // tested for a whitespace separator (otherwise it will be + // skipped!) + bool moveToNextToken = true; + while (m_nBufferIndex < nBufLen && + m_pSysCC->getType(m_aBufferString, m_nBufferIndex) != + UnicodeType::SPACE_SEPARATOR) + { + NextToken(); + if (m_aCurToken.eType != TNUMBER && + m_aCurToken.eType != TIDENT) + { + // Neither a number nor an identifier. We just moved to + // the next token, so no need to do that again. + moveToNextToken = false; + break; + } + aNodes.emplace_back(std::unique_ptr<SmNode>(new SmTextNode(m_aCurToken, + m_aCurToken.eType == + TNUMBER ? + FNT_NUMBER : + FNT_VARIABLE))); + } + if (moveToNextToken) + NextToken(); + if (aNodes.empty()) + return std::unique_ptr<SmNode>(pTextNode.release()); + // We have several concatenated identifiers and numbers. + // Let's group them into one SmExpressionNode. + aNodes.insert(aNodes.begin(), std::move(pTextNode)); + std::unique_ptr<SmExpressionNode> xNode(new SmExpressionNode(SmToken())); + xNode->SetSubNodes(buildNodeArray(aNodes)); + return std::unique_ptr<SmNode>(xNode.release()); + } + case TLEFTARROW : + case TRIGHTARROW : + case TUPARROW : + case TDOWNARROW : + case TCIRC : + case TDRARROW : + case TDLARROW : + case TDLRARROW : + case TEXISTS : + case TNOTEXISTS : + case TFORALL : + case TPARTIAL : + case TNABLA : + case TLAPLACE : + case TTOWARD : + case TDOTSAXIS : + case TDOTSDIAG : + case TDOTSDOWN : + case TDOTSLOW : + case TDOTSUP : + case TDOTSVERT : + { + auto pNode = std::make_unique<SmMathSymbolNode>(m_aCurToken); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + + case TSETN : + case TSETZ : + case TSETQ : + case TSETR : + case TSETC : + case THBAR : + case TLAMBDABAR : + case TBACKEPSILON : + case TALEPH : + case TIM : + case TRE : + case TWP : + case TEMPTYSET : + case TINFINITY : + { + auto pNode = std::make_unique<SmMathIdentifierNode>(m_aCurToken); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + + case TPLACE: + { + auto pNode = std::make_unique<SmPlaceNode>(m_aCurToken); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + + case TSPECIAL: + return DoSpecial(); + + case TBINOM: + return DoBinom(); + + case TSTACK: + return DoStack(); + + case TMATRIX: + return DoMatrix(); + + default: + if (TokenInGroup(TG::LBrace)) + return DoBrace(); + if (TokenInGroup(TG::Oper)) + return DoOperator(); + if (TokenInGroup(TG::UnOper)) + return DoUnOper(); + if ( TokenInGroup(TG::Attribute) || + TokenInGroup(TG::FontAttr) ) + { + std::stack<std::unique_ptr<SmStructureNode>> aStack; + bool bIsAttr; + for (;;) + { + bIsAttr = TokenInGroup(TG::Attribute); + if (!bIsAttr && !TokenInGroup(TG::FontAttr)) + break; + aStack.push(bIsAttr ? DoAttribut() : DoFontAttribut()); + } + + auto xFirstNode = DoPower(); + while (!aStack.empty()) + { + std::unique_ptr<SmStructureNode> xNode = std::move(aStack.top()); + aStack.pop(); + xNode->SetSubNodes(nullptr, std::move(xFirstNode)); + xFirstNode = std::move(xNode); + } + return xFirstNode; + } + if (TokenInGroup(TG::Function)) + return DoFunction(); + return DoError(SmParseError::UnexpectedChar); + } +} + +std::unique_ptr<SmNode> SmParser::DoEscape() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + NextToken(); + + switch (m_aCurToken.eType) + { + case TLPARENT : + case TRPARENT : + case TLBRACKET : + case TRBRACKET : + case TLDBRACKET : + case TRDBRACKET : + case TLBRACE : + case TLGROUP : + case TRBRACE : + case TRGROUP : + case TLANGLE : + case TRANGLE : + case TLCEIL : + case TRCEIL : + case TLFLOOR : + case TRFLOOR : + case TLLINE : + case TRLINE : + case TLDLINE : + case TRDLINE : + { + auto pNode = std::make_unique<SmMathSymbolNode>(m_aCurToken); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + default: + return DoError(SmParseError::UnexpectedToken); + } +} + +std::unique_ptr<SmOperNode> SmParser::DoOperator() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(TokenInGroup(TG::Oper)); + + auto xSNode = std::make_unique<SmOperNode>(m_aCurToken); + + // get operator + auto xOperator = DoOper(); + + if (m_aCurToken.nGroup == TG::Limit || m_aCurToken.nGroup == TG::Power) + xOperator = DoSubSup(m_aCurToken.nGroup, xOperator.release()); + + // get argument + auto xArg = DoPower(); + + xSNode->SetSubNodes(std::move(xOperator), std::move(xArg)); + return xSNode; +} + +std::unique_ptr<SmNode> SmParser::DoOper() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + SmTokenType eType (m_aCurToken.eType); + std::unique_ptr<SmNode> pNode; + + switch (eType) + { + case TSUM : + case TPROD : + case TCOPROD : + case TINT : + case TINTD : + case TIINT : + case TIIINT : + case TLINT : + case TLLINT : + case TLLLINT : + pNode.reset(new SmMathSymbolNode(m_aCurToken)); + break; + + case TLIM : + case TLIMSUP : + case TLIMINF : + { + const char* pLim = nullptr; + switch (eType) + { + case TLIM : pLim = "lim"; break; + case TLIMSUP : pLim = "lim sup"; break; + case TLIMINF : pLim = "lim inf"; break; + default: + break; + } + if( pLim ) + m_aCurToken.aText = OUString::createFromAscii(pLim); + pNode.reset(new SmTextNode(m_aCurToken, FNT_TEXT)); + } + break; + + case TOPER : + NextToken(); + + OSL_ENSURE(m_aCurToken.eType == TSPECIAL, "Sm: wrong token"); + pNode.reset(new SmGlyphSpecialNode(m_aCurToken)); + break; + + default : + assert(false && "unknown case"); + } + + NextToken(); + return pNode; +} + +std::unique_ptr<SmStructureNode> SmParser::DoUnOper() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(TokenInGroup(TG::UnOper)); + + SmToken aNodeToken = m_aCurToken; + SmTokenType eType = m_aCurToken.eType; + bool bIsPostfix = eType == TFACT; + + std::unique_ptr<SmStructureNode> xSNode; + std::unique_ptr<SmNode> xOper; + std::unique_ptr<SmNode> xExtra; + std::unique_ptr<SmNode> xArg; + + switch (eType) + { + case TABS : + case TSQRT : + NextToken(); + break; + + case TNROOT : + NextToken(); + xExtra = DoPower(); + break; + + case TUOPER : + NextToken(); + //Let the glyph know what it is... + m_aCurToken.eType = TUOPER; + m_aCurToken.nGroup = TG::UnOper; + xOper = DoGlyphSpecial(); + break; + + case TPLUS : + case TMINUS : + case TPLUSMINUS : + case TMINUSPLUS : + case TNEG : + case TFACT : + xOper = DoOpSubSup(); + break; + + default : + assert(false); + } + + // get argument + xArg = DoPower(); + + if (eType == TABS) + { + xSNode.reset(new SmBraceNode(aNodeToken)); + xSNode->SetScaleMode(SmScaleMode::Height); + + // build nodes for left & right lines + // (text, group, level of the used token are of no interest here) + // we'll use row & column of the keyword for abs + aNodeToken.eType = TABS; + + aNodeToken.cMathChar = MS_VERTLINE; + std::unique_ptr<SmNode> xLeft(new SmMathSymbolNode(aNodeToken)); + std::unique_ptr<SmNode> xRight(new SmMathSymbolNode(aNodeToken)); + + xSNode->SetSubNodes(std::move(xLeft), std::move(xArg), std::move(xRight)); + } + else if (eType == TSQRT || eType == TNROOT) + { + xSNode.reset(new SmRootNode(aNodeToken)); + xOper.reset(new SmRootSymbolNode(aNodeToken)); + xSNode->SetSubNodes(std::move(xExtra), std::move(xOper), std::move(xArg)); + } + else + { + xSNode.reset(new SmUnHorNode(aNodeToken)); + if (bIsPostfix) + xSNode->SetSubNodes(std::move(xArg), std::move(xOper)); + else + { + // prefix operator + xSNode->SetSubNodes(std::move(xOper), std::move(xArg)); + } + } + return xSNode; +} + +std::unique_ptr<SmStructureNode> SmParser::DoAttribut() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(TokenInGroup(TG::Attribute)); + + auto xSNode = std::make_unique<SmAttributNode>(m_aCurToken); + std::unique_ptr<SmNode> xAttr; + SmScaleMode eScaleMode = SmScaleMode::None; + + // get appropriate node for the attribute itself + switch (m_aCurToken.eType) + { case TUNDERLINE : + case TOVERLINE : + case TOVERSTRIKE : + xAttr.reset(new SmRectangleNode(m_aCurToken)); + eScaleMode = SmScaleMode::Width; + break; + + case TWIDEVEC : + case TWIDEHARPOON : + case TWIDEHAT : + case TWIDETILDE : + xAttr.reset(new SmMathSymbolNode(m_aCurToken)); + eScaleMode = SmScaleMode::Width; + break; + + default : + xAttr.reset(new SmMathSymbolNode(m_aCurToken)); + } + + NextToken(); + + xSNode->SetSubNodes(std::move(xAttr), nullptr); // the body will be filled later + xSNode->SetScaleMode(eScaleMode); + return xSNode; +} + +std::unique_ptr<SmStructureNode> SmParser::DoFontAttribut() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(TokenInGroup(TG::FontAttr)); + + switch (m_aCurToken.eType) + { + case TITALIC : + case TNITALIC : + case TBOLD : + case TNBOLD : + case TPHANTOM : + { + auto pNode = std::make_unique<SmFontNode>(m_aCurToken); + NextToken(); + return pNode; + } + + case TSIZE : + return DoFontSize(); + + case TFONT : + return DoFont(); + + case TCOLOR : + return DoColor(); + + default : + assert(false); + return {}; + } +} + +std::unique_ptr<SmStructureNode> SmParser::DoColor() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(m_aCurToken.eType == TCOLOR); + + std::unique_ptr<SmStructureNode> xNode; + // last color rules, get that one + SmToken aToken; + do + { + + NextToken(); + + if (TokenInGroup(TG::Color)) + { + aToken = m_aCurToken; + if(m_aCurToken.eType==TRGB){ + SmToken r,g,b; + sal_Int32 nr, ng, nb, nc; + NextToken(); + if(m_aCurToken.eType!=TNUMBER)return DoError(SmParseError::ColorExpected); + r = m_aCurToken; + NextToken(); + if(m_aCurToken.eType!=TNUMBER)return DoError(SmParseError::ColorExpected); + g = m_aCurToken; + NextToken(); + if(m_aCurToken.eType!=TNUMBER)return DoError(SmParseError::ColorExpected); + b = m_aCurToken; + nr = r.aText.toInt32(); + if( nr < 0 || nr > 255 )return DoError(SmParseError::ColorExpected); + ng = g.aText.toInt32(); + if( ng < 0 || ng > 255 )return DoError(SmParseError::ColorExpected); + nb = b.aText.toInt32(); + if( nb < 0 || nb > 255 )return DoError(SmParseError::ColorExpected); + nc = nb + 256 * ( ng + nr*256 ); + aToken.aText = OUString::number(nc); + } + NextToken(); + } + else + { + return DoError(SmParseError::ColorExpected); + } + } while (m_aCurToken.eType == TCOLOR); + + xNode.reset(new SmFontNode(aToken)); + return xNode; +} + +std::unique_ptr<SmStructureNode> SmParser::DoFont() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(m_aCurToken.eType == TFONT); + + std::unique_ptr<SmStructureNode> xNode; + // last font rules, get that one + SmToken aToken; + do + { NextToken(); + + if (TokenInGroup(TG::Font)) + { aToken = m_aCurToken; + NextToken(); + } + else + { + return DoError(SmParseError::FontExpected); + } + } while (m_aCurToken.eType == TFONT); + + xNode.reset(new SmFontNode(aToken)); + return xNode; +} + + +// gets number used as arguments in Math formulas (e.g. 'size' command) +// Format: no negative numbers, must start with a digit, no exponent notation, ... +static bool lcl_IsNumber(const OUString& rText) +{ + bool bPoint = false; + const sal_Unicode* pBuffer = rText.getStr(); + for(sal_Int32 nPos = 0; nPos < rText.getLength(); nPos++, pBuffer++) + { + const sal_Unicode cChar = *pBuffer; + if(cChar == '.') + { + if(bPoint) + return false; + else + bPoint = true; + } + else if ( !rtl::isAsciiDigit( cChar ) ) + return false; + } + return true; +} + +std::unique_ptr<SmStructureNode> SmParser::DoFontSize() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(m_aCurToken.eType == TSIZE); + + FontSizeType Type; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(m_aCurToken)); + + NextToken(); + + switch (m_aCurToken.eType) + { + case TNUMBER: Type = FontSizeType::ABSOLUT; break; + case TPLUS: Type = FontSizeType::PLUS; break; + case TMINUS: Type = FontSizeType::MINUS; break; + case TMULTIPLY: Type = FontSizeType::MULTIPLY; break; + case TDIVIDEBY: Type = FontSizeType::DIVIDE; break; + + default: + return DoError(SmParseError::SizeExpected); + } + + if (Type != FontSizeType::ABSOLUT) + { + NextToken(); + if (m_aCurToken.eType != TNUMBER) + return DoError(SmParseError::SizeExpected); + } + + // get number argument + Fraction aValue( 1 ); + if (lcl_IsNumber( m_aCurToken.aText )) + { + double fTmp = m_aCurToken.aText.toDouble(); + if (fTmp != 0.0) + { + aValue = fTmp; + + //!! keep the numerator and denominator from being too large + //!! otherwise ongoing multiplications may result in overflows + //!! (for example in SmNode::SetFontSize the font size calculated + //!! may become 0 because of this!!! Happens e.g. for ftmp = 2.9 with Linux + //!! or ftmp = 1.11111111111111111... (11/9) on every platform.) + if (aValue.GetDenominator() > 1000) + { + long nNum = aValue.GetNumerator(); + long nDenom = aValue.GetDenominator(); + while (nDenom > 1000) + { + nNum /= 10; + nDenom /= 10; + } + aValue = Fraction( nNum, nDenom ); + } + } + } + + NextToken(); + + pFontNode->SetSizeParameter(aValue, Type); + return pFontNode; +} + +std::unique_ptr<SmStructureNode> SmParser::DoBrace() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + assert(m_aCurToken.eType == TLEFT || TokenInGroup(TG::LBrace)); + + std::unique_ptr<SmStructureNode> xSNode(new SmBraceNode(m_aCurToken)); + std::unique_ptr<SmNode> pBody, pLeft, pRight; + SmScaleMode eScaleMode = SmScaleMode::None; + SmParseError eError = SmParseError::None; + + if (m_aCurToken.eType == TLEFT) + { NextToken(); + + eScaleMode = SmScaleMode::Height; + + // check for left bracket + if (TokenInGroup(TG::LBrace) || TokenInGroup(TG::RBrace)) + { + pLeft.reset(new SmMathSymbolNode(m_aCurToken)); + + NextToken(); + pBody = DoBracebody(true); + + if (m_aCurToken.eType == TRIGHT) + { NextToken(); + + // check for right bracket + if (TokenInGroup(TG::LBrace) || TokenInGroup(TG::RBrace)) + { + pRight.reset(new SmMathSymbolNode(m_aCurToken)); + NextToken(); + } + else + eError = SmParseError::RbraceExpected; + } + else + eError = SmParseError::RightExpected; + } + else + eError = SmParseError::LbraceExpected; + } + else + { + assert(TokenInGroup(TG::LBrace)); + + pLeft.reset(new SmMathSymbolNode(m_aCurToken)); + + NextToken(); + pBody = DoBracebody(false); + + SmTokenType eExpectedType = TUNKNOWN; + switch (pLeft->GetToken().eType) + { case TLPARENT : eExpectedType = TRPARENT; break; + case TLBRACKET : eExpectedType = TRBRACKET; break; + case TLBRACE : eExpectedType = TRBRACE; break; + case TLDBRACKET : eExpectedType = TRDBRACKET; break; + case TLLINE : eExpectedType = TRLINE; break; + case TLDLINE : eExpectedType = TRDLINE; break; + case TLANGLE : eExpectedType = TRANGLE; break; + case TLFLOOR : eExpectedType = TRFLOOR; break; + case TLCEIL : eExpectedType = TRCEIL; break; + default : + SAL_WARN("starmath", "unknown case"); + } + + if (m_aCurToken.eType == eExpectedType) + { + pRight.reset(new SmMathSymbolNode(m_aCurToken)); + NextToken(); + } + else + eError = SmParseError::ParentMismatch; + } + + if (eError == SmParseError::None) + { + assert(pLeft); + assert(pRight); + xSNode->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + xSNode->SetScaleMode(eScaleMode); + return xSNode; + } + return DoError(eError); +} + +std::unique_ptr<SmBracebodyNode> SmParser::DoBracebody(bool bIsLeftRight) +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + auto pBody = std::make_unique<SmBracebodyNode>(m_aCurToken); + + std::vector<std::unique_ptr<SmNode>> aNodes; + // get body if any + if (bIsLeftRight) + { + do + { + if (m_aCurToken.eType == TMLINE) + { + aNodes.emplace_back(std::make_unique<SmMathSymbolNode>(m_aCurToken)); + NextToken(); + } + else if (m_aCurToken.eType != TRIGHT) + { + aNodes.push_back(DoAlign()); + if (m_aCurToken.eType != TMLINE && m_aCurToken.eType != TRIGHT) + aNodes.emplace_back(DoError(SmParseError::RightExpected)); + } + } while (m_aCurToken.eType != TEND && m_aCurToken.eType != TRIGHT); + } + else + { + do + { + if (m_aCurToken.eType == TMLINE) + { + aNodes.emplace_back(std::make_unique<SmMathSymbolNode>(m_aCurToken)); + NextToken(); + } + else if (!TokenInGroup(TG::RBrace)) + { + aNodes.push_back(DoAlign()); + if (m_aCurToken.eType != TMLINE && !TokenInGroup(TG::RBrace)) + aNodes.emplace_back(DoError(SmParseError::RbraceExpected)); + } + } while (m_aCurToken.eType != TEND && !TokenInGroup(TG::RBrace)); + } + + pBody->SetSubNodes(buildNodeArray(aNodes)); + pBody->SetScaleMode(bIsLeftRight ? SmScaleMode::Height : SmScaleMode::None); + return pBody; +} + +std::unique_ptr<SmTextNode> SmParser::DoFunction() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + switch (m_aCurToken.eType) + { + case TFUNC: + NextToken(); // skip "FUNC"-statement + [[fallthrough]]; + + case TSIN : + case TCOS : + case TTAN : + case TCOT : + case TASIN : + case TACOS : + case TATAN : + case TACOT : + case TSINH : + case TCOSH : + case TTANH : + case TCOTH : + case TASINH : + case TACOSH : + case TATANH : + case TACOTH : + case TLN : + case TLOG : + case TEXP : + { + auto pNode = std::make_unique<SmTextNode>(m_aCurToken, FNT_FUNCTION); + NextToken(); + return pNode; + } + + default: + assert(false); + return nullptr; + } +} + +std::unique_ptr<SmTableNode> SmParser::DoBinom() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + auto xSNode = std::make_unique<SmTableNode>(m_aCurToken); + + NextToken(); + + auto xFirst = DoSum(); + auto xSecond = DoSum(); + xSNode->SetSubNodes(std::move(xFirst), std::move(xSecond)); + return xSNode; +} + +std::unique_ptr<SmStructureNode> SmParser::DoStack() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + std::unique_ptr<SmStructureNode> xSNode(new SmTableNode(m_aCurToken)); + NextToken(); + if (m_aCurToken.eType != TLGROUP) + return DoError(SmParseError::LgroupExpected); + std::vector<std::unique_ptr<SmNode>> aExprArr; + do + { + NextToken(); + aExprArr.push_back(DoAlign()); + } + while (m_aCurToken.eType == TPOUND); + + if (m_aCurToken.eType == TRGROUP) + NextToken(); + else + aExprArr.emplace_back(DoError(SmParseError::RgroupExpected)); + + xSNode->SetSubNodes(buildNodeArray(aExprArr)); + return xSNode; +} + +std::unique_ptr<SmStructureNode> SmParser::DoMatrix() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + std::unique_ptr<SmMatrixNode> xMNode(new SmMatrixNode(m_aCurToken)); + NextToken(); + if (m_aCurToken.eType != TLGROUP) + return DoError(SmParseError::LgroupExpected); + + std::vector<std::unique_ptr<SmNode>> aExprArr; + do + { + NextToken(); + aExprArr.push_back(DoAlign()); + } + while (m_aCurToken.eType == TPOUND); + + size_t nCol = aExprArr.size(); + size_t nRow = 1; + while (m_aCurToken.eType == TDPOUND) + { + NextToken(); + for (size_t i = 0; i < nCol; i++) + { + auto xNode = DoAlign(); + if (i < (nCol - 1)) + { + if (m_aCurToken.eType == TPOUND) + NextToken(); + else + xNode = DoError(SmParseError::PoundExpected); + } + aExprArr.emplace_back(std::move(xNode)); + } + ++nRow; + } + + if (m_aCurToken.eType == TRGROUP) + NextToken(); + else + { + std::unique_ptr<SmNode> xENode(DoError(SmParseError::RgroupExpected)); + if (aExprArr.empty()) + nRow = nCol = 1; + else + aExprArr.pop_back(); + aExprArr.emplace_back(std::move(xENode)); + } + + xMNode->SetSubNodes(buildNodeArray(aExprArr)); + xMNode->SetRowCol(static_cast<sal_uInt16>(nRow), + static_cast<sal_uInt16>(nCol)); + return std::unique_ptr<SmStructureNode>(xMNode.release()); +} + +std::unique_ptr<SmSpecialNode> SmParser::DoSpecial() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + bool bReplace = false; + OUString &rName = m_aCurToken.aText; + OUString aNewName; + + // conversion of symbol names for 6.0 (XML) file format + // (name change on import / export. + // UI uses localized names XML file format does not.) + if( rName.startsWith("%") ) + { + if (IsImportSymbolNames()) + { + aNewName = SmLocalizedSymbolData::GetUiSymbolName(rName.copy(1)); + bReplace = true; + } + else if (IsExportSymbolNames()) + { + aNewName = SmLocalizedSymbolData::GetExportSymbolName(rName.copy(1)); + bReplace = true; + } + } + if (!aNewName.isEmpty()) + aNewName = "%" + aNewName; + + + if (bReplace && !aNewName.isEmpty() && rName != aNewName) + { + Replace(GetTokenIndex(), rName.getLength(), aNewName); + rName = aNewName; + } + + // add symbol name to list of used symbols + const OUString aSymbolName(m_aCurToken.aText.copy(1)); + if (!aSymbolName.isEmpty()) + m_aUsedSymbols.insert( aSymbolName ); + + auto pNode = std::make_unique<SmSpecialNode>(m_aCurToken); + NextToken(); + return pNode; +} + +std::unique_ptr<SmGlyphSpecialNode> SmParser::DoGlyphSpecial() +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + auto pNode = std::make_unique<SmGlyphSpecialNode>(m_aCurToken); + NextToken(); + return pNode; +} + +std::unique_ptr<SmExpressionNode> SmParser::DoError(SmParseError eError) +{ + DepthProtect aDepthGuard(m_nParseDepth); + if (aDepthGuard.TooDeep()) + throw std::range_error("parser depth limit"); + + auto xSNode = std::make_unique<SmExpressionNode>(m_aCurToken); + std::unique_ptr<SmErrorNode> pErr(new SmErrorNode(m_aCurToken)); + xSNode->SetSubNodes(std::move(pErr), nullptr); + + AddError(eError, xSNode.get()); + + NextToken(); + + return xSNode; +} + +// end grammar + + +SmParser::SmParser() + : m_nCurError( 0 ) + , m_nBufferIndex( 0 ) + , m_nTokenIndex( 0 ) + , m_nRow( 0 ) + , m_nColOff( 0 ) + , m_bImportSymNames( false ) + , m_bExportSymNames( false ) + , m_nParseDepth(0) + , m_aNumCC( LanguageTag( LANGUAGE_ENGLISH_US ) ) + , m_pSysCC( SM_MOD()->GetSysLocale().GetCharClassPtr() ) +{ +} + +std::unique_ptr<SmTableNode> SmParser::Parse(const OUString &rBuffer) +{ + m_aUsedSymbols.clear(); + + m_aBufferString = convertLineEnd(rBuffer, LINEEND_LF); + m_nBufferIndex = 0; + m_nTokenIndex = 0; + m_nRow = 1; + m_nColOff = 0; + m_nCurError = -1; + + m_aErrDescList.clear(); + + NextToken(); + return DoTable(); +} + +std::unique_ptr<SmNode> SmParser::ParseExpression(const OUString &rBuffer) +{ + m_aBufferString = convertLineEnd(rBuffer, LINEEND_LF); + m_nBufferIndex = 0; + m_nTokenIndex = 0; + m_nRow = 1; + m_nColOff = 0; + m_nCurError = -1; + + m_aErrDescList.clear(); + + NextToken(); + return DoExpression(); +} + + +void SmParser::AddError(SmParseError Type, SmNode *pNode) +{ + std::unique_ptr<SmErrorDesc> pErrDesc(new SmErrorDesc); + + pErrDesc->m_eType = Type; + pErrDesc->m_pNode = pNode; + pErrDesc->m_aText = SmResId(RID_ERR_IDENT); + + const char* pRID; + switch (Type) + { + case SmParseError::UnexpectedChar: pRID = RID_ERR_UNEXPECTEDCHARACTER; break; + case SmParseError::UnexpectedToken: pRID = RID_ERR_UNEXPECTEDTOKEN; break; + case SmParseError::PoundExpected: pRID = RID_ERR_POUNDEXPECTED; break; + case SmParseError::ColorExpected: pRID = RID_ERR_COLOREXPECTED; break; + case SmParseError::LgroupExpected: pRID = RID_ERR_LGROUPEXPECTED; break; + case SmParseError::RgroupExpected: pRID = RID_ERR_RGROUPEXPECTED; break; + case SmParseError::LbraceExpected: pRID = RID_ERR_LBRACEEXPECTED; break; + case SmParseError::RbraceExpected: pRID = RID_ERR_RBRACEEXPECTED; break; + case SmParseError::ParentMismatch: pRID = RID_ERR_PARENTMISMATCH; break; + case SmParseError::RightExpected: pRID = RID_ERR_RIGHTEXPECTED; break; + case SmParseError::FontExpected: pRID = RID_ERR_FONTEXPECTED; break; + case SmParseError::SizeExpected: pRID = RID_ERR_SIZEEXPECTED; break; + case SmParseError::DoubleAlign: pRID = RID_ERR_DOUBLEALIGN; break; + case SmParseError::DoubleSubsupscript: pRID = RID_ERR_DOUBLESUBSUPSCRIPT; break; + default: + assert(false); + return; + } + pErrDesc->m_aText += SmResId(pRID); + + m_aErrDescList.push_back(std::move(pErrDesc)); +} + + +const SmErrorDesc *SmParser::NextError() +{ + if ( !m_aErrDescList.empty() ) + if (m_nCurError > 0) return m_aErrDescList[ --m_nCurError ].get(); + else + { + m_nCurError = 0; + return m_aErrDescList[ m_nCurError ].get(); + } + else return nullptr; +} + + +const SmErrorDesc *SmParser::PrevError() +{ + if ( !m_aErrDescList.empty() ) + if (m_nCurError < static_cast<int>(m_aErrDescList.size() - 1)) return m_aErrDescList[ ++m_nCurError ].get(); + else + { + m_nCurError = static_cast<int>(m_aErrDescList.size() - 1); + return m_aErrDescList[ m_nCurError ].get(); + } + else return nullptr; +} + + +const SmErrorDesc *SmParser::GetError() +{ + if ( !m_aErrDescList.empty() ) + return m_aErrDescList.front().get(); + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/rect.cxx b/starmath/source/rect.cxx new file mode 100644 index 000000000..d49daf747 --- /dev/null +++ b/starmath/source/rect.cxx @@ -0,0 +1,623 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/diagnose.h> +#include <o3tl/sorted_vector.hxx> +#include <vcl/metric.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <sal/log.hxx> + +#include <format.hxx> +#include <rect.hxx> +#include <types.hxx> +#include <smmod.hxx> + +#include <cassert> + +namespace { + +bool SmGetGlyphBoundRect(const vcl::RenderContext &rDev, + const OUString &rText, tools::Rectangle &rRect) + // basically the same as 'GetTextBoundRect' (in class 'OutputDevice') + // but with a string as argument. +{ + // handle special case first + if (rText.isEmpty()) + { + rRect.SetEmpty(); + return true; + } + + // get a device where 'OutputDevice::GetTextBoundRect' will be successful + OutputDevice *pGlyphDev; + if (rDev.GetOutDevType() != OUTDEV_PRINTER) + pGlyphDev = const_cast<OutputDevice *>(&rDev); + else + { + // since we format for the printer (where GetTextBoundRect will fail) + // we need a virtual device here. + pGlyphDev = &SM_MOD()->GetDefaultVirtualDev(); + } + + const FontMetric aDevFM (rDev.GetFontMetric()); + + pGlyphDev->Push(PushFlags::FONT | PushFlags::MAPMODE); + vcl::Font aFnt(rDev.GetFont()); + aFnt.SetAlignment(ALIGN_TOP); + + // use scale factor when calling GetTextBoundRect to counter + // negative effects from antialiasing which may otherwise result + // in significant incorrect bounding rectangles for some characters. + Size aFntSize = aFnt.GetFontSize(); + + // Workaround to avoid HUGE font sizes and resulting problems + long nScaleFactor = 1; + while( aFntSize.Height() > 2000 * nScaleFactor ) + nScaleFactor *= 2; + + aFnt.SetFontSize( Size( aFntSize.Width() / nScaleFactor, aFntSize.Height() / nScaleFactor ) ); + pGlyphDev->SetFont(aFnt); + + long nTextWidth = rDev.GetTextWidth(rText); + tools::Rectangle aResult (Point(), Size(nTextWidth, rDev.GetTextHeight())), + aTmp; + + bool bSuccess = pGlyphDev->GetTextBoundRect(aTmp, rText); + OSL_ENSURE( bSuccess, "GetTextBoundRect failed" ); + + + if (!aTmp.IsEmpty()) + { + aResult = tools::Rectangle(aTmp.Left() * nScaleFactor, aTmp.Top() * nScaleFactor, + aTmp.Right() * nScaleFactor, aTmp.Bottom() * nScaleFactor); + if (&rDev != pGlyphDev) /* only when rDev is a printer... */ + { + long nGDTextWidth = pGlyphDev->GetTextWidth(rText); + if (nGDTextWidth != 0 && + nTextWidth != nGDTextWidth) + { + aResult.SetRight( aResult.Right() * nTextWidth ); + aResult.SetRight( aResult.Right() / ( nGDTextWidth * nScaleFactor) ); + } + } + } + + // move rectangle to match possibly different baselines + // (because of different devices) + long nDelta = aDevFM.GetAscent() - pGlyphDev->GetFontMetric().GetAscent() * nScaleFactor; + aResult.Move(0, nDelta); + + pGlyphDev->Pop(); + + rRect = aResult; + return bSuccess; +} + +bool SmIsMathAlpha(const OUString &rText) + // true iff symbol (from StarMath Font) should be treated as letter +{ + // Set of symbols, which should be treated as letters in StarMath Font + // (to get a normal (non-clipped) SmRect in contrast to the other operators + // and symbols). + static o3tl::sorted_vector<sal_Unicode> const aMathAlpha({ + MS_ALEPH, MS_IM, MS_RE, + MS_WP, u'\xE070', MS_EMPTYSET, + u'\x2113', u'\xE0D6', u'\x2107', + u'\x2127', u'\x210A', MS_HBAR, + MS_LAMBDABAR, MS_SETN, MS_SETZ, + MS_SETQ, MS_SETR, MS_SETC, + u'\x2373', u'\xE0A5', u'\x2112', + u'\x2130', u'\x2131' + }); + + if (rText.isEmpty()) + return false; + + OSL_ENSURE(rText.getLength() == 1, "Sm : string must be exactly one character long"); + sal_Unicode cChar = rText[0]; + + // is it a greek symbol? + if (u'\xE0AC' <= cChar && cChar <= u'\xE0D4') + return true; + // or, does it appear in 'aMathAlpha'? + return aMathAlpha.find(cChar) != aMathAlpha.end(); +} + +} + + +SmRect::SmRect() + // constructs empty rectangle at (0, 0) with width and height 0. + : aTopLeft(0, 0) + , aSize(0, 0) + , nBaseline(0) + , nAlignT(0) + , nAlignM(0) + , nAlignB(0) + , nGlyphTop(0) + , nGlyphBottom(0) + , nItalicLeftSpace(0) + , nItalicRightSpace(0) + , nLoAttrFence(0) + , nHiAttrFence(0) + , nBorderWidth(0) + , bHasBaseline(false) + , bHasAlignInfo(false) +{ +} + + +void SmRect::CopyAlignInfo(const SmRect &rRect) +{ + nBaseline = rRect.nBaseline; + bHasBaseline = rRect.bHasBaseline; + nAlignT = rRect.nAlignT; + nAlignM = rRect.nAlignM; + nAlignB = rRect.nAlignB; + bHasAlignInfo = rRect.bHasAlignInfo; + nLoAttrFence = rRect.nLoAttrFence; + nHiAttrFence = rRect.nHiAttrFence; +} + + +SmRect::SmRect(const OutputDevice &rDev, const SmFormat *pFormat, + const OUString &rText, sal_uInt16 nBorder) + // get rectangle fitting for drawing 'rText' on OutputDevice 'rDev' + : aTopLeft(0, 0) + , aSize(rDev.GetTextWidth(rText), rDev.GetTextHeight()) +{ + const FontMetric aFM (rDev.GetFontMetric()); + bool bIsMath = aFM.GetFamilyName().equalsIgnoreAsciiCase( FONTNAME_MATH ); + bool bAllowSmaller = bIsMath && !SmIsMathAlpha(rText); + const long nFontHeight = rDev.GetFont().GetFontSize().Height(); + + nBorderWidth = nBorder; + bHasAlignInfo = true; + bHasBaseline = true; + nBaseline = aFM.GetAscent(); + nAlignT = nBaseline - nFontHeight * 750 / 1000; + nAlignM = nBaseline - nFontHeight * 121 / 422; + // that's where the horizontal bars of '+', '-', ... are + // (1/3 of ascent over baseline) + // (121 = 1/3 of 12pt ascent, 422 = 12pt fontheight) + nAlignB = nBaseline; + + // workaround for printer fonts with very small (possible 0 or even + // negative(!)) leading + if (aFM.GetInternalLeading() < 5 && rDev.GetOutDevType() == OUTDEV_PRINTER) + { + OutputDevice *pWindow = Application::GetDefaultDevice(); + + pWindow->Push(PushFlags::MAPMODE | PushFlags::FONT); + + pWindow->SetMapMode(rDev.GetMapMode()); + pWindow->SetFont(rDev.GetFontMetric()); + + long nDelta = pWindow->GetFontMetric().GetInternalLeading(); + if (nDelta == 0) + { // this value approx. fits a Leading of 80 at a + // Fontheight of 422 (12pt) + nDelta = nFontHeight * 8 / 43; + } + SetTop(GetTop() - nDelta); + + pWindow->Pop(); + } + + // get GlyphBoundRect + tools::Rectangle aGlyphRect; + bool bSuccess = SmGetGlyphBoundRect(rDev, rText, aGlyphRect); + if (!bSuccess) + SAL_WARN("starmath", "Ooops... (Font missing?)"); + + nItalicLeftSpace = GetLeft() - aGlyphRect.Left() + nBorderWidth; + nItalicRightSpace = aGlyphRect.Right() - GetRight() + nBorderWidth; + if (nItalicLeftSpace < 0 && !bAllowSmaller) + nItalicLeftSpace = 0; + if (nItalicRightSpace < 0 && !bAllowSmaller) + nItalicRightSpace = 0; + + long nDist = 0; + if (pFormat) + nDist = (rDev.GetFont().GetFontSize().Height() + * pFormat->GetDistance(DIS_ORNAMENTSIZE)) / 100; + + nHiAttrFence = aGlyphRect.TopLeft().Y() - 1 - nBorderWidth - nDist; + nLoAttrFence = SmFromTo(GetAlignB(), GetBottom(), 0.0); + + nGlyphTop = aGlyphRect.Top() - nBorderWidth; + nGlyphBottom = aGlyphRect.Bottom() + nBorderWidth; + + if (bAllowSmaller) + { + // for symbols and operators from the StarMath Font + // we adjust upper and lower margin of the symbol + SetTop(nGlyphTop); + SetBottom(nGlyphBottom); + } + + if (nHiAttrFence < GetTop()) + nHiAttrFence = GetTop(); + + if (nLoAttrFence > GetBottom()) + nLoAttrFence = GetBottom(); + + OSL_ENSURE(rText.isEmpty() || !IsEmpty(), + "Sm: empty rectangle created"); +} + + +SmRect::SmRect(long nWidth, long nHeight) + // this constructor should never be used for anything textlike because + // it will not provide useful values for baseline, AlignT and AlignB! + // It's purpose is to get a 'SmRect' for the horizontal line in fractions + // as used in 'SmBinVerNode'. + : aTopLeft(0, 0) + , aSize(nWidth, nHeight) + , nBaseline(0) + , nItalicLeftSpace(0) + , nItalicRightSpace(0) + , nBorderWidth(0) + , bHasBaseline(false) + , bHasAlignInfo(true) +{ + nAlignT = nGlyphTop = nHiAttrFence = GetTop(); + nAlignB = nGlyphBottom = nLoAttrFence = GetBottom(); + nAlignM = (nAlignT + nAlignB) / 2; // this is the default +} + + +void SmRect::SetLeft(long nLeft) +{ + if (nLeft <= GetRight()) + { aSize.setWidth( GetRight() - nLeft + 1 ); + aTopLeft.setX( nLeft ); + } +} + + +void SmRect::SetRight(long nRight) +{ + if (nRight >= GetLeft()) + aSize.setWidth( nRight - GetLeft() + 1 ); +} + + +void SmRect::SetBottom(long nBottom) +{ + if (nBottom >= GetTop()) + aSize.setHeight( nBottom - GetTop() + 1 ); +} + + +void SmRect::SetTop(long nTop) +{ + if (nTop <= GetBottom()) + { aSize.setHeight( GetBottom() - nTop + 1 ); + aTopLeft.setY( nTop ); + } +} + + +void SmRect::Move(const Point &rPosition) + // move rectangle by position 'rPosition'. +{ + aTopLeft += rPosition; + + long nDelta = rPosition.Y(); + nBaseline += nDelta; + nAlignT += nDelta; + nAlignM += nDelta; + nAlignB += nDelta; + nGlyphTop += nDelta; + nGlyphBottom += nDelta; + nHiAttrFence += nDelta; + nLoAttrFence += nDelta; +} + + +Point SmRect::AlignTo(const SmRect &rRect, RectPos ePos, + RectHorAlign eHor, RectVerAlign eVer) const +{ Point aPos (GetTopLeft()); + // will become the topleft point of the new rectangle position + + // set horizontal or vertical new rectangle position depending on ePos + switch (ePos) + { case RectPos::Left: + aPos.setX( rRect.GetItalicLeft() - GetItalicRightSpace() + - GetWidth() ); + break; + case RectPos::Right: + aPos.setX( rRect.GetItalicRight() + 1 + GetItalicLeftSpace() ); + break; + case RectPos::Top: + aPos.setY( rRect.GetTop() - GetHeight() ); + break; + case RectPos::Bottom: + aPos.setY( rRect.GetBottom() + 1 ); + break; + case RectPos::Attribute: + aPos.setX( rRect.GetItalicCenterX() - GetItalicWidth() / 2 + + GetItalicLeftSpace() ); + break; + default: + assert(false); + } + + // check if horizontal position is already set + if (ePos == RectPos::Left || ePos == RectPos::Right || ePos == RectPos::Attribute) + // correct error in current vertical position + switch (eVer) + { case RectVerAlign::Top : + aPos.AdjustY(rRect.GetAlignT() - GetAlignT() ); + break; + case RectVerAlign::Mid : + aPos.AdjustY(rRect.GetAlignM() - GetAlignM() ); + break; + case RectVerAlign::Baseline : + // align baselines if possible else align mid's + if (HasBaseline() && rRect.HasBaseline()) + aPos.AdjustY(rRect.GetBaseline() - GetBaseline() ); + else + aPos.AdjustY(rRect.GetAlignM() - GetAlignM() ); + break; + case RectVerAlign::Bottom : + aPos.AdjustY(rRect.GetAlignB() - GetAlignB() ); + break; + case RectVerAlign::CenterY : + aPos.AdjustY(rRect.GetCenterY() - GetCenterY() ); + break; + case RectVerAlign::AttributeHi: + aPos.AdjustY(rRect.GetHiAttrFence() - GetBottom() ); + break; + case RectVerAlign::AttributeMid : + aPos.AdjustY(SmFromTo(rRect.GetAlignB(), rRect.GetAlignT(), 0.4) + - GetCenterY() ); + break; + case RectVerAlign::AttributeLo : + aPos.AdjustY(rRect.GetLoAttrFence() - GetTop() ); + break; + default : + assert(false); + } + + // check if vertical position is already set + if (ePos == RectPos::Top || ePos == RectPos::Bottom) + // correct error in current horizontal position + switch (eHor) + { case RectHorAlign::Left: + aPos.AdjustX(rRect.GetItalicLeft() - GetItalicLeft() ); + break; + case RectHorAlign::Center: + aPos.AdjustX(rRect.GetItalicCenterX() - GetItalicCenterX() ); + break; + case RectHorAlign::Right: + aPos.AdjustX(rRect.GetItalicRight() - GetItalicRight() ); + break; + default: + assert(false); + } + + return aPos; +} + + +void SmRect::Union(const SmRect &rRect) + // rectangle union of current one with 'rRect'. The result is to be the + // smallest rectangles that covers the space of both rectangles. + // (empty rectangles cover no space) + //! Italic correction is NOT taken into account here! +{ + if (rRect.IsEmpty()) + return; + + long nL = rRect.GetLeft(), + nR = rRect.GetRight(), + nT = rRect.GetTop(), + nB = rRect.GetBottom(), + nGT = rRect.nGlyphTop, + nGB = rRect.nGlyphBottom; + if (!IsEmpty()) + { long nTmp; + + if ((nTmp = GetLeft()) < nL) + nL = nTmp; + if ((nTmp = GetRight()) > nR) + nR = nTmp; + if ((nTmp = GetTop()) < nT) + nT = nTmp; + if ((nTmp = GetBottom()) > nB) + nB = nTmp; + if ((nTmp = nGlyphTop) < nGT) + nGT = nTmp; + if ((nTmp = nGlyphBottom) > nGB) + nGB = nTmp; + } + + SetLeft(nL); + SetRight(nR); + SetTop(nT); + SetBottom(nB); + nGlyphTop = nGT; + nGlyphBottom = nGB; +} + + +SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode) + // let current rectangle be the union of itself and 'rRect' + // (the smallest rectangle surrounding both). Also adapt values for + // 'AlignT', 'AlignM', 'AlignB', baseline and italic-spaces. + // The baseline is set according to 'eCopyMode'. + // If one of the rectangles has no relevant info the other one is copied. +{ + // get some values used for (italic) spaces adaptation + // ! (need to be done before changing current SmRect) ! + long nL = std::min(GetItalicLeft(), rRect.GetItalicLeft()), + nR = std::max(GetItalicRight(), rRect.GetItalicRight()); + + Union(rRect); + + SetItalicSpaces(GetLeft() - nL, nR - GetRight()); + + if (!HasAlignInfo()) + CopyAlignInfo(rRect); + else if (rRect.HasAlignInfo()) + { + assert(HasAlignInfo()); + nAlignT = std::min(GetAlignT(), rRect.GetAlignT()); + nAlignB = std::max(GetAlignB(), rRect.GetAlignB()); + nHiAttrFence = std::min(GetHiAttrFence(), rRect.GetHiAttrFence()); + nLoAttrFence = std::max(GetLoAttrFence(), rRect.GetLoAttrFence()); + + switch (eCopyMode) + { case RectCopyMBL::This: + // already done + break; + case RectCopyMBL::Arg: + CopyMBL(rRect); + break; + case RectCopyMBL::None: + bHasBaseline = false; + nAlignM = (nAlignT + nAlignB) / 2; + break; + case RectCopyMBL::Xor: + if (!HasBaseline()) + CopyMBL(rRect); + break; + default : + assert(false); + } + } + + return *this; +} + + +void SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode, + long nNewAlignM) + // as 'ExtendBy' but sets AlignM value to 'nNewAlignM'. + // (this version will be used in 'SmBinVerNode' to provide means to + // align eg "{a over b} over c" correctly where AlignM should not + // be (AlignT + AlignB) / 2) +{ + OSL_ENSURE(HasAlignInfo(), "Sm: no align info"); + + ExtendBy(rRect, eCopyMode); + nAlignM = nNewAlignM; +} + + +SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode, + bool bKeepVerAlignParams) + // as 'ExtendBy' but keeps original values for AlignT, -M and -B and + // baseline. + // (this is used in 'SmSupSubNode' where the sub-/supscripts shouldn't + // be allowed to modify these values.) +{ + long nOldAlignT = GetAlignT(), + nOldAlignM = GetAlignM(), + nOldAlignB = GetAlignB(), + nOldBaseline = nBaseline; //! depends not on 'HasBaseline' + bool bOldHasAlignInfo = HasAlignInfo(); + + ExtendBy(rRect, eCopyMode); + + if (bKeepVerAlignParams) + { nAlignT = nOldAlignT; + nAlignM = nOldAlignM; + nAlignB = nOldAlignB; + nBaseline = nOldBaseline; + bHasAlignInfo = bOldHasAlignInfo; + } + + return *this; +} + + +long SmRect::OrientedDist(const Point &rPoint) const + // return oriented distance of rPoint to the current rectangle, + // especially the return value is <= 0 iff the point is inside the + // rectangle. + // For simplicity the maximum-norm is used. +{ + bool bIsInside = IsInsideItalicRect(rPoint); + + // build reference point to define the distance + Point aRef; + if (bIsInside) + { Point aIC (GetItalicCenterX(), GetCenterY()); + + aRef.setX( rPoint.X() >= aIC.X() ? GetItalicRight() : GetItalicLeft() ); + aRef.setY( rPoint.Y() >= aIC.Y() ? GetBottom() : GetTop() ); + } + else + { + // x-coordinate + if (rPoint.X() > GetItalicRight()) + aRef.setX( GetItalicRight() ); + else if (rPoint.X() < GetItalicLeft()) + aRef.setX( GetItalicLeft() ); + else + aRef.setX( rPoint.X() ); + // y-coordinate + if (rPoint.Y() > GetBottom()) + aRef.setY( GetBottom() ); + else if (rPoint.Y() < GetTop()) + aRef.setY( GetTop() ); + else + aRef.setY( rPoint.Y() ); + } + + // build distance vector + Point aDist (aRef - rPoint); + + long nAbsX = labs(aDist.X()), + nAbsY = labs(aDist.Y()); + + return bIsInside ? - std::min(nAbsX, nAbsY) : std::max (nAbsX, nAbsY); +} + + +bool SmRect::IsInsideRect(const Point &rPoint) const +{ + return rPoint.Y() >= GetTop() + && rPoint.Y() <= GetBottom() + && rPoint.X() >= GetLeft() + && rPoint.X() <= GetRight(); +} + + +bool SmRect::IsInsideItalicRect(const Point &rPoint) const +{ + return rPoint.Y() >= GetTop() + && rPoint.Y() <= GetBottom() + && rPoint.X() >= GetItalicLeft() + && rPoint.X() <= GetItalicRight(); +} + +SmRect SmRect::AsGlyphRect() const +{ + SmRect aRect (*this); + aRect.SetTop(nGlyphTop); + aRect.SetBottom(nGlyphBottom); + return aRect; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/register.cxx b/starmath/source/register.cxx new file mode 100644 index 000000000..89f7c2dd3 --- /dev/null +++ b/starmath/source/register.cxx @@ -0,0 +1,72 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <rtl/ustring.hxx> + +#include <sfx2/sfxmodelfactory.hxx> + +#include <com/sun/star/lang/XSingleServiceFactory.hpp> + +#include "register.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + +extern "C" { + +SAL_DLLPUBLIC_EXPORT void* sm_component_getFactory( const char* pImplementationName, + void* pServiceManager, + void* /*pRegistryKey*/ ) +{ + // Set default return value for this operation - if it failed. + void* pReturn = nullptr ; + + if ( + ( pImplementationName != nullptr ) && + ( pServiceManager != nullptr ) + ) + { + // Define variables which are used in following macros. + Reference< XSingleServiceFactory > xFactory ; + Reference< XMultiServiceFactory > xServiceManager( static_cast< XMultiServiceFactory* >( pServiceManager ) ) ; + + if (SmDocument_getImplementationName().equalsAscii(pImplementationName)) + { + xFactory = ::sfx2::createSfxModelFactory( xServiceManager, + SmDocument_getImplementationName(), + SmDocument_createInstance, + SmDocument_getSupportedServiceNames() ); + } + + // Factory is valid - service was found. + if ( xFactory.is() ) + { + xFactory->acquire(); + pReturn = xFactory.get(); + } + } + + // Return with result of this operation. + return pReturn ; +} +} // extern "C" + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/register.hxx b/starmath/source/register.hxx new file mode 100644 index 000000000..be875fbec --- /dev/null +++ b/starmath/source/register.hxx @@ -0,0 +1,37 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_REGISTER_HXX +#define INCLUDED_STARMATH_SOURCE_REGISTER_HXX + +#include <sal/config.h> +#include <sfx2/sfxmodelfactory.hxx> + +//Math document +css::uno::Sequence< OUString > + SmDocument_getSupportedServiceNames() throw(); +OUString + SmDocument_getImplementationName() throw(); +/// @throws css::uno::Exception +css::uno::Reference< css::uno::XInterface > + SmDocument_createInstance(const css::uno::Reference< css::lang::XMultiServiceFactory > & rSMgr, SfxModelFlags _nCreationFlags); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/rtfexport.cxx b/starmath/source/rtfexport.cxx new file mode 100644 index 000000000..87e51a3b9 --- /dev/null +++ b/starmath/source/rtfexport.cxx @@ -0,0 +1,505 @@ +/* -*- 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 "rtfexport.hxx" +#include <node.hxx> + +#include <svtools/rtfkeywd.hxx> +#include <filter/msfilter/rtfutil.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +SmRtfExport::SmRtfExport(const SmNode* pIn) + : SmWordExportBase(pIn) + , m_pBuffer(nullptr) + , m_nEncoding(RTL_TEXTENCODING_DONTKNOW) +{ +} + +void SmRtfExport::ConvertFromStarMath(OStringBuffer& rBuffer, rtl_TextEncoding nEncoding) +{ + if (!m_pTree) + return; + m_pBuffer = &rBuffer; + m_nEncoding = nEncoding; + m_pBuffer->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE LO_STRING_SVTOOLS_RTF_MOMATH " "); + HandleNode(m_pTree, 0); + m_pBuffer->append("}"); // moMath +} + +// NOTE: This is still work in progress and unfinished, but it already covers a good +// part of the rtf math stuff. + +void SmRtfExport::HandleVerticalStack(const SmNode* pNode, int nLevel) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MEQARR " "); + int size = pNode->GetNumSubNodes(); + for (int i = 0; i < size; ++i) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->GetSubNode(i), nLevel + 1); + m_pBuffer->append("}"); // me + } + m_pBuffer->append("}"); // meqArr +} + +void SmRtfExport::HandleText(const SmNode* pNode, int /*nLevel*/) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MR " "); + + if (pNode->GetToken().eType == TTEXT) // literal text + m_pBuffer->append(LO_STRING_SVTOOLS_RTF_MNOR " "); + + auto pTemp = static_cast<const SmTextNode*>(pNode); + SAL_INFO("starmath.rtf", "Text: " << pTemp->GetText()); + for (sal_Int32 i = 0; i < pTemp->GetText().getLength(); i++) + { + sal_uInt16 nChar = pTemp->GetText()[i]; + OUString aValue(SmTextNode::ConvertSymbolToUnicode(nChar)); + m_pBuffer->append(msfilter::rtfutil::OutString(aValue, m_nEncoding)); + } + + m_pBuffer->append("}"); // mr +} + +void SmRtfExport::HandleFractions(const SmNode* pNode, int nLevel, const char* type) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MF " "); + if (type) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MFPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MTYPE " "); + m_pBuffer->append(type); + m_pBuffer->append("}"); // mtype + m_pBuffer->append("}"); // mfPr + } + assert(pNode->GetNumSubNodes() == 3); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MNUM " "); + HandleNode(pNode->GetSubNode(0), nLevel + 1); + m_pBuffer->append("}"); // mnum + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDEN " "); + HandleNode(pNode->GetSubNode(2), nLevel + 1); + m_pBuffer->append("}"); // mden + m_pBuffer->append("}"); // mf +} + +void SmRtfExport::HandleAttribute(const SmAttributNode* pNode, int nLevel) +{ + switch (pNode->Attribute()->GetToken().eType) + { + case TCHECK: + case TACUTE: + case TGRAVE: + case TBREVE: + case TCIRCLE: + case TVEC: + case TTILDE: + case THAT: + case TDOT: + case TDDOT: + case TDDDOT: + case TWIDETILDE: + case TWIDEHAT: + case TWIDEHARPOON: + case TWIDEVEC: + case TBAR: + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MACC " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MACCPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MCHR " "); + OUString aValue(pNode->Attribute()->GetToken().cMathChar); + m_pBuffer->append(msfilter::rtfutil::OutString(aValue, m_nEncoding)); + m_pBuffer->append("}"); // mchr + m_pBuffer->append("}"); // maccPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // macc + break; + } + case TOVERLINE: + case TUNDERLINE: + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBAR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBARPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MPOS " "); + m_pBuffer->append((pNode->Attribute()->GetToken().eType == TUNDERLINE) ? "bot" : "top"); + m_pBuffer->append("}"); // mpos + m_pBuffer->append("}"); // mbarPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mbar + break; + case TOVERSTRIKE: + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBORDERBOX " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBORDERBOXPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MHIDETOP " 1}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MHIDEBOT " 1}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MHIDELEFT " 1}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MHIDERIGHT " 1}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSTRIKEH " 1}"); + m_pBuffer->append("}"); // mborderBoxPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mborderBox + break; + default: + HandleAllSubNodes(pNode, nLevel); + break; + } +} + +void SmRtfExport::HandleRoot(const SmRootNode* pNode, int nLevel) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MRAD " "); + if (const SmNode* argument = pNode->Argument()) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDEG " "); + HandleNode(argument, nLevel + 1); + m_pBuffer->append("}"); // mdeg + } + else + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MRADPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDEGHIDE " 1}"); + m_pBuffer->append("}"); // mradPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDEG " }"); // empty but present + } + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mrad +} + +namespace +{ +OString mathSymbolToString(const SmNode* node, rtl_TextEncoding nEncoding) +{ + assert(node->GetType() == SmNodeType::Math || node->GetType() == SmNodeType::MathIdent); + auto txtnode = static_cast<const SmTextNode*>(node); + if (txtnode->GetText().isEmpty()) + return OString(); + assert(txtnode->GetText().getLength() == 1); + sal_Unicode chr = SmTextNode::ConvertSymbolToUnicode(txtnode->GetText()[0]); + OUString aValue(chr); + return msfilter::rtfutil::OutString(aValue, nEncoding); +} +} + +void SmRtfExport::HandleOperator(const SmOperNode* pNode, int nLevel) +{ + SAL_INFO("starmath.rtf", "Operator: " << int(pNode->GetToken().eType)); + switch (pNode->GetToken().eType) + { + case TINT: + case TINTD: + case TIINT: + case TIIINT: + case TLINT: + case TLLINT: + case TLLLINT: + case TPROD: + case TCOPROD: + case TSUM: + { + const SmSubSupNode* subsup + = pNode->GetSubNode(0)->GetType() == SmNodeType::SubSup + ? static_cast<const SmSubSupNode*>(pNode->GetSubNode(0)) + : nullptr; + const SmNode* operation = subsup ? subsup->GetBody() : pNode->GetSubNode(0); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MNARY " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MNARYPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MCHR " "); + m_pBuffer->append(mathSymbolToString(operation, m_nEncoding)); + m_pBuffer->append("}"); // mchr + if (!subsup || !subsup->GetSubSup(CSUB)) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUBHIDE " 1}"); + if (!subsup || !subsup->GetSubSup(CSUP)) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUPHIDE " 1}"); + m_pBuffer->append("}"); // mnaryPr + if (!subsup || !subsup->GetSubSup(CSUB)) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " }"); + else + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " "); + HandleNode(subsup->GetSubSup(CSUB), nLevel + 1); + m_pBuffer->append("}"); // msub + } + if (!subsup || !subsup->GetSubSup(CSUP)) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " }"); + else + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " "); + HandleNode(subsup->GetSubSup(CSUP), nLevel + 1); + m_pBuffer->append("}"); // msup + } + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->GetSubNode(1), nLevel + 1); // body + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mnary + break; + } + case TLIM: + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MFUNC " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MFNAME " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMLOW " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->GetSymbol(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIM " "); + if (const SmSubSupNode* subsup + = pNode->GetSubNode(0)->GetType() == SmNodeType::SubSup + ? static_cast<const SmSubSupNode*>(pNode->GetSubNode(0)) + : nullptr) + if (subsup->GetSubSup(CSUB)) + HandleNode(subsup->GetSubSup(CSUB), nLevel + 1); + m_pBuffer->append("}"); // mlim + m_pBuffer->append("}"); // mlimLow + m_pBuffer->append("}"); // mfName + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->GetSubNode(1), nLevel + 1); // body + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mfunc + break; + default: + SAL_INFO("starmath.rtf", "TODO: " << OSL_THIS_FUNC << " unhandled oper type"); + break; + } +} + +void SmRtfExport::HandleSubSupScriptInternal(const SmSubSupNode* pNode, int nLevel, int flags) +{ + // rtf supports only a certain combination of sub/super scripts, but LO can have any, + // so try to merge it using several tags if necessary + if (flags == 0) // none + return; + if ((flags & (1 << RSUP | 1 << RSUB)) == (1 << RSUP | 1 << RSUB)) + { + // m:sSubSup + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSSUBSUP " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << RSUP | 1 << RSUB); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " "); + HandleNode(pNode->GetSubSup(RSUB), nLevel + 1); + m_pBuffer->append("}"); // msub + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " "); + HandleNode(pNode->GetSubSup(RSUP), nLevel + 1); + m_pBuffer->append("}"); // msup + m_pBuffer->append("}"); // msubSup + } + else if ((flags & (1 << RSUB)) == 1 << RSUB) + { + // m:sSub + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSSUB " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << RSUB); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " "); + HandleNode(pNode->GetSubSup(RSUB), nLevel + 1); + m_pBuffer->append("}"); // msub + m_pBuffer->append("}"); // msSub + } + else if ((flags & (1 << RSUP)) == 1 << RSUP) + { + // m:sSup + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSSUP " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << RSUP); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " "); + HandleNode(pNode->GetSubSup(RSUP), nLevel + 1); + m_pBuffer->append("}"); // msup + m_pBuffer->append("}"); // msSup + } + else if ((flags & (1 << LSUP | 1 << LSUB)) == (1 << LSUP | 1 << LSUB)) + { + // m:sPre + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSPRE " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " "); + HandleNode(pNode->GetSubSup(LSUB), nLevel + 1); + m_pBuffer->append("}"); // msub + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " "); + HandleNode(pNode->GetSubSup(LSUP), nLevel + 1); + m_pBuffer->append("}"); // msup + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << LSUP | 1 << LSUB); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // msPre + } + else if ((flags & (1 << CSUB)) == (1 << CSUB)) + { + // m:limLow looks like a good element for central superscript + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMLOW " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << CSUB); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIM " "); + HandleNode(pNode->GetSubSup(CSUB), nLevel + 1); + m_pBuffer->append("}"); // mlim + m_pBuffer->append("}"); // mlimLow + } + else if ((flags & (1 << CSUP)) == (1 << CSUP)) + { + // m:limUpp looks like a good element for central superscript + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMUPP " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << CSUP); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIM " "); + HandleNode(pNode->GetSubSup(CSUP), nLevel + 1); + m_pBuffer->append("}"); // mlim + m_pBuffer->append("}"); // mlimUpp + } + else + SAL_INFO("starmath.rtf", "TODO: " << OSL_THIS_FUNC << " unhandled subsup type"); +} + +void SmRtfExport::HandleMatrix(const SmMatrixNode* pNode, int nLevel) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MM " "); + for (size_t row = 0; row < pNode->GetNumRows(); ++row) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MMR " "); + for (size_t col = 0; col < pNode->GetNumCols(); ++col) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + if (const SmNode* node = pNode->GetSubNode(row * pNode->GetNumCols() + col)) + HandleNode(node, nLevel + 1); + m_pBuffer->append("}"); // me + } + m_pBuffer->append("}"); // mmr + } + m_pBuffer->append("}"); // mm +} + +void SmRtfExport::HandleBrace(const SmBraceNode* pNode, int nLevel) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MD " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBEGCHR " "); + m_pBuffer->append(mathSymbolToString(pNode->OpeningBrace(), m_nEncoding)); + m_pBuffer->append("}"); // mbegChr + std::vector<const SmNode*> subnodes; + if (pNode->Body()->GetType() == SmNodeType::Bracebody) + { + auto body = static_cast<const SmBracebodyNode*>(pNode->Body()); + bool separatorWritten = false; // assume all separators are the same + for (size_t i = 0; i < body->GetNumSubNodes(); ++i) + { + const SmNode* subnode = body->GetSubNode(i); + if (subnode->GetType() == SmNodeType::Math + || subnode->GetType() == SmNodeType::MathIdent) + { + // do not write, but write what separator it is + auto math = static_cast<const SmMathSymbolNode*>(subnode); + if (!separatorWritten) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSEPCHR " "); + m_pBuffer->append(mathSymbolToString(math, m_nEncoding)); + m_pBuffer->append("}"); // msepChr + separatorWritten = true; + } + } + else + subnodes.push_back(subnode); + } + } + else + subnodes.push_back(pNode->Body()); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MENDCHR " "); + m_pBuffer->append(mathSymbolToString(pNode->ClosingBrace(), m_nEncoding)); + m_pBuffer->append("}"); // mendChr + m_pBuffer->append("}"); // mdPr + for (const SmNode* subnode : subnodes) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(subnode, nLevel + 1); + m_pBuffer->append("}"); // me + } + m_pBuffer->append("}"); // md +} + +void SmRtfExport::HandleVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) +{ + SAL_INFO("starmath.rtf", "Vertical: " << int(pNode->GetToken().eType)); + switch (pNode->GetToken().eType) + { + case TOVERBRACE: + case TUNDERBRACE: + { + bool top = (pNode->GetToken().eType == TOVERBRACE); + if (top) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMUPP " "); + else + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMLOW " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MGROUPCHR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MGROUPCHRPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MCHR " "); + m_pBuffer->append(mathSymbolToString(pNode->Brace(), m_nEncoding)); + m_pBuffer->append("}"); // mchr + // TODO not sure if pos and vertJc are correct + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MPOS " ") + .append(top ? "top" : "bot") + .append("}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MVERTJC " ") + .append(top ? "bot" : "top") + .append("}"); + m_pBuffer->append("}"); // mgroupChrPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mgroupChr + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIM " "); + HandleNode(pNode->Script(), nLevel + 1); + m_pBuffer->append("}"); // mlim + m_pBuffer->append("}"); // mlimUpp or mlimLow + break; + } + default: + SAL_INFO("starmath.rtf", "TODO: " << OSL_THIS_FUNC << " unhandled vertical brace type"); + break; + } +} + +void SmRtfExport::HandleBlank() +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MR " "); + m_pBuffer->append(" "); + m_pBuffer->append("}"); // mr +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/rtfexport.hxx b/starmath/source/rtfexport.hxx new file mode 100644 index 000000000..3a1dd4feb --- /dev/null +++ b/starmath/source/rtfexport.hxx @@ -0,0 +1,45 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_STARMATH_SOURCE_RTFEXPORT_HXX +#define INCLUDED_STARMATH_SOURCE_RTFEXPORT_HXX + +#include "wordexportbase.hxx" + +#include <rtl/strbuf.hxx> + +/** + Class implementing writing of formulas to RTF. + */ +class SmRtfExport : public SmWordExportBase +{ +public: + explicit SmRtfExport(const SmNode* pIn); + void ConvertFromStarMath(OStringBuffer& rBuffer, rtl_TextEncoding nEncoding); + +private: + void HandleVerticalStack(const SmNode* pNode, int nLevel) override; + void HandleText(const SmNode* pNode, int nLevel) override; + void HandleFractions(const SmNode* pNode, int nLevel, const char* type) override; + void HandleRoot(const SmRootNode* pNode, int nLevel) override; + void HandleAttribute(const SmAttributNode* pNode, int nLevel) override; + void HandleOperator(const SmOperNode* pNode, int nLevel) override; + void HandleSubSupScriptInternal(const SmSubSupNode* pNode, int nLevel, int flags) override; + void HandleMatrix(const SmMatrixNode* pNode, int nLevel) override; + void HandleBrace(const SmBraceNode* pNode, int nLevel) override; + void HandleVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) override; + void HandleBlank() override; + + OStringBuffer* m_pBuffer; + rtl_TextEncoding m_nEncoding; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/smdetect.cxx b/starmath/source/smdetect.cxx new file mode 100644 index 000000000..aa6280156 --- /dev/null +++ b/starmath/source/smdetect.cxx @@ -0,0 +1,149 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "smdetect.hxx" +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <sfx2/docfile.hxx> +#include <unotools/mediadescriptor.hxx> +#include <sal/log.hxx> +#include <sot/storage.hxx> +#include <tools/diagnose_ex.h> + +#include "eqnolefilehdr.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using utl::MediaDescriptor; + +SmFilterDetect::SmFilterDetect() +{ +} + +SmFilterDetect::~SmFilterDetect() +{ +} + +OUString SAL_CALL SmFilterDetect::detect( Sequence< PropertyValue >& lDescriptor ) +{ + MediaDescriptor aMediaDesc( lDescriptor ); + uno::Reference< io::XInputStream > xInStream ( aMediaDesc[MediaDescriptor::PROP_INPUTSTREAM()], uno::UNO_QUERY ); + if ( !xInStream.is() ) + return OUString(); + + SfxMedium aMedium; + aMedium.UseInteractionHandler( false ); + aMedium.setStreamToLoadFrom( xInStream, true ); + + SvStream *pInStrm = aMedium.GetInStream(); + if ( !pInStrm || pInStrm->GetError() ) + return OUString(); + + // Do not attempt to create an SotStorage on a + // 0-length stream as that would create the compound + // document header on the stream and effectively write to + // disk! + pInStrm->Seek( STREAM_SEEK_TO_BEGIN ); + if ( pInStrm->remainingSize() == 0 ) + return OUString(); + + bool bStorageOk = false; + try + { + tools::SvRef<SotStorage> aStorage = new SotStorage( pInStrm, false ); + bStorageOk = !aStorage->GetError(); + if (bStorageOk) + { + if ( aStorage->IsStream("Equation Native") ) + { + sal_uInt8 nVersion; + if ( GetMathTypeVersion( aStorage.get(), nVersion ) && nVersion <=3 ) + return "math_MathType_3x"; + } + } + } + catch (const css::ucb::ContentCreationException &) + { + TOOLS_WARN_EXCEPTION("starmath", "SmFilterDetect::detect caught" ); + } + + if (!bStorageOk) + { + // 200 should be enough for the XML + // version, encoding and !DOCTYPE + // stuff I hope? + static const sal_uInt16 nBufferSize = 200; + char aBuffer[nBufferSize+1]; + pInStrm->Seek( STREAM_SEEK_TO_BEGIN ); + pInStrm->StartReadingUnicodeText( RTL_TEXTENCODING_DONTKNOW ); // avoid BOM marker + auto nBytesRead = pInStrm->ReadBytes( aBuffer, nBufferSize ); + if (nBytesRead >= 6) + { + aBuffer[nBytesRead] = 0; + bool bIsMathType = false; + if (0 == strncmp( "<?xml", aBuffer, 5)) + bIsMathType = (strstr( aBuffer, "<math>" ) || + strstr( aBuffer, "<math " ) || + strstr( aBuffer, "<math:math " )); + else + // this is the old <math tag to MathML in the beginning of the XML file + bIsMathType = (0 == strncmp( "<math ", aBuffer, 6) || + 0 == strncmp( "<math> ", aBuffer, 7) || + 0 == strncmp( "<math:math> ", aBuffer, 12)); + + if ( bIsMathType ) + return "math_MathML_XML_Math"; + } + } + + return OUString(); +} + +/* XServiceInfo */ +OUString SAL_CALL SmFilterDetect::getImplementationName() +{ + return "com.sun.star.comp.math.FormatDetector"; +} + +/* XServiceInfo */ +sal_Bool SAL_CALL SmFilterDetect::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +/* XServiceInfo */ +Sequence< OUString > SAL_CALL SmFilterDetect::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ExtendedTypeDetection" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +math_FormatDetector_get_implementation(uno::XComponentContext* /*pCtx*/, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new SmFilterDetect); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/smdetect.hxx b/starmath/source/smdetect.hxx new file mode 100644 index 000000000..7869146f4 --- /dev/null +++ b/starmath/source/smdetect.hxx @@ -0,0 +1,61 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_SMDETECT_HXX +#define INCLUDED_STARMATH_SOURCE_SMDETECT_HXX + +#include <rtl/ustring.hxx> +#include <com/sun/star/document/XExtendedFilterDetection.hpp> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> + + +namespace com +{ + namespace sun + { + namespace star + { + namespace beans + { + struct PropertyValue; + } + } + } +} + +class SmFilterDetect : public ::cppu::WeakImplHelper< css::document::XExtendedFilterDetection, css::lang::XServiceInfo > +{ +public: + explicit SmFilterDetect(); + virtual ~SmFilterDetect() override; + + /* XServiceInfo */ + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XExtendedFilterDetect + virtual OUString SAL_CALL detect( css::uno::Sequence< css::beans::PropertyValue >& lDescriptor ) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/smdll.cxx b/starmath/source/smdll.cxx new file mode 100644 index 000000000..78dc2183b --- /dev/null +++ b/starmath/source/smdll.cxx @@ -0,0 +1,88 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <svx/svxids.hrc> +#include <svx/modctrl.hxx> +#include <svx/zoomctrl.hxx> +#include <svx/zoomsliderctrl.hxx> +#include <sfx2/docfac.hxx> +#include <svx/lboxctrl.hxx> +#include <sfx2/app.hxx> + +#include <smdll.hxx> +#include <smmod.hxx> +#include <document.hxx> +#include <view.hxx> + +#include <ElementsDockingWindow.hxx> + +#include <starmath.hrc> + +#include <svx/xmlsecctrl.hxx> + +namespace +{ + class SmDLL + { + public: + SmDLL(); + }; + + SmDLL::SmDLL() + { + if ( SfxApplication::GetModule(SfxToolsModule::Math) ) // Module already active + return; + + SfxObjectFactory& rFactory = SmDocShell::Factory(); + + auto pUniqueModule = std::make_unique<SmModule>(&rFactory); + SmModule* pModule = pUniqueModule.get(); + SfxApplication::SetModule(SfxToolsModule::Math, std::move(pUniqueModule)); + + rFactory.SetDocumentServiceName( "com.sun.star.formula.FormulaProperties" ); + + SmModule::RegisterInterface(pModule); + SmDocShell::RegisterInterface(pModule); + SmViewShell::RegisterInterface(pModule); + + SmViewShell::RegisterFactory(SFX_INTERFACE_SFXAPP); + + SvxZoomStatusBarControl::RegisterControl(SID_ATTR_ZOOM, pModule); + SvxZoomSliderControl::RegisterControl(SID_ATTR_ZOOMSLIDER, pModule); + SvxModifyControl::RegisterControl(SID_TEXTSTATUS, pModule); + XmlSecStatusBarControl::RegisterControl(SID_SIGNATURE, pModule); + + SmCmdBoxWrapper::RegisterChildWindow(true); + SmElementsDockingWindowWrapper::RegisterChildWindow(true); + } + + struct theSmDLLInstance : public rtl::Static<SmDLL, theSmDLLInstance> {}; +} + +namespace SmGlobals +{ + void ensure() + { + theSmDLLInstance::get(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/smmod.cxx b/starmath/source/smmod.cxx new file mode 100644 index 000000000..788f5f5e2 --- /dev/null +++ b/starmath/source/smmod.cxx @@ -0,0 +1,235 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sfx2/objface.hxx> +#include <svl/whiter.hxx> +#include <sfx2/viewsh.hxx> +#include <svx/svxids.hrc> +#include <unotools/resmgr.hxx> +#include <vcl/virdev.hxx> +#include <unotools/syslocale.hxx> +#include <smmod.hxx> +#include "cfgitem.hxx" +#include <dialog.hxx> +#include <edit.hxx> +#include <view.hxx> +#include <smmod.hrc> +#include <starmath.hrc> +#include <svx/modctrl.hxx> +#include <svtools/colorcfg.hxx> + + +#define ShellClass_SmModule +#include <smslots.hxx> + +OUString SmResId(const char* pId) +{ + return Translate::get(pId, SM_MOD()->GetResLocale()); +} + +OUString SmLocalizedSymbolData::GetUiSymbolName( const OUString &rExportName ) +{ + OUString aRes; + + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UI_SYMBOL_NAMES); ++i) + { + if (rExportName.equalsAscii(strchr(RID_UI_SYMBOL_NAMES[i], '\004') + 1)) + { + aRes = SmResId(RID_UI_SYMBOL_NAMES[i]); + break; + } + } + + return aRes; +} + +OUString SmLocalizedSymbolData::GetExportSymbolName( const OUString &rUiName ) +{ + OUString aRes; + + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UI_SYMBOL_NAMES); ++i) + { + if (rUiName == SmResId(RID_UI_SYMBOL_NAMES[i])) + { + const char *pKey = strchr(RID_UI_SYMBOL_NAMES[i], '\004') + 1; + aRes = OUString(pKey, strlen(pKey), RTL_TEXTENCODING_UTF8); + break; + } + } + + return aRes; +} + +OUString SmLocalizedSymbolData::GetUiSymbolSetName( const OUString &rExportName ) +{ + OUString aRes; + + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UI_SYMBOLSET_NAMES); ++i) + { + if (rExportName.equalsAscii(strchr(RID_UI_SYMBOLSET_NAMES[i], '\004') + 1)) + { + aRes = SmResId(RID_UI_SYMBOLSET_NAMES[i]); + break; + } + } + + return aRes; +} + +OUString SmLocalizedSymbolData::GetExportSymbolSetName( const OUString &rUiName ) +{ + OUString aRes; + + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UI_SYMBOLSET_NAMES); ++i) + { + if (rUiName == SmResId(RID_UI_SYMBOLSET_NAMES[i])) + { + const char *pKey = strchr(RID_UI_SYMBOLSET_NAMES[i], '\004') + 1; + aRes = OUString(pKey, strlen(pKey), RTL_TEXTENCODING_UTF8); + break; + } + } + + return aRes; +} + +SFX_IMPL_INTERFACE(SmModule, SfxModule) + +void SmModule::InitInterface_Impl() +{ + GetStaticInterface()->RegisterStatusBar(StatusBarId::MathStatusBar); +} + +SmModule::SmModule(SfxObjectFactory* pObjFact) + : SfxModule("sm", {pObjFact}) +{ + SetName("StarMath"); + + SvxModifyControl::RegisterControl(SID_DOC_MODIFIED, this); +} + +SmModule::~SmModule() +{ + if (mpColorConfig) + mpColorConfig->RemoveListener(this); + mpVirtualDev.disposeAndClear(); +} + +svtools::ColorConfig & SmModule::GetColorConfig() +{ + if(!mpColorConfig) + { + mpColorConfig.reset(new svtools::ColorConfig); + mpColorConfig->AddListener(this); + } + return *mpColorConfig; +} + +void SmModule::ConfigurationChanged(utl::ConfigurationBroadcaster* pBrdCst, ConfigurationHints) +{ + if (pBrdCst == mpColorConfig.get()) + { + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (dynamic_cast<const SmViewShell *>(pViewShell) != nullptr) + pViewShell->GetWindow()->Invalidate(); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + } +} + +SmMathConfig * SmModule::GetConfig() +{ + if(!mpConfig) + mpConfig.reset(new SmMathConfig); + return mpConfig.get(); +} + +SmSymbolManager & SmModule::GetSymbolManager() +{ + return GetConfig()->GetSymbolManager(); +} + +const SvtSysLocale& SmModule::GetSysLocale() +{ + if( !mpSysLocale ) + mpSysLocale.reset(new SvtSysLocale); + return *mpSysLocale; +} + +VirtualDevice &SmModule::GetDefaultVirtualDev() +{ + if (!mpVirtualDev) + { + mpVirtualDev.reset( VclPtr<VirtualDevice>::Create() ); + mpVirtualDev->SetReferenceDevice( VirtualDevice::RefDevMode::MSO1 ); + } + return *mpVirtualDev; +} + +void SmModule::GetState(SfxItemSet &rSet) +{ + SfxWhichIter aIter(rSet); + + for (sal_uInt16 nWh = aIter.FirstWhich(); 0 != nWh; nWh = aIter.NextWhich()) + switch (nWh) + { + case SID_CONFIGEVENT : + rSet.DisableItem(SID_CONFIGEVENT); + break; + } +} + +std::unique_ptr<SfxItemSet> SmModule::CreateItemSet( sal_uInt16 nId ) +{ + std::unique_ptr<SfxItemSet> pRet; + if(nId == SID_SM_EDITOPTIONS) + { + pRet = std::make_unique<SfxItemSet>( + GetPool(), + svl::Items< //TP_SMPRINT + SID_PRINTTITLE, SID_PRINTZOOM, + SID_NO_RIGHT_SPACES, SID_SAVE_ONLY_USED_SYMBOLS, + SID_AUTO_CLOSE_BRACKETS, SID_AUTO_CLOSE_BRACKETS>{}); + + GetConfig()->ConfigToItemSet(*pRet); + } + return pRet; +} + +void SmModule::ApplyItemSet( sal_uInt16 nId, const SfxItemSet& rSet ) +{ + if(nId == SID_SM_EDITOPTIONS) + { + GetConfig()->ItemSetToConfig(rSet); + } +} + +std::unique_ptr<SfxTabPage> SmModule::CreateTabPage( sal_uInt16 nId, weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet ) +{ + std::unique_ptr<SfxTabPage> xRet; + if (nId == SID_SM_TP_PRINTOPTIONS) + xRet = SmPrintOptionsTabPage::Create(pPage, pController, rSet); + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/symbol.cxx b/starmath/source/symbol.cxx new file mode 100644 index 000000000..5e6a6486a --- /dev/null +++ b/starmath/source/symbol.cxx @@ -0,0 +1,274 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vector> + +#include <symbol.hxx> +#include <utility.hxx> +#include "cfgitem.hxx" +#include <smmod.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + + +SmSym::SmSym() : + m_aName(OUString("unknown")), + m_aSetName(OUString("unknown")), + m_cChar('\0'), + m_bPredefined(false) +{ + m_aExportName = m_aName; + m_aFace.SetTransparent(true); + m_aFace.SetAlignment(ALIGN_BASELINE); +} + + +SmSym::SmSym(const SmSym& rSymbol) +{ + *this = rSymbol; +} + + +SmSym::SmSym(const OUString& rName, const vcl::Font& rFont, sal_UCS4 cChar, + const OUString& rSet, bool bIsPredefined) +{ + m_aName = m_aExportName = rName; + + m_aFace = rFont; + m_aFace.SetTransparent(true); + m_aFace.SetAlignment(ALIGN_BASELINE); + + m_cChar = cChar; + m_aSetName = rSet; + m_bPredefined = bIsPredefined; +} + + +SmSym& SmSym::operator = (const SmSym& rSymbol) +{ + m_aName = rSymbol.m_aName; + m_aExportName = rSymbol.m_aExportName; + m_cChar = rSymbol.m_cChar; + m_aFace = rSymbol.m_aFace; + m_aSetName = rSymbol.m_aSetName; + m_bPredefined = rSymbol.m_bPredefined; + + SM_MOD()->GetSymbolManager().SetModified(true); + + return *this; +} + + +bool SmSym::IsEqualInUI( const SmSym& rSymbol ) const +{ + return m_aName == rSymbol.m_aName && + m_aFace == rSymbol.m_aFace && + m_cChar == rSymbol.m_cChar; +} + +/**************************************************************************/ + + +SmSymbolManager::SmSymbolManager() +{ + m_bModified = false; +} + + +SmSymbolManager::SmSymbolManager(const SmSymbolManager& rSymbolSetManager) +{ + m_aSymbols = rSymbolSetManager.m_aSymbols; + m_bModified = true; +} + + +SmSymbolManager::~SmSymbolManager() +{ +} + + +SmSymbolManager& SmSymbolManager::operator = (const SmSymbolManager& rSymbolSetManager) +{ + m_aSymbols = rSymbolSetManager.m_aSymbols; + m_bModified = true; + return *this; +} + + +SmSym *SmSymbolManager::GetSymbolByName(const OUString& rSymbolName) +{ + SmSym *pRes = nullptr; + SymbolMap_t::iterator aIt( m_aSymbols.find( rSymbolName ) ); + if (aIt != m_aSymbols.end()) + pRes = &aIt->second; + return pRes; +} + + +SymbolPtrVec_t SmSymbolManager::GetSymbols() const +{ + SymbolPtrVec_t aRes; + for (const auto& rEntry : m_aSymbols) + aRes.push_back( &rEntry.second ); +// OSL_ENSURE( sSymbols.size() == m_aSymbols.size(), "number of symbols mismatch " ); + return aRes; +} + + +bool SmSymbolManager::AddOrReplaceSymbol( const SmSym &rSymbol, bool bForceChange ) +{ + bool bAdded = false; + + const OUString& aSymbolName( rSymbol.GetName() ); + if (!aSymbolName.isEmpty() && !rSymbol.GetSymbolSetName().isEmpty()) + { + const SmSym *pFound = GetSymbolByName( aSymbolName ); + const bool bSymbolConflict = pFound && !pFound->IsEqualInUI( rSymbol ); + + // avoid having the same symbol name twice but with different symbols in use + if (!pFound || bForceChange) + { + m_aSymbols[ aSymbolName ] = rSymbol; + bAdded = true; + } + else if (bSymbolConflict) + { + // TODO: to solve this a document owned symbol manager would be required ... + SAL_WARN("starmath", "symbol conflict, different symbol with same name found!"); + // symbols in all formulas. A copy of the global one would be needed here + // and then the new symbol has to be forcefully applied. This would keep + // the current formula intact but will leave the set of symbols in the + // global symbol manager somewhat to chance. + } + + OSL_ENSURE( bAdded, "failed to add symbol" ); + if (bAdded) + m_bModified = true; + OSL_ENSURE( bAdded || (pFound && !bSymbolConflict), "AddOrReplaceSymbol: unresolved symbol conflict" ); + } + + return bAdded; +} + + +void SmSymbolManager::RemoveSymbol( const OUString & rSymbolName ) +{ + if (!rSymbolName.isEmpty()) + { + size_t nOldSize = m_aSymbols.size(); + m_aSymbols.erase( rSymbolName ); + m_bModified = nOldSize != m_aSymbols.size(); + } +} + + +std::set< OUString > SmSymbolManager::GetSymbolSetNames() const +{ + std::set< OUString > aRes; + for (const auto& rEntry : m_aSymbols) + aRes.insert( rEntry.second.GetSymbolSetName() ); + return aRes; +} + + +SymbolPtrVec_t SmSymbolManager::GetSymbolSet( const OUString& rSymbolSetName ) +{ + SymbolPtrVec_t aRes; + if (!rSymbolSetName.isEmpty()) + { + for (const auto& rEntry : m_aSymbols) + { + if (rEntry.second.GetSymbolSetName() == rSymbolSetName) + aRes.push_back( &rEntry.second ); + } + } + return aRes; +} + + +void SmSymbolManager::Load() +{ + std::vector< SmSym > aSymbols; + SmMathConfig &rCfg = *SM_MOD()->GetConfig(); + rCfg.GetSymbols( aSymbols ); + size_t nSymbolCount = aSymbols.size(); + + m_aSymbols.clear(); + for (size_t i = 0; i < nSymbolCount; ++i) + { + const SmSym &rSym = aSymbols[i]; + OSL_ENSURE( !rSym.GetName().isEmpty(), "symbol without name!" ); + if (!rSym.GetName().isEmpty()) + AddOrReplaceSymbol( rSym ); + } + m_bModified = true; + + if (0 == nSymbolCount) + { + SAL_WARN("starmath", "no symbol set found"); + m_bModified = false; + } + + // now add a %i... symbol to the 'iGreek' set for every symbol found in the 'Greek' set. + const OUString aGreekSymbolSetName(SmLocalizedSymbolData::GetUiSymbolSetName("Greek")); + const SymbolPtrVec_t aGreekSymbols( GetSymbolSet( aGreekSymbolSetName ) ); + OUString aSymbolSetName = "i" + aGreekSymbolSetName; + size_t nSymbols = aGreekSymbols.size(); + for (size_t i = 0; i < nSymbols; ++i) + { + // make the new symbol a copy but with ITALIC_NORMAL, and add it to iGreek + const SmSym &rSym = *aGreekSymbols[i]; + vcl::Font aFont( rSym.GetFace() ); + OSL_ENSURE( aFont.GetItalic() == ITALIC_NONE, "expected Font with ITALIC_NONE, failed." ); + aFont.SetItalic( ITALIC_NORMAL ); + OUString aSymbolName = "i" + rSym.GetName(); + SmSym aSymbol( aSymbolName, aFont, rSym.GetCharacter(), + aSymbolSetName, true /*bIsPredefined*/ ); + + AddOrReplaceSymbol( aSymbol ); + } +} + +void SmSymbolManager::Save() +{ + if (!m_bModified) + return; + + SmMathConfig &rCfg = *SM_MOD()->GetConfig(); + + // prepare to skip symbols from iGreek on saving + OUString aSymbolSetName = "i" + + SmLocalizedSymbolData::GetUiSymbolSetName("Greek"); + + SymbolPtrVec_t aTmp( GetSymbols() ); + std::vector< SmSym > aSymbols; + for (const SmSym* i : aTmp) + { + // skip symbols from iGreek set since those symbols always get added + // by computational means in SmSymbolManager::Load + if (i->GetSymbolSetName() != aSymbolSetName) + aSymbols.push_back( *i ); + } + rCfg.SetSymbols( aSymbols ); + + m_bModified = false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/tmpdevice.cxx b/starmath/source/tmpdevice.cxx new file mode 100644 index 000000000..074903d3c --- /dev/null +++ b/starmath/source/tmpdevice.cxx @@ -0,0 +1,66 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <smmod.hxx> +#include "tmpdevice.hxx" + +#include <svtools/colorcfg.hxx> +#include <vcl/window.hxx> +#include <sal/log.hxx> + +// SmTmpDevice +// Allows for font and color changes. The original settings will be restored +// in the destructor. +// It's main purpose is to allow for the "const" in the 'OutputDevice' +// argument in the 'Arrange' functions and restore changes made in the 'Draw' +// functions. +// Usually a MapMode of 1/100th mm will be used. + +SmTmpDevice::SmTmpDevice(OutputDevice &rTheDev, bool bUseMap100th_mm) : + rOutDev(rTheDev) +{ + rOutDev.Push(PushFlags::FONT | PushFlags::MAPMODE | + PushFlags::LINECOLOR | PushFlags::FILLCOLOR | PushFlags::TEXTCOLOR); + if (bUseMap100th_mm && MapUnit::Map100thMM != rOutDev.GetMapMode().GetMapUnit()) + { + SAL_WARN("starmath", "incorrect MapMode?"); + rOutDev.SetMapMode(MapMode(MapUnit::Map100thMM)); // format for 100% always + } +} + + +Color SmTmpDevice::GetTextColor(const Color& rTextColor) +{ + if (rTextColor == COL_AUTO) + { + Color aConfigFontColor = SM_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + return rOutDev.GetReadableFontColor(aConfigFontColor, rOutDev.GetBackgroundColor()); + } + + return rTextColor; +} + + +void SmTmpDevice::SetFont(const vcl::Font &rNewFont) +{ + rOutDev.SetFont(rNewFont); + rOutDev.SetTextColor(GetTextColor(rNewFont.GetColor())); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/tmpdevice.hxx b/starmath/source/tmpdevice.hxx new file mode 100644 index 000000000..e7c812cd6 --- /dev/null +++ b/starmath/source/tmpdevice.hxx @@ -0,0 +1,49 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_STARMATH_SOURCE_TMPDEVICE_HXX +#define INCLUDED_STARMATH_SOURCE_TMPDEVICE_HXX + +#include <tools/color.hxx> +#include <vcl/outdev.hxx> + +class SmTmpDevice +{ + OutputDevice &rOutDev; + + SmTmpDevice(const SmTmpDevice&) = delete; + SmTmpDevice& operator=(const SmTmpDevice&) = delete; + + Color GetTextColor(const Color& rTextColor); + +public: + SmTmpDevice(OutputDevice &rTheDev, bool bUseMap100th_mm); + ~SmTmpDevice() COVERITY_NOEXCEPT_FALSE { rOutDev.Pop(); } + + void SetFont(const vcl::Font &rNewFont); + + void SetLineColor(const Color& rColor) { rOutDev.SetLineColor(GetTextColor(rColor)); } + void SetFillColor(const Color& rColor) { rOutDev.SetFillColor(GetTextColor(rColor)); } + + operator OutputDevice & () { return rOutDev; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/typemap.cxx b/starmath/source/typemap.cxx new file mode 100644 index 000000000..ba7910747 --- /dev/null +++ b/starmath/source/typemap.cxx @@ -0,0 +1,39 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_options.h> + +#include <sfx2/msg.hxx> +#include <sfx2/zoomitem.hxx> +#include <svx/zoomslideritem.hxx> +#include <svl/slstitm.hxx> + +#ifdef DISABLE_DYNLOADING +/* Avoid clash with the ones from svx/source/form/typemap.cxx */ +#define aSfxInt16Item_Impl starmath_source_appl_typemap_aSfxInt16Item_Impl +#endif + +#define SFX_TYPEMAP +#include <smslots.hxx> + +#ifdef DISABLE_DYNLOADING +#undef aSfxInt16Item_Impl +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/uiobject.cxx b/starmath/source/uiobject.cxx new file mode 100644 index 000000000..a6f0c47cb --- /dev/null +++ b/starmath/source/uiobject.cxx @@ -0,0 +1,109 @@ +/* -*- 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 <memory> +#include "uiobject.hxx" +#include <vcl/layout.hxx> +#include <ElementsDockingWindow.hxx> + +ElementUIObject::ElementUIObject(SmElementsControl* pElementSelector, + const OUString& rID): + mpElementsSelector(pElementSelector), + maID(rID) +{ +} + +SmElement* ElementUIObject::get_element() +{ + sal_uInt32 nID = maID.toUInt32(); + size_t n = mpElementsSelector->maElementList.size(); + if (nID >= n) + return nullptr; + + return mpElementsSelector->maElementList[nID].get(); +} + +StringMap ElementUIObject::get_state() +{ + StringMap aMap; + aMap["ID"] = maID; + + SmElement* pElement = get_element(); + if (pElement) + aMap["Text"] = pElement->getText(); + + return aMap; +} + +void ElementUIObject::execute(const OUString& rAction, + const StringMap& /*rParameters*/) +{ + if (rAction == "SELECT") + { + SmElement* pElement = get_element(); + if (pElement) + mpElementsSelector->maSelectHdlLink.Call(*pElement); + } +} + +ElementSelectorUIObject::ElementSelectorUIObject(vcl::Window* pElementSelectorWindow, SmElementsControl* pElementSelector) + : WindowUIObject(pElementSelectorWindow) + , mpElementsSelector(pElementSelector) +{ +} + +StringMap ElementSelectorUIObject::get_state() +{ + StringMap aMap = WindowUIObject::get_state(); + + SmElement* pElement = mpElementsSelector->current(); + if (pElement) + aMap["CurrentEntry"] = pElement->getText(); + + aMap["CurrentSelection"] = OUString::fromUtf8(mpElementsSelector->msCurrentSetId); + + return aMap; +} + +std::unique_ptr<UIObject> ElementSelectorUIObject::get_child(const OUString& rID) +{ + size_t nID = rID.toInt32(); + size_t n = mpElementsSelector->maElementList.size(); + if (nID >= n) + throw css::uno::RuntimeException("invalid id"); + + return std::unique_ptr<UIObject>(new ElementUIObject(mpElementsSelector, rID)); +} + +std::set<OUString> ElementSelectorUIObject::get_children() const +{ + std::set<OUString> aChildren; + + size_t n = mpElementsSelector->maElementList.size(); + for (size_t i = 0; i < n; ++i) + { + aChildren.insert(OUString::number(i)); + } + + return aChildren; +} + +std::unique_ptr<UIObject> ElementSelectorUIObject::create(vcl::Window* pWindow) +{ + VclDrawingArea* pSmElementsWin = dynamic_cast<VclDrawingArea*>(pWindow); + assert(pSmElementsWin); + return std::unique_ptr<UIObject>(new ElementSelectorUIObject(pSmElementsWin, static_cast<SmElementsControl*>(pSmElementsWin->GetUserData()))); +} + +OUString ElementSelectorUIObject::get_name() const +{ + return "SmElementSelector"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/uiobject.hxx b/starmath/source/uiobject.hxx new file mode 100644 index 000000000..607c9194c --- /dev/null +++ b/starmath/source/uiobject.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/. + */ + +#ifndef INCLUDED_STARMATH_SOURCE_UIOBJECT_HXX +#define INCLUDED_STARMATH_SOURCE_UIOBJECT_HXX + +#include <memory> +#include <vcl/uitest/uiobject.hxx> + +#include <ElementsDockingWindow.hxx> + +class ElementUIObject : public UIObject +{ +private: + SmElementsControl* mpElementsSelector; + OUString maID; + +public: + + ElementUIObject(SmElementsControl* pElementSelector, + const OUString& rID); + + virtual StringMap get_state() override; + + virtual void execute(const OUString& rAction, + const StringMap& rParameters) override; + +private: + SmElement* get_element(); +}; + +class ElementSelectorUIObject : public WindowUIObject +{ +private: + SmElementsControl* mpElementsSelector; + +public: + + explicit ElementSelectorUIObject(vcl::Window* pElementSelectorWindow, SmElementsControl* pElementSelector); + + virtual StringMap get_state() override; + + static std::unique_ptr<UIObject> create(vcl::Window* pWindow); + + virtual std::unique_ptr<UIObject> get_child(const OUString& rID) override; + + virtual std::set<OUString> get_children() const override; + +protected: + + virtual OUString get_name() const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/unodoc.cxx b/starmath/source/unodoc.cxx new file mode 100644 index 000000000..ac7a36439 --- /dev/null +++ b/starmath/source/unodoc.cxx @@ -0,0 +1,50 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sfx2/sfxmodelfactory.hxx> + +#include "register.hxx" +#include <smdll.hxx> +#include <document.hxx> +#include <com/sun/star/frame/XModel.hpp> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; + +OUString SmDocument_getImplementationName() throw() +{ + return "com.sun.star.comp.Math.FormulaDocument"; +} + +uno::Sequence< OUString > SmDocument_getSupportedServiceNames() throw() +{ + return uno::Sequence<OUString>{ "com.sun.star.formula.FormulaProperties" }; +} + +uno::Reference< uno::XInterface > SmDocument_createInstance( + const uno::Reference< lang::XMultiServiceFactory > & /*rSMgr*/, SfxModelFlags _nCreationFlags ) +{ + SolarMutexGuard aGuard; + SmGlobals::ensure(); + SfxObjectShell* pShell = new SmDocShell( _nCreationFlags ); + return uno::Reference< uno::XInterface >( pShell->GetModel() ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/unofilter.cxx b/starmath/source/unofilter.cxx new file mode 100644 index 000000000..719681af4 --- /dev/null +++ b/starmath/source/unofilter.cxx @@ -0,0 +1,119 @@ +/* -*- 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 <memory> + +#include <unotools/mediadescriptor.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <sot/storage.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <document.hxx> +#include "mathtype.hxx" +#include <unomodel.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; + +namespace +{ +/// Invokes the MathType importer via UNO. +class MathTypeFilter + : public cppu::WeakImplHelper<document::XFilter, document::XImporter, lang::XServiceInfo> +{ + uno::Reference<lang::XComponent> m_xDstDoc; + +public: + MathTypeFilter(); + + // XFilter + sal_Bool SAL_CALL filter(const uno::Sequence<beans::PropertyValue>& rDescriptor) override; + void SAL_CALL cancel() override; + + // XImporter + void SAL_CALL setTargetDocument(const uno::Reference<lang::XComponent>& xDoc) override; + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override; + uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; +} + +MathTypeFilter::MathTypeFilter() = default; + +sal_Bool MathTypeFilter::filter(const uno::Sequence<beans::PropertyValue>& rDescriptor) +{ + bool bSuccess = false; + try + { + utl::MediaDescriptor aMediaDesc(rDescriptor); + aMediaDesc.addInputStream(); + uno::Reference<io::XInputStream> xInputStream; + aMediaDesc[utl::MediaDescriptor::PROP_INPUTSTREAM()] >>= xInputStream; + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream)); + if (pStream) + { + if (SotStorage::IsStorageFile(pStream.get())) + { + tools::SvRef<SotStorage> aStorage(new SotStorage(pStream.get(), false)); + // Is this a MathType Storage? + if (aStorage->IsStream("Equation Native")) + { + if (auto pModel = dynamic_cast<SmModel*>(m_xDstDoc.get())) + { + auto pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + OUStringBuffer aText(pDocShell->GetText()); + MathType aEquation(aText); + bSuccess = aEquation.Parse(aStorage.get()); + if (bSuccess) + { + pDocShell->SetText(aText.makeStringAndClear()); + pDocShell->Parse(); + } + } + } + } + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("starmath"); + } + return bSuccess; +} + +void MathTypeFilter::cancel() {} + +void MathTypeFilter::setTargetDocument(const uno::Reference<lang::XComponent>& xDoc) +{ + m_xDstDoc = xDoc; +} + +OUString MathTypeFilter::getImplementationName() { return "com.sun.star.comp.Math.MathTypeFilter"; } + +sal_Bool MathTypeFilter::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence<OUString> MathTypeFilter::getSupportedServiceNames() +{ + uno::Sequence<OUString> aRet = { OUString("com.sun.star.document.ImportFilter") }; + return aRet; +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +com_sun_star_comp_Math_MathTypeFilter_get_implementation(uno::XComponentContext* /*pCtx*/, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new MathTypeFilter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/unomodel.cxx b/starmath/source/unomodel.cxx new file mode 100644 index 000000000..17d52ce7b --- /dev/null +++ b/starmath/source/unomodel.cxx @@ -0,0 +1,1100 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <utility> + +#include <o3tl/any.hxx> +#include <sfx2/printer.hxx> +#include <svl/itemprop.hxx> +#include <svl/itemset.hxx> +#include <vcl/svapp.hxx> +#include <unotools/localedatawrapper.hxx> +#include <vcl/settings.hxx> +#include <vcl/print.hxx> +#include <toolkit/awt/vclxdevice.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/formula/SymbolDescriptor.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <comphelper/propertysetinfo.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/moduleoptions.hxx> +#include <tools/mapunit.hxx> +#include <tools/stream.hxx> + +#include <unomodel.hxx> +#include <document.hxx> +#include <view.hxx> +#include <symbol.hxx> +#include <starmath.hrc> +#include <strings.hrc> +#include <smmod.hxx> +#include "cfgitem.hxx" + +using namespace ::cppu; +using namespace ::std; +using namespace ::comphelper; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::formula; +using namespace ::com::sun::star::view; +using namespace ::com::sun::star::script; + +SmPrintUIOptions::SmPrintUIOptions() +{ + SmModule *pp = SM_MOD(); + SmMathConfig *pConfig = pp->GetConfig(); + SAL_WARN_IF( !pConfig, "starmath", "SmConfig not found" ); + if (!pConfig) + return; + + sal_Int32 nNumProps = 10, nIdx=0; + + // create sequence of print UI options + // (Actually IsIgnoreSpacesRight is a parser option. Without it we need only 8 properties here.) + m_aUIProperties.resize( nNumProps ); + + // load the math PrinterOptions into the custom tab + m_aUIProperties[nIdx].Name = "OptionsUIFile"; + m_aUIProperties[nIdx++].Value <<= OUString("modules/smath/ui/printeroptions.ui"); + + // create Section for formula (results in an extra tab page in dialog) + SvtModuleOptions aOpt; + OUString aAppGroupname( + SmResId( RID_PRINTUIOPT_PRODNAME ). + replaceFirst( "%s", aOpt.GetModuleName( SvtModuleOptions::EModule::MATH ) ) ); + m_aUIProperties[nIdx++].Value = setGroupControlOpt("tabcontrol-page2", aAppGroupname, ".HelpID:vcl:PrintDialog:TabPage:AppPage"); + + // create subgroup for print options + m_aUIProperties[nIdx++].Value = setSubgroupControlOpt("contents", SmResId( RID_PRINTUIOPT_CONTENTS ), OUString()); + + // create a bool option for title row (matches to SID_PRINTTITLE) + m_aUIProperties[nIdx++].Value = setBoolControlOpt("title", SmResId( RID_PRINTUIOPT_TITLE ), + ".HelpID:vcl:PrintDialog:TitleRow:CheckBox", + PRTUIOPT_TITLE_ROW, + pConfig->IsPrintTitle()); + // create a bool option for formula text (matches to SID_PRINTTEXT) + m_aUIProperties[nIdx++].Value = setBoolControlOpt("formulatext", SmResId( RID_PRINTUIOPT_FRMLTXT ), + ".HelpID:vcl:PrintDialog:FormulaText:CheckBox", + PRTUIOPT_FORMULA_TEXT, + pConfig->IsPrintFormulaText()); + // create a bool option for border (matches to SID_PRINTFRAME) + m_aUIProperties[nIdx++].Value = setBoolControlOpt("borders", SmResId( RID_PRINTUIOPT_BORDERS ), + ".HelpID:vcl:PrintDialog:Border:CheckBox", + PRTUIOPT_BORDER, + pConfig->IsPrintFrame()); + + // create subgroup for print format + m_aUIProperties[nIdx++].Value = setSubgroupControlOpt("size", SmResId( RID_PRINTUIOPT_SIZE ), OUString()); + + // create a radio button group for print format (matches to SID_PRINTSIZE) + Sequence< OUString > aChoices{ + SmResId( RID_PRINTUIOPT_ORIGSIZE ), + SmResId( RID_PRINTUIOPT_FITTOPAGE ), + SmResId( RID_PRINTUIOPT_SCALING ) + }; + Sequence< OUString > aHelpIds{ + ".HelpID:vcl:PrintDialog:PrintFormat:RadioButton:0", + ".HelpID:vcl:PrintDialog:PrintFormat:RadioButton:1", + ".HelpID:vcl:PrintDialog:PrintFormat:RadioButton:2" + }; + Sequence< OUString > aWidgetIds{ + "originalsize", + "fittopage", + "scaling" + }; + OUString aPrintFormatProp( PRTUIOPT_PRINT_FORMAT ); + m_aUIProperties[nIdx++].Value = setChoiceRadiosControlOpt(aWidgetIds, OUString(), + aHelpIds, + aPrintFormatProp, + aChoices, static_cast< sal_Int32 >(pConfig->GetPrintSize()) + ); + + // create a numeric box for scale dependent on PrintFormat = "Scaling" (matches to SID_PRINTZOOM) + vcl::PrinterOptionsHelper::UIControlOptions aRangeOpt( aPrintFormatProp, 2, true ); + m_aUIProperties[nIdx++].Value = setRangeControlOpt("scalingspin", OUString(), + ".HelpID:vcl:PrintDialog:PrintScale:NumericField", + PRTUIOPT_PRINT_SCALE, + pConfig->GetPrintZoomFactor(), // initial value + 10, // min value + 1000, // max value + aRangeOpt); + + Sequence< PropertyValue > aHintNoLayoutPage( 1 ); + aHintNoLayoutPage[0].Name = "HintNoLayoutPage"; + aHintNoLayoutPage[0].Value <<= true; + m_aUIProperties[nIdx++].Value <<= aHintNoLayoutPage; + + assert(nIdx == nNumProps); +} + + + +namespace { + +enum SmModelPropertyHandles +{ + HANDLE_FORMULA, + HANDLE_FONT_NAME_VARIABLES, + HANDLE_FONT_NAME_FUNCTIONS, + HANDLE_FONT_NAME_NUMBERS, + HANDLE_FONT_NAME_TEXT, + HANDLE_CUSTOM_FONT_NAME_SERIF, + HANDLE_CUSTOM_FONT_NAME_SANS, + HANDLE_CUSTOM_FONT_NAME_FIXED, + HANDLE_CUSTOM_FONT_FIXED_POSTURE, + HANDLE_CUSTOM_FONT_FIXED_WEIGHT, + HANDLE_CUSTOM_FONT_SANS_POSTURE, + HANDLE_CUSTOM_FONT_SANS_WEIGHT, + HANDLE_CUSTOM_FONT_SERIF_POSTURE, + HANDLE_CUSTOM_FONT_SERIF_WEIGHT, + HANDLE_FONT_VARIABLES_POSTURE, + HANDLE_FONT_VARIABLES_WEIGHT, + HANDLE_FONT_FUNCTIONS_POSTURE, + HANDLE_FONT_FUNCTIONS_WEIGHT, + HANDLE_FONT_NUMBERS_POSTURE, + HANDLE_FONT_NUMBERS_WEIGHT, + HANDLE_FONT_TEXT_POSTURE, + HANDLE_FONT_TEXT_WEIGHT, + HANDLE_BASE_FONT_HEIGHT, + HANDLE_RELATIVE_FONT_HEIGHT_TEXT, + HANDLE_RELATIVE_FONT_HEIGHT_INDICES, + HANDLE_RELATIVE_FONT_HEIGHT_FUNCTIONS, + HANDLE_RELATIVE_FONT_HEIGHT_OPERATORS, + HANDLE_RELATIVE_FONT_HEIGHT_LIMITS, + HANDLE_IS_TEXT_MODE, + HANDLE_GREEK_CHAR_STYLE, + HANDLE_ALIGNMENT, + HANDLE_RELATIVE_SPACING, + HANDLE_RELATIVE_LINE_SPACING, + HANDLE_RELATIVE_ROOT_SPACING, + HANDLE_RELATIVE_INDEX_SUPERSCRIPT, + HANDLE_RELATIVE_INDEX_SUBSCRIPT, + HANDLE_RELATIVE_FRACTION_NUMERATOR_HEIGHT, + HANDLE_RELATIVE_FRACTION_DENOMINATOR_DEPTH, + HANDLE_RELATIVE_FRACTION_BAR_EXCESS_LENGTH, + HANDLE_RELATIVE_FRACTION_BAR_LINE_WEIGHT, + HANDLE_RELATIVE_UPPER_LIMIT_DISTANCE, + HANDLE_RELATIVE_LOWER_LIMIT_DISTANCE, + HANDLE_RELATIVE_BRACKET_EXCESS_SIZE, + HANDLE_RELATIVE_BRACKET_DISTANCE, + HANDLE_IS_SCALE_ALL_BRACKETS, + HANDLE_RELATIVE_SCALE_BRACKET_EXCESS_SIZE, + HANDLE_RELATIVE_MATRIX_LINE_SPACING, + HANDLE_RELATIVE_MATRIX_COLUMN_SPACING, + HANDLE_RELATIVE_SYMBOL_PRIMARY_HEIGHT, + HANDLE_RELATIVE_SYMBOL_MINIMUM_HEIGHT, + HANDLE_RELATIVE_OPERATOR_EXCESS_SIZE, + HANDLE_RELATIVE_OPERATOR_SPACING, + HANDLE_LEFT_MARGIN, + HANDLE_RIGHT_MARGIN, + HANDLE_TOP_MARGIN, + HANDLE_BOTTOM_MARGIN, + HANDLE_PRINTER_NAME, + HANDLE_PRINTER_SETUP, + HANDLE_SYMBOLS, + HANDLE_SAVE_THUMBNAIL, + HANDLE_USED_SYMBOLS, + HANDLE_BASIC_LIBRARIES, + HANDLE_RUNTIME_UID, + HANDLE_LOAD_READONLY, // Security Options + HANDLE_DIALOG_LIBRARIES, // #i73329# + HANDLE_BASELINE, + HANDLE_INTEROP_GRAB_BAG, +}; + +} + +static rtl::Reference<PropertySetInfo> lcl_createModelPropertyInfo () +{ + static PropertyMapEntry aModelPropertyInfoMap[] = + { + { OUString("Alignment") , HANDLE_ALIGNMENT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString("BaseFontHeight") , HANDLE_BASE_FONT_HEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString("BasicLibraries") , HANDLE_BASIC_LIBRARIES , cppu::UnoType<script::XLibraryContainer>::get(), PropertyAttribute::READONLY, 0 }, + { OUString("BottomMargin") , HANDLE_BOTTOM_MARGIN , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_BOTTOMSPACE }, + { OUString("CustomFontNameFixed") , HANDLE_CUSTOM_FONT_NAME_FIXED , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_FIXED }, + { OUString("CustomFontNameSans") , HANDLE_CUSTOM_FONT_NAME_SANS , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_SANS }, + { OUString("CustomFontNameSerif") , HANDLE_CUSTOM_FONT_NAME_SERIF , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_SERIF }, + { OUString("DialogLibraries") , HANDLE_DIALOG_LIBRARIES , cppu::UnoType<script::XLibraryContainer>::get(), PropertyAttribute::READONLY, 0 }, + { OUString("FontFixedIsBold") , HANDLE_CUSTOM_FONT_FIXED_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_FIXED }, + { OUString("FontFixedIsItalic") , HANDLE_CUSTOM_FONT_FIXED_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_FIXED }, + { OUString("FontFunctionsIsBold") , HANDLE_FONT_FUNCTIONS_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_FUNCTION }, + { OUString("FontFunctionsIsItalic") , HANDLE_FONT_FUNCTIONS_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_FUNCTION }, + { OUString("FontNameFunctions") , HANDLE_FONT_NAME_FUNCTIONS , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_FUNCTION }, + { OUString("FontNameNumbers") , HANDLE_FONT_NAME_NUMBERS , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_NUMBER }, + { OUString("FontNameText") , HANDLE_FONT_NAME_TEXT , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_TEXT }, + { OUString("FontNameVariables") , HANDLE_FONT_NAME_VARIABLES , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_VARIABLE }, + { OUString("FontNumbersIsBold") , HANDLE_FONT_NUMBERS_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_NUMBER }, + { OUString("FontNumbersIsItalic") , HANDLE_FONT_NUMBERS_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_NUMBER }, + { OUString("FontSansIsBold") , HANDLE_CUSTOM_FONT_SANS_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_SANS }, + { OUString("FontSansIsItalic") , HANDLE_CUSTOM_FONT_SANS_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_SANS }, + { OUString("FontSerifIsBold") , HANDLE_CUSTOM_FONT_SERIF_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_SERIF }, + { OUString("FontSerifIsItalic") , HANDLE_CUSTOM_FONT_SERIF_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_SERIF }, + { OUString("FontTextIsBold") , HANDLE_FONT_TEXT_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_TEXT }, + { OUString("FontTextIsItalic") , HANDLE_FONT_TEXT_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_TEXT }, + { OUString("FontVariablesIsBold") , HANDLE_FONT_VARIABLES_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_VARIABLE }, + { OUString("FontVariablesIsItalic") , HANDLE_FONT_VARIABLES_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_VARIABLE }, + { OUString("Formula") , HANDLE_FORMULA , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString("IsScaleAllBrackets") , HANDLE_IS_SCALE_ALL_BRACKETS , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString("IsTextMode") , HANDLE_IS_TEXT_MODE , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString("GreekCharStyle") , HANDLE_GREEK_CHAR_STYLE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString("LeftMargin") , HANDLE_LEFT_MARGIN , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_LEFTSPACE }, + { OUString("PrinterName") , HANDLE_PRINTER_NAME , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString("PrinterSetup") , HANDLE_PRINTER_SETUP , cppu::UnoType<const Sequence < sal_Int8 >>::get(), PROPERTY_NONE, 0 }, + { OUString("RelativeBracketDistance") , HANDLE_RELATIVE_BRACKET_DISTANCE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_BRACKETSPACE }, + { OUString("RelativeBracketExcessSize") , HANDLE_RELATIVE_BRACKET_EXCESS_SIZE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_BRACKETSIZE }, + { OUString("RelativeFontHeightFunctions") , HANDLE_RELATIVE_FONT_HEIGHT_FUNCTIONS , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_FUNCTION }, + { OUString("RelativeFontHeightIndices") , HANDLE_RELATIVE_FONT_HEIGHT_INDICES , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_INDEX }, + { OUString("RelativeFontHeightLimits") , HANDLE_RELATIVE_FONT_HEIGHT_LIMITS , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_LIMITS }, + { OUString("RelativeFontHeightOperators") , HANDLE_RELATIVE_FONT_HEIGHT_OPERATORS , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_OPERATOR }, + { OUString("RelativeFontHeightText") , HANDLE_RELATIVE_FONT_HEIGHT_TEXT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_TEXT }, + { OUString("RelativeFractionBarExcessLength") , HANDLE_RELATIVE_FRACTION_BAR_EXCESS_LENGTH, ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_FRACTION }, + { OUString("RelativeFractionBarLineWeight") , HANDLE_RELATIVE_FRACTION_BAR_LINE_WEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_STROKEWIDTH }, + { OUString("RelativeFractionDenominatorDepth") , HANDLE_RELATIVE_FRACTION_DENOMINATOR_DEPTH, ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_DENOMINATOR }, + { OUString("RelativeFractionNumeratorHeight") , HANDLE_RELATIVE_FRACTION_NUMERATOR_HEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_NUMERATOR }, + { OUString("RelativeIndexSubscript") , HANDLE_RELATIVE_INDEX_SUBSCRIPT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_SUBSCRIPT }, + { OUString("RelativeIndexSuperscript") , HANDLE_RELATIVE_INDEX_SUPERSCRIPT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_SUPERSCRIPT }, + { OUString("RelativeLineSpacing") , HANDLE_RELATIVE_LINE_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_VERTICAL }, + { OUString("RelativeLowerLimitDistance") , HANDLE_RELATIVE_LOWER_LIMIT_DISTANCE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_LOWERLIMIT }, + { OUString("RelativeMatrixColumnSpacing") , HANDLE_RELATIVE_MATRIX_COLUMN_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_MATRIXCOL }, + { OUString("RelativeMatrixLineSpacing") , HANDLE_RELATIVE_MATRIX_LINE_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_MATRIXROW }, + { OUString("RelativeOperatorExcessSize") , HANDLE_RELATIVE_OPERATOR_EXCESS_SIZE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_OPERATORSIZE }, + { OUString("RelativeOperatorSpacing") , HANDLE_RELATIVE_OPERATOR_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_OPERATORSPACE }, + { OUString("RelativeRootSpacing") , HANDLE_RELATIVE_ROOT_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_ROOT }, + { OUString("RelativeScaleBracketExcessSize") , HANDLE_RELATIVE_SCALE_BRACKET_EXCESS_SIZE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_NORMALBRACKETSIZE }, + { OUString("RelativeSpacing") , HANDLE_RELATIVE_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_HORIZONTAL }, + { OUString("RelativeSymbolMinimumHeight") , HANDLE_RELATIVE_SYMBOL_MINIMUM_HEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_ORNAMENTSPACE }, + { OUString("RelativeSymbolPrimaryHeight") , HANDLE_RELATIVE_SYMBOL_PRIMARY_HEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_ORNAMENTSIZE }, + { OUString("RelativeUpperLimitDistance") , HANDLE_RELATIVE_UPPER_LIMIT_DISTANCE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_UPPERLIMIT }, + { OUString("RightMargin") , HANDLE_RIGHT_MARGIN , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_RIGHTSPACE }, + { OUString("RuntimeUID") , HANDLE_RUNTIME_UID , cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0 }, + { OUString("SaveThumbnail") , HANDLE_SAVE_THUMBNAIL , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString("Symbols") , HANDLE_SYMBOLS , cppu::UnoType<Sequence < SymbolDescriptor >>::get(), PROPERTY_NONE, 0 }, + { OUString("UserDefinedSymbolsInUse") , HANDLE_USED_SYMBOLS , cppu::UnoType<Sequence < SymbolDescriptor >>::get(), PropertyAttribute::READONLY, 0 }, + { OUString("TopMargin") , HANDLE_TOP_MARGIN , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_TOPSPACE }, + // #i33095# Security Options + { OUString("LoadReadonly") , HANDLE_LOAD_READONLY , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + // #i972# + { OUString("BaseLine") , HANDLE_BASELINE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString("InteropGrabBag") , HANDLE_INTEROP_GRAB_BAG , cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get(), PROPERTY_NONE, 0 }, + { OUString(), 0, css::uno::Type(), 0, 0 } + }; + return rtl::Reference<PropertySetInfo>( new PropertySetInfo ( aModelPropertyInfoMap ) ); +} + +SmModel::SmModel( SfxObjectShell *pObjSh ) +: SfxBaseModel(pObjSh) +, PropertySetHelper ( lcl_createModelPropertyInfo () ) +{ +} + +SmModel::~SmModel() throw () +{ +} + +uno::Any SAL_CALL SmModel::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet = ::cppu::queryInterface ( rType, + // OWeakObject interfaces + &dynamic_cast<XInterface&>(static_cast<XUnoTunnel&>(*this)), + static_cast< XWeak* > ( this ), + // PropertySetHelper interfaces + static_cast< XPropertySet* > ( this ), + static_cast< XMultiPropertySet* > ( this ), + // my own interfaces + static_cast< XServiceInfo* > ( this ), + static_cast< XRenderable* > ( this ) ); + if (!aRet.hasValue()) + aRet = SfxBaseModel::queryInterface ( rType ); + return aRet; +} + +void SAL_CALL SmModel::acquire() throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL SmModel::release() throw() +{ + OWeakObject::release(); +} + +uno::Sequence< uno::Type > SAL_CALL SmModel::getTypes( ) +{ + return comphelper::concatSequences(SfxBaseModel::getTypes(), + uno::Sequence { + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XPropertySet>::get(), + cppu::UnoType<XMultiPropertySet>::get(), + cppu::UnoType<XRenderable>::get() }); +} + +namespace +{ + class theSmModelUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSmModelUnoTunnelId> {}; +} + +const uno::Sequence< sal_Int8 > & SmModel::getUnoTunnelId() +{ + return theSmModelUnoTunnelId::get().getSeq(); +} + +sal_Int64 SAL_CALL SmModel::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + if( isUnoTunnelId<SmModel>(rId) ) + { + return sal::static_int_cast< sal_Int64 >(reinterpret_cast< sal_uIntPtr >(this)); + } + + return SfxBaseModel::getSomething( rId ); +} + +static sal_Int16 lcl_AnyToINT16(const uno::Any& rAny) +{ + sal_Int16 nRet = 0; + if( auto x = o3tl::tryAccess<double>(rAny) ) + nRet = static_cast<sal_Int16>(*x); + else + rAny >>= nRet; + return nRet; +} + +OUString SmModel::getImplementationName() +{ + return "com.sun.star.comp.Math.FormulaDocument"; +} + +sal_Bool SmModel::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SmModel::getSupportedServiceNames() +{ + return uno::Sequence<OUString>{ + "com.sun.star.document.OfficeDocument", + "com.sun.star.formula.FormulaProperties" + }; +} + +void SmModel::_setPropertyValues(const PropertyMapEntry** ppEntries, const Any* pValues) +{ + SolarMutexGuard aGuard; + + SmDocShell *pDocSh = static_cast < SmDocShell * > (GetObjectShell()); + + if ( nullptr == pDocSh ) + throw UnknownPropertyException(); + + SmFormat aFormat = pDocSh->GetFormat(); + + for (; *ppEntries; ppEntries++, pValues++ ) + { + if ((*ppEntries)->mnAttributes & PropertyAttribute::READONLY) + throw PropertyVetoException(); + + switch ( (*ppEntries)->mnHandle ) + { + case HANDLE_FORMULA: + { + OUString aText; + *pValues >>= aText; + pDocSh->SetText(aText); + } + break; + case HANDLE_FONT_NAME_VARIABLES : + case HANDLE_FONT_NAME_FUNCTIONS : + case HANDLE_FONT_NAME_NUMBERS : + case HANDLE_FONT_NAME_TEXT : + case HANDLE_CUSTOM_FONT_NAME_SERIF : + case HANDLE_CUSTOM_FONT_NAME_SANS : + case HANDLE_CUSTOM_FONT_NAME_FIXED : + { + OUString sFontName; + *pValues >>= sFontName; + if(sFontName.isEmpty()) + throw IllegalArgumentException(); + + if(aFormat.GetFont((*ppEntries)->mnMemberId).GetFamilyName() != sFontName) + { + const SmFace rOld = aFormat.GetFont((*ppEntries)->mnMemberId); + + SmFace aSet( sFontName, rOld.GetFontSize() ); + aSet.SetBorderWidth( rOld.GetBorderWidth() ); + aSet.SetAlignment( ALIGN_BASELINE ); + aFormat.SetFont( (*ppEntries)->mnMemberId, aSet ); + } + } + break; + case HANDLE_CUSTOM_FONT_FIXED_POSTURE: + case HANDLE_CUSTOM_FONT_SANS_POSTURE : + case HANDLE_CUSTOM_FONT_SERIF_POSTURE: + case HANDLE_FONT_VARIABLES_POSTURE : + case HANDLE_FONT_FUNCTIONS_POSTURE : + case HANDLE_FONT_NUMBERS_POSTURE : + case HANDLE_FONT_TEXT_POSTURE : + { + auto bVal = o3tl::tryAccess<bool>(*pValues); + if(!bVal) + throw IllegalArgumentException(); + vcl::Font aNewFont(aFormat.GetFont((*ppEntries)->mnMemberId)); + aNewFont.SetItalic(*bVal ? ITALIC_NORMAL : ITALIC_NONE); + aFormat.SetFont((*ppEntries)->mnMemberId, aNewFont); + } + break; + case HANDLE_CUSTOM_FONT_FIXED_WEIGHT : + case HANDLE_CUSTOM_FONT_SANS_WEIGHT : + case HANDLE_CUSTOM_FONT_SERIF_WEIGHT : + case HANDLE_FONT_VARIABLES_WEIGHT : + case HANDLE_FONT_FUNCTIONS_WEIGHT : + case HANDLE_FONT_NUMBERS_WEIGHT : + case HANDLE_FONT_TEXT_WEIGHT : + { + auto bVal = o3tl::tryAccess<bool>(*pValues); + if(!bVal) + throw IllegalArgumentException(); + vcl::Font aNewFont(aFormat.GetFont((*ppEntries)->mnMemberId)); + aNewFont.SetWeight(*bVal ? WEIGHT_BOLD : WEIGHT_NORMAL); + aFormat.SetFont((*ppEntries)->mnMemberId, aNewFont); + } + break; + case HANDLE_BASE_FONT_HEIGHT : + { + // Point! + sal_Int16 nVal = lcl_AnyToINT16(*pValues); + if(nVal < 1) + throw IllegalArgumentException(); + Size aSize = aFormat.GetBaseSize(); + aSize.setHeight( SmPtsTo100th_mm(nVal) ); + aFormat.SetBaseSize(aSize); + + // apply base size to fonts + const Size aTmp( aFormat.GetBaseSize() ); + for (sal_uInt16 i = FNT_BEGIN; i <= FNT_END; i++) + aFormat.SetFontSize(i, aTmp); + } + break; + case HANDLE_RELATIVE_FONT_HEIGHT_TEXT : + case HANDLE_RELATIVE_FONT_HEIGHT_INDICES : + case HANDLE_RELATIVE_FONT_HEIGHT_FUNCTIONS : + case HANDLE_RELATIVE_FONT_HEIGHT_OPERATORS : + case HANDLE_RELATIVE_FONT_HEIGHT_LIMITS : + { + sal_Int16 nVal = 0; + *pValues >>= nVal; + if(nVal < 1) + throw IllegalArgumentException(); + aFormat.SetRelSize((*ppEntries)->mnMemberId, nVal); + } + break; + + case HANDLE_IS_TEXT_MODE : + { + aFormat.SetTextmode(*o3tl::doAccess<bool>(*pValues)); + } + break; + + case HANDLE_GREEK_CHAR_STYLE : + { + sal_Int16 nVal = 0; + *pValues >>= nVal; + if (nVal < 0 || nVal > 2) + throw IllegalArgumentException(); + aFormat.SetGreekCharStyle( nVal ); + } + break; + + case HANDLE_ALIGNMENT : + { + // SmHorAlign uses the same values as HorizontalAlignment + sal_Int16 nVal = 0; + *pValues >>= nVal; + if(nVal < 0 || nVal > 2) + throw IllegalArgumentException(); + aFormat.SetHorAlign(static_cast<SmHorAlign>(nVal)); + } + break; + + case HANDLE_RELATIVE_SPACING : + case HANDLE_RELATIVE_LINE_SPACING : + case HANDLE_RELATIVE_ROOT_SPACING : + case HANDLE_RELATIVE_INDEX_SUPERSCRIPT : + case HANDLE_RELATIVE_INDEX_SUBSCRIPT : + case HANDLE_RELATIVE_FRACTION_NUMERATOR_HEIGHT : + case HANDLE_RELATIVE_FRACTION_DENOMINATOR_DEPTH: + case HANDLE_RELATIVE_FRACTION_BAR_EXCESS_LENGTH: + case HANDLE_RELATIVE_FRACTION_BAR_LINE_WEIGHT : + case HANDLE_RELATIVE_UPPER_LIMIT_DISTANCE : + case HANDLE_RELATIVE_LOWER_LIMIT_DISTANCE : + case HANDLE_RELATIVE_BRACKET_EXCESS_SIZE : + case HANDLE_RELATIVE_BRACKET_DISTANCE : + case HANDLE_RELATIVE_SCALE_BRACKET_EXCESS_SIZE : + case HANDLE_RELATIVE_MATRIX_LINE_SPACING : + case HANDLE_RELATIVE_MATRIX_COLUMN_SPACING : + case HANDLE_RELATIVE_SYMBOL_PRIMARY_HEIGHT : + case HANDLE_RELATIVE_SYMBOL_MINIMUM_HEIGHT : + case HANDLE_RELATIVE_OPERATOR_EXCESS_SIZE : + case HANDLE_RELATIVE_OPERATOR_SPACING : + case HANDLE_LEFT_MARGIN : + case HANDLE_RIGHT_MARGIN : + case HANDLE_TOP_MARGIN : + case HANDLE_BOTTOM_MARGIN : + { + sal_Int16 nVal = 0; + *pValues >>= nVal; + if(nVal < 0) + throw IllegalArgumentException(); + aFormat.SetDistance((*ppEntries)->mnMemberId, nVal); + } + break; + case HANDLE_IS_SCALE_ALL_BRACKETS : + aFormat.SetScaleNormalBrackets(*o3tl::doAccess<bool>(*pValues)); + break; + case HANDLE_PRINTER_NAME: + { + // embedded documents just ignore this property for now + if ( pDocSh->GetCreateMode() != SfxObjectCreateMode::EMBEDDED ) + { + SfxPrinter *pPrinter = pDocSh->GetPrinter ( ); + if (pPrinter) + { + OUString sPrinterName; + if ( !(*pValues >>= sPrinterName) ) + throw IllegalArgumentException(); + + if ( !sPrinterName.isEmpty() ) + { + VclPtrInstance<SfxPrinter> pNewPrinter( pPrinter->GetOptions().Clone(), sPrinterName ); + if (pNewPrinter->IsKnown()) + pDocSh->SetPrinter ( pNewPrinter ); + else + pNewPrinter.disposeAndClear(); + } + } + } + } + break; + case HANDLE_PRINTER_SETUP: + { + Sequence < sal_Int8 > aSequence; + if ( !(*pValues >>= aSequence) ) + throw IllegalArgumentException(); + + sal_uInt32 nSize = aSequence.getLength(); + SvMemoryStream aStream ( aSequence.getArray(), nSize, StreamMode::READ ); + aStream.Seek ( STREAM_SEEK_TO_BEGIN ); + static sal_uInt16 const nRange[] = + { + SID_PRINTSIZE, SID_PRINTSIZE, + SID_PRINTZOOM, SID_PRINTZOOM, + SID_PRINTTITLE, SID_PRINTTITLE, + SID_PRINTTEXT, SID_PRINTTEXT, + SID_PRINTFRAME, SID_PRINTFRAME, + SID_NO_RIGHT_SPACES, SID_NO_RIGHT_SPACES, + SID_SAVE_ONLY_USED_SYMBOLS, SID_SAVE_ONLY_USED_SYMBOLS, + SID_AUTO_CLOSE_BRACKETS, SID_AUTO_CLOSE_BRACKETS, + 0 + }; + auto pItemSet = std::make_unique<SfxItemSet>( SmDocShell::GetPool(), nRange ); + SmModule *pp = SM_MOD(); + pp->GetConfig()->ConfigToItemSet(*pItemSet); + VclPtr<SfxPrinter> pPrinter = SfxPrinter::Create ( aStream, std::move(pItemSet) ); + + pDocSh->SetPrinter( pPrinter ); + } + break; + case HANDLE_SYMBOLS: + { + // this is set + Sequence < SymbolDescriptor > aSequence; + if ( !(*pValues >>= aSequence) ) + throw IllegalArgumentException(); + + SmModule *pp = SM_MOD(); + SmSymbolManager &rManager = pp->GetSymbolManager(); + for (const SymbolDescriptor& rDescriptor : std::as_const(aSequence)) + { + vcl::Font aFont; + aFont.SetFamilyName ( rDescriptor.sFontName ); + aFont.SetCharSet ( static_cast < rtl_TextEncoding > (rDescriptor.nCharSet) ); + aFont.SetFamily ( static_cast < FontFamily > (rDescriptor.nFamily ) ); + aFont.SetPitch ( static_cast < FontPitch > (rDescriptor.nPitch ) ); + aFont.SetWeight ( static_cast < FontWeight > (rDescriptor.nWeight ) ); + aFont.SetItalic ( static_cast < FontItalic > (rDescriptor.nItalic ) ); + SmSym aSymbol ( rDescriptor.sName, aFont, static_cast < sal_Unicode > (rDescriptor.nCharacter), + rDescriptor.sSymbolSet ); + aSymbol.SetExportName ( rDescriptor.sExportName ); + rManager.AddOrReplaceSymbol ( aSymbol ); + } + } + break; + // #i33095# Security Options + case HANDLE_LOAD_READONLY : + { + if ( (*pValues).getValueType() != cppu::UnoType<bool>::get() ) + throw IllegalArgumentException(); + bool bReadonly = false; + if ( *pValues >>= bReadonly ) + pDocSh->SetLoadReadonly( bReadonly ); + break; + } + case HANDLE_INTEROP_GRAB_BAG: + setGrabBagItem(*pValues); + break; + case HANDLE_SAVE_THUMBNAIL: + { + if ((*pValues).getValueType() != cppu::UnoType<bool>::get()) + throw IllegalArgumentException(); + bool bThumbnail = false; + if (*pValues >>= bThumbnail) + pDocSh->SetUseThumbnailSave(bThumbnail); + } + break; + } + } + + pDocSh->SetFormat( aFormat ); + + // #i67283# since about all of the above changes are likely to change + // the formula size we have to recalculate the vis-area now + pDocSh->SetVisArea( tools::Rectangle( Point(0, 0), pDocSh->GetSize() ) ); +} + +void SmModel::_getPropertyValues( const PropertyMapEntry **ppEntries, Any *pValue ) +{ + SmDocShell *pDocSh = static_cast < SmDocShell * > (GetObjectShell()); + + if ( nullptr == pDocSh ) + throw UnknownPropertyException(); + + const SmFormat & aFormat = pDocSh->GetFormat(); + + for (; *ppEntries; ppEntries++, pValue++ ) + { + switch ( (*ppEntries)->mnHandle ) + { + case HANDLE_FORMULA: + *pValue <<= pDocSh->GetText(); + break; + case HANDLE_FONT_NAME_VARIABLES : + case HANDLE_FONT_NAME_FUNCTIONS : + case HANDLE_FONT_NAME_NUMBERS : + case HANDLE_FONT_NAME_TEXT : + case HANDLE_CUSTOM_FONT_NAME_SERIF : + case HANDLE_CUSTOM_FONT_NAME_SANS : + case HANDLE_CUSTOM_FONT_NAME_FIXED : + { + const SmFace & rFace = aFormat.GetFont((*ppEntries)->mnMemberId); + *pValue <<= rFace.GetFamilyName(); + } + break; + case HANDLE_CUSTOM_FONT_FIXED_POSTURE: + case HANDLE_CUSTOM_FONT_SANS_POSTURE : + case HANDLE_CUSTOM_FONT_SERIF_POSTURE: + case HANDLE_FONT_VARIABLES_POSTURE : + case HANDLE_FONT_FUNCTIONS_POSTURE : + case HANDLE_FONT_NUMBERS_POSTURE : + case HANDLE_FONT_TEXT_POSTURE : + { + const SmFace & rFace = aFormat.GetFont((*ppEntries)->mnMemberId); + *pValue <<= IsItalic( rFace ); + } + break; + case HANDLE_CUSTOM_FONT_FIXED_WEIGHT : + case HANDLE_CUSTOM_FONT_SANS_WEIGHT : + case HANDLE_CUSTOM_FONT_SERIF_WEIGHT : + case HANDLE_FONT_VARIABLES_WEIGHT : + case HANDLE_FONT_FUNCTIONS_WEIGHT : + case HANDLE_FONT_NUMBERS_WEIGHT : + case HANDLE_FONT_TEXT_WEIGHT : + { + const SmFace & rFace = aFormat.GetFont((*ppEntries)->mnMemberId); + *pValue <<= IsBold( rFace ); + } + break; + case HANDLE_BASE_FONT_HEIGHT : + { + // Point! + *pValue <<= sal_Int16( + SmRoundFraction( + Sm100th_mmToPts(aFormat.GetBaseSize().Height()))); + } + break; + case HANDLE_RELATIVE_FONT_HEIGHT_TEXT : + case HANDLE_RELATIVE_FONT_HEIGHT_INDICES : + case HANDLE_RELATIVE_FONT_HEIGHT_FUNCTIONS : + case HANDLE_RELATIVE_FONT_HEIGHT_OPERATORS : + case HANDLE_RELATIVE_FONT_HEIGHT_LIMITS : + *pValue <<= static_cast<sal_Int16>(aFormat.GetRelSize((*ppEntries)->mnMemberId)); + break; + + case HANDLE_IS_TEXT_MODE : + *pValue <<= aFormat.IsTextmode(); + break; + + case HANDLE_GREEK_CHAR_STYLE : + *pValue <<= aFormat.GetGreekCharStyle(); + break; + + case HANDLE_ALIGNMENT : + // SmHorAlign uses the same values as HorizontalAlignment + *pValue <<= static_cast<sal_Int16>(aFormat.GetHorAlign()); + break; + + case HANDLE_RELATIVE_SPACING : + case HANDLE_RELATIVE_LINE_SPACING : + case HANDLE_RELATIVE_ROOT_SPACING : + case HANDLE_RELATIVE_INDEX_SUPERSCRIPT : + case HANDLE_RELATIVE_INDEX_SUBSCRIPT : + case HANDLE_RELATIVE_FRACTION_NUMERATOR_HEIGHT : + case HANDLE_RELATIVE_FRACTION_DENOMINATOR_DEPTH: + case HANDLE_RELATIVE_FRACTION_BAR_EXCESS_LENGTH: + case HANDLE_RELATIVE_FRACTION_BAR_LINE_WEIGHT : + case HANDLE_RELATIVE_UPPER_LIMIT_DISTANCE : + case HANDLE_RELATIVE_LOWER_LIMIT_DISTANCE : + case HANDLE_RELATIVE_BRACKET_EXCESS_SIZE : + case HANDLE_RELATIVE_BRACKET_DISTANCE : + case HANDLE_RELATIVE_SCALE_BRACKET_EXCESS_SIZE : + case HANDLE_RELATIVE_MATRIX_LINE_SPACING : + case HANDLE_RELATIVE_MATRIX_COLUMN_SPACING : + case HANDLE_RELATIVE_SYMBOL_PRIMARY_HEIGHT : + case HANDLE_RELATIVE_SYMBOL_MINIMUM_HEIGHT : + case HANDLE_RELATIVE_OPERATOR_EXCESS_SIZE : + case HANDLE_RELATIVE_OPERATOR_SPACING : + case HANDLE_LEFT_MARGIN : + case HANDLE_RIGHT_MARGIN : + case HANDLE_TOP_MARGIN : + case HANDLE_BOTTOM_MARGIN : + *pValue <<= static_cast<sal_Int16>(aFormat.GetDistance((*ppEntries)->mnMemberId)); + break; + case HANDLE_IS_SCALE_ALL_BRACKETS : + *pValue <<= aFormat.IsScaleNormalBrackets(); + break; + case HANDLE_PRINTER_NAME: + { + SfxPrinter *pPrinter = pDocSh->GetPrinter ( ); + *pValue <<= pPrinter ? pPrinter->GetName() : OUString(); + } + break; + case HANDLE_PRINTER_SETUP: + { + SfxPrinter *pPrinter = pDocSh->GetPrinter (); + if (pPrinter) + { + SvMemoryStream aStream; + pPrinter->Store( aStream ); + sal_uInt32 nSize = aStream.TellEnd(); + aStream.Seek ( STREAM_SEEK_TO_BEGIN ); + Sequence < sal_Int8 > aSequence ( nSize ); + aStream.ReadBytes(aSequence.getArray(), nSize); + *pValue <<= aSequence; + } + } + break; + case HANDLE_SYMBOLS: + case HANDLE_USED_SYMBOLS: + { + const bool bUsedSymbolsOnly = (*ppEntries)->mnHandle == HANDLE_USED_SYMBOLS; + const std::set< OUString > &rUsedSymbols = pDocSh->GetUsedSymbols(); + + // this is get + SmModule *pp = SM_MOD(); + const SmSymbolManager &rManager = pp->GetSymbolManager(); + vector < const SmSym * > aVector; + + const SymbolPtrVec_t aSymbols( rManager.GetSymbols() ); + for (const SmSym* pSymbol : aSymbols) + { + if (pSymbol && !pSymbol->IsPredefined() && + (!bUsedSymbolsOnly || + rUsedSymbols.find( pSymbol->GetName() ) != rUsedSymbols.end())) + aVector.push_back ( pSymbol ); + } + Sequence < SymbolDescriptor > aSequence ( aVector.size() ); + SymbolDescriptor * pDescriptor = aSequence.getArray(); + + for (const SmSym* pSymbol : aVector) + { + pDescriptor->sName = pSymbol->GetName(); + pDescriptor->sExportName = pSymbol->GetExportName(); + pDescriptor->sSymbolSet = pSymbol->GetSymbolSetName(); + pDescriptor->nCharacter = static_cast < sal_Int32 > (pSymbol->GetCharacter()); + + vcl::Font rFont = pSymbol->GetFace(); + pDescriptor->sFontName = rFont.GetFamilyName(); + pDescriptor->nCharSet = sal::static_int_cast< sal_Int16 >(rFont.GetCharSet()); + pDescriptor->nFamily = sal::static_int_cast< sal_Int16 >(rFont.GetFamilyType()); + pDescriptor->nPitch = sal::static_int_cast< sal_Int16 >(rFont.GetPitch()); + pDescriptor->nWeight = sal::static_int_cast< sal_Int16 >(rFont.GetWeight()); + pDescriptor->nItalic = sal::static_int_cast< sal_Int16 >(rFont.GetItalic()); + pDescriptor++; + } + *pValue <<= aSequence; + } + break; + case HANDLE_BASIC_LIBRARIES: + *pValue <<= pDocSh->GetBasicContainer(); + break; + case HANDLE_DIALOG_LIBRARIES: + *pValue <<= pDocSh->GetDialogContainer(); + break; + case HANDLE_RUNTIME_UID: + *pValue <<= getRuntimeUID(); + break; + // #i33095# Security Options + case HANDLE_LOAD_READONLY : + { + *pValue <<= pDocSh->IsLoadReadonly(); + break; + } + // #i972# + case HANDLE_BASELINE: + { + if ( !pDocSh->GetFormulaTree() ) + pDocSh->Parse(); + if ( pDocSh->GetFormulaTree() ) + { + pDocSh->ArrangeFormula(); + + *pValue <<= static_cast<sal_Int32>( pDocSh->GetFormulaTree()->GetFormulaBaseline() ); + } + break; + } + case HANDLE_INTEROP_GRAB_BAG: + getGrabBagItem(*pValue); + break; + case HANDLE_SAVE_THUMBNAIL: + { + *pValue <<= pDocSh->IsUseThumbnailSave(); + } + break; + } + } +} + + +sal_Int32 SAL_CALL SmModel::getRendererCount( + const uno::Any& /*rSelection*/, + const uno::Sequence< beans::PropertyValue >& /*xOptions*/ ) +{ + return 1; +} + + +static Size lcl_GuessPaperSize() +{ + Size aRes; + const LocaleDataWrapper& rLocWrp( AllSettings().GetLocaleDataWrapper() ); + if( MeasurementSystem::Metric == rLocWrp.getMeasurementSystemEnum() ) + { + // in 100th mm + PaperInfo aInfo( PAPER_A4 ); + aRes.setWidth( aInfo.getWidth() ); + aRes.setHeight( aInfo.getHeight() ); + } + else + { + // in 100th mm + PaperInfo aInfo( PAPER_LETTER ); + aRes.setWidth( aInfo.getWidth() ); + aRes.setHeight( aInfo.getHeight() ); + } + return aRes; +} + +uno::Sequence< beans::PropertyValue > SAL_CALL SmModel::getRenderer( + sal_Int32 nRenderer, + const uno::Any& /*rSelection*/, + const uno::Sequence< beans::PropertyValue >& /*rxOptions*/ ) +{ + SolarMutexGuard aGuard; + + if (0 != nRenderer) + throw IllegalArgumentException(); + + SmDocShell *pDocSh = static_cast < SmDocShell * >( GetObjectShell() ); + if (!pDocSh) + throw RuntimeException(); + + SmPrinterAccess aPrinterAccess( *pDocSh ); + Printer *pPrinter = aPrinterAccess.GetPrinter(); + Size aPrtPaperSize ( pPrinter->GetPaperSize() ); + + // if paper size is 0 (usually if no 'real' printer is found), + // guess the paper size + if (aPrtPaperSize.IsEmpty()) + aPrtPaperSize = lcl_GuessPaperSize(); + awt::Size aPageSize( aPrtPaperSize.Width(), aPrtPaperSize.Height() ); + + uno::Sequence< beans::PropertyValue > aRenderer(1); + PropertyValue &rValue = aRenderer.getArray()[0]; + rValue.Name = "PageSize"; + rValue.Value <<= aPageSize; + + if (!m_pPrintUIOptions) + m_pPrintUIOptions.reset(new SmPrintUIOptions); + m_pPrintUIOptions->appendPrintUIOptions( aRenderer ); + + return aRenderer; +} + +void SAL_CALL SmModel::render( + sal_Int32 nRenderer, + const uno::Any& rSelection, + const uno::Sequence< beans::PropertyValue >& rxOptions ) +{ + SolarMutexGuard aGuard; + + if (0 != nRenderer) + throw IllegalArgumentException(); + + SmDocShell *pDocSh = static_cast < SmDocShell * >( GetObjectShell() ); + if (!pDocSh) + throw RuntimeException(); + + // get device to be rendered in + uno::Reference< awt::XDevice > xRenderDevice; + for (const auto& rxOption : rxOptions) + { + if( rxOption.Name == "RenderDevice" ) + rxOption.Value >>= xRenderDevice; + } + + if (!xRenderDevice.is()) + return; + + VCLXDevice* pDevice = comphelper::getUnoTunnelImplementation<VCLXDevice>( xRenderDevice ); + VclPtr< OutputDevice> pOut = pDevice ? pDevice->GetOutputDevice() + : VclPtr< OutputDevice >(); + if (!pOut) + throw RuntimeException(); + + pOut->SetMapMode(MapMode(MapUnit::Map100thMM)); + + uno::Reference< frame::XModel > xModel; + rSelection >>= xModel; + if (xModel != pDocSh->GetModel()) + return; + + //!! when called via API we may not have an active view + //!! thus we go and look for a view that can be used. + SfxViewShell* pViewSh = SfxViewShell::GetFirst( false /* search non-visible views as well*/, checkSfxViewShell<SmViewShell> ); + while (pViewSh && pViewSh->GetObjectShell() != pDocSh) + pViewSh = SfxViewShell::GetNext( *pViewSh, false /* search non-visible views as well*/, checkSfxViewShell<SmViewShell> ); + SmViewShell *pView = dynamic_cast< SmViewShell *>( pViewSh ); + SAL_WARN_IF( !pView, "starmath", "SmModel::render : no SmViewShell found" ); + + if (!pView) + return; + + SmPrinterAccess aPrinterAccess( *pDocSh ); + Printer *pPrinter = aPrinterAccess.GetPrinter(); + + Size aPrtPaperSize ( pPrinter->GetPaperSize() ); + Size aOutputSize ( pPrinter->GetOutputSize() ); + Point aPrtPageOffset( pPrinter->GetPageOffset() ); + + // no real printer ?? + if (aPrtPaperSize.IsEmpty()) + { + aPrtPaperSize = lcl_GuessPaperSize(); + // factors from Windows DIN A4 + aOutputSize = Size( static_cast<long>(aPrtPaperSize.Width() * 0.941), + static_cast<long>(aPrtPaperSize.Height() * 0.961)); + aPrtPageOffset = Point( static_cast<long>(aPrtPaperSize.Width() * 0.0250), + static_cast<long>(aPrtPaperSize.Height() * 0.0214)); + } + tools::Rectangle OutputRect( Point(), aOutputSize ); + + + // set minimum top and bottom border + if (aPrtPageOffset.Y() < 2000) + OutputRect.AdjustTop(2000 - aPrtPageOffset.Y() ); + if ((aPrtPaperSize.Height() - (aPrtPageOffset.Y() + OutputRect.Bottom())) < 2000) + OutputRect.AdjustBottom( -(2000 - (aPrtPaperSize.Height() - + (aPrtPageOffset.Y() + OutputRect.Bottom()))) ); + + // set minimum left and right border + if (aPrtPageOffset.X() < 2500) + OutputRect.AdjustLeft(2500 - aPrtPageOffset.X() ); + if ((aPrtPaperSize.Width() - (aPrtPageOffset.X() + OutputRect.Right())) < 1500) + OutputRect.AdjustRight( -(1500 - (aPrtPaperSize.Width() - + (aPrtPageOffset.X() + OutputRect.Right()))) ); + + if (!m_pPrintUIOptions) + m_pPrintUIOptions.reset(new SmPrintUIOptions); + m_pPrintUIOptions->processProperties( rxOptions ); + + pView->Impl_Print( *pOut, *m_pPrintUIOptions, OutputRect ); + + // release SmPrintUIOptions when everything is done. + // That way, when SmPrintUIOptions is needed again it will read the latest configuration settings in its c-tor. + if (m_pPrintUIOptions->getBoolValue( "IsLastPage" )) + { + m_pPrintUIOptions.reset(); + } +} + +void SAL_CALL SmModel::setParent( const uno::Reference< uno::XInterface >& xParent) +{ + SolarMutexGuard aGuard; + SfxBaseModel::setParent( xParent ); + uno::Reference< lang::XUnoTunnel > xParentTunnel( xParent, uno::UNO_QUERY ); + if ( xParentTunnel.is() ) + { + SvGlobalName aSfxIdent( SFX_GLOBAL_CLASSID ); + SfxObjectShell* pDoc = reinterpret_cast<SfxObjectShell *>(xParentTunnel->getSomething( + aSfxIdent.GetByteSequence() ) ); + if ( pDoc ) + GetObjectShell()->OnDocumentPrinterChanged( pDoc->GetDocumentPrinter() ); + } +} + +void SmModel::writeFormulaOoxml( + ::sax_fastparser::FSHelperPtr const pSerializer, + oox::core::OoxmlVersion const version, + oox::drawingml::DocumentType const documentType, sal_Int8 nAlign) +{ + static_cast<SmDocShell*>(GetObjectShell())->writeFormulaOoxml(pSerializer, version, documentType, nAlign); +} + +void SmModel::writeFormulaRtf(OStringBuffer& rBuffer, rtl_TextEncoding nEncoding) +{ + static_cast<SmDocShell*>(GetObjectShell())->writeFormulaRtf(rBuffer, nEncoding); +} + +void SmModel::readFormulaOoxml( oox::formulaimport::XmlStream& stream ) +{ + static_cast< SmDocShell* >( GetObjectShell())->readFormulaOoxml( stream ); +} + +Size SmModel::getFormulaSize() const +{ + return static_cast< SmDocShell* >( GetObjectShell())->GetSize(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/utility.cxx b/starmath/source/utility.cxx new file mode 100644 index 000000000..a842c7ed6 --- /dev/null +++ b/starmath/source/utility.cxx @@ -0,0 +1,240 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <strings.hrc> +#include <smmod.hxx> +#include <utility.hxx> +#include <dialog.hxx> +#include <view.hxx> + +// return pointer to active SmViewShell, if this is not possible +// return 0 instead. +//!! Since this method is based on the current focus it is somewhat +//!! unreliable and may return unexpected 0 pointers! +SmViewShell * SmGetActiveView() +{ + SfxViewShell *pView = SfxViewShell::Current(); + return dynamic_cast<SmViewShell*>( pView); +} + + +/**************************************************************************/ + +void SmFontPickList::Clear() +{ + aFontVec.clear(); +} + +SmFontPickList& SmFontPickList::operator = (const SmFontPickList& rList) +{ + Clear(); + nMaxItems = rList.nMaxItems; + for (const auto & nPos : rList.aFontVec) + aFontVec.push_back( nPos ); + + return *this; +} + +vcl::Font SmFontPickList::Get(sal_uInt16 nPos) const +{ + return nPos < aFontVec.size() ? aFontVec[nPos] : vcl::Font(); +} + +namespace { + +bool lcl_CompareItem(const vcl::Font & rFirstFont, const vcl::Font & rSecondFont) +{ + return rFirstFont.GetFamilyName() == rSecondFont.GetFamilyName() && + rFirstFont.GetFamilyType() == rSecondFont.GetFamilyType() && + rFirstFont.GetCharSet() == rSecondFont.GetCharSet() && + rFirstFont.GetWeight() == rSecondFont.GetWeight() && + rFirstFont.GetItalic() == rSecondFont.GetItalic(); +} + +OUString lcl_GetStringItem(const vcl::Font &rFont) +{ + OUStringBuffer aString(rFont.GetFamilyName()); + + if (IsItalic( rFont )) + { + aString.append(", "); + aString.append(SmResId(RID_FONTITALIC)); + } + if (IsBold( rFont )) + { + aString.append(", "); + aString.append(SmResId(RID_FONTBOLD)); + } + + return aString.makeStringAndClear(); +} + +} + +void SmFontPickList::Insert(const vcl::Font &rFont) +{ + for (size_t nPos = 0; nPos < aFontVec.size(); nPos++) + if (lcl_CompareItem( aFontVec[nPos], rFont)) + { + aFontVec.erase( aFontVec.begin() + nPos ); + break; + } + + aFontVec.push_front( rFont ); + + if (aFontVec.size() > nMaxItems) + { + aFontVec.pop_back(); + } +} + +void SmFontPickList::ReadFrom(const SmFontDialog& rDialog) +{ + Insert(rDialog.GetFont()); +} + +void SmFontPickList::WriteTo(SmFontDialog& rDialog) const +{ + rDialog.SetFont(Get()); +} + + +/**************************************************************************/ + +SmFontPickListBox::SmFontPickListBox(std::unique_ptr<weld::ComboBox> pWidget) + : SmFontPickList(4) + , m_xWidget(std::move(pWidget)) +{ + m_xWidget->connect_changed(LINK(this, SmFontPickListBox, SelectHdl)); +} + +IMPL_LINK_NOARG(SmFontPickListBox, SelectHdl, weld::ComboBox&, void) +{ + OUString aString; + + const int nPos = m_xWidget->get_active(); + if (nPos != 0) + { + SmFontPickList::Insert(Get(nPos)); + aString = m_xWidget->get_text(nPos); + m_xWidget->remove(nPos); + m_xWidget->insert_text(0, aString); + } + + m_xWidget->set_active(0); +} + +SmFontPickListBox& SmFontPickListBox::operator=(const SmFontPickList& rList) +{ + *static_cast<SmFontPickList *>(this) = rList; + + for (decltype(aFontVec)::size_type nPos = 0; nPos < aFontVec.size(); nPos++) + m_xWidget->insert_text(nPos, lcl_GetStringItem(aFontVec[nPos])); + + if (!aFontVec.empty()) + m_xWidget->set_active_text(lcl_GetStringItem(aFontVec.front())); + + return *this; +} + +void SmFontPickListBox::Insert(const vcl::Font &rFont) +{ + SmFontPickList::Insert(rFont); + + OUString aEntry(lcl_GetStringItem(aFontVec.front())); + int nPos = m_xWidget->find_text(aEntry); + if (nPos != -1) + m_xWidget->remove(nPos); + m_xWidget->insert_text(0, aEntry); + m_xWidget->set_active(0); + + while (m_xWidget->get_count() > nMaxItems) + m_xWidget->remove(m_xWidget->get_count() - 1); +} + +bool IsItalic( const vcl::Font &rFont ) +{ + FontItalic eItalic = rFont.GetItalic(); + // the code below leaves only _NONE and _DONTKNOW as not italic + return eItalic == ITALIC_OBLIQUE || eItalic == ITALIC_NORMAL; +} + + +bool IsBold( const vcl::Font &rFont ) +{ + FontWeight eWeight = rFont.GetWeight(); + return eWeight > WEIGHT_NORMAL; +} + + +void SmFace::Impl_Init() +{ + SetSize( GetFontSize() ); + SetTransparent( true ); + SetAlignment( ALIGN_BASELINE ); + SetColor( COL_AUTO ); +} + +void SmFace::SetSize(const Size& rSize) +{ + Size aSize (rSize); + + // check the requested size against minimum value + static int const nMinVal = SmPtsTo100th_mm(2); + + if (aSize.Height() < nMinVal) + aSize.setHeight( nMinVal ); + + //! we don't force a maximum value here because this may prevent eg the + //! parentheses in "left ( ... right )" from matching up with large + //! bodies (eg stack{...} with many entries). + //! Of course this is holds only if characters are used and not polygons. + + Font::SetFontSize(aSize); +} + + +long SmFace::GetBorderWidth() const +{ + if (nBorderWidth < 0) + return GetDefaultBorderWidth(); + else + return nBorderWidth; +} + +SmFace & SmFace::operator = (const SmFace &rFace) +{ + Font::operator = (rFace); + nBorderWidth = -1; + return *this; +} + + +SmFace & operator *= (SmFace &rFace, const Fraction &rFrac) + // scales the width and height of 'rFace' by 'rFrac' and returns a + // reference to 'rFace'. + // It's main use is to make scaling fonts look easier. +{ const Size &rFaceSize = rFace.GetFontSize(); + + rFace.SetSize(Size(long(rFaceSize.Width() * rFrac), + long(rFaceSize.Height() * rFrac))); + return rFace; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/view.cxx b/starmath/source/view.cxx new file mode 100644 index 000000000..19274324a --- /dev/null +++ b/starmath/source/view.cxx @@ -0,0 +1,2034 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XFramesSupplier.hpp> +#include <com/sun/star/container/XChild.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/string.hxx> +#include <i18nutil/unicode.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/docinsert.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/infobar.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/request.hxx> +#include <sfx2/viewfac.hxx> +#include <svl/eitem.hxx> +#include <svl/itemset.hxx> +#include <svl/poolitem.hxx> +#include <svl/stritem.hxx> +#include <vcl/transfer.hxx> +#include <svtools/colorcfg.hxx> +#include <svtools/miscopt.hxx> +#include <svl/whiter.hxx> +#include <svx/zoomslideritem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <svx/svxdlg.hxx> +#include <sfx2/zoomitem.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/decoview.hxx> +#include <vcl/menu.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <sal/log.hxx> +#include <tools/svborder.hxx> + +#include <unotools/streamwrap.hxx> + +#include <unomodel.hxx> +#include <view.hxx> +#include "cfgitem.hxx" +#include <dialog.hxx> +#include <document.hxx> +#include <starmath.hrc> +#include <strings.hrc> +#include <smmod.hxx> +#include "mathmlimport.hxx" +#include <cursor.hxx> +#include "accessibility.hxx" +#include <ElementsDockingWindow.hxx> +#include <helpids.h> +#include <cassert> +#include <memory> + +#define MINZOOM sal_uInt16(25) +#define MAXZOOM sal_uInt16(800) + +// space around the edit window, in pixels +// fdo#69111: Increased border on the top so that the window is +// easier to tear off. +#define CMD_BOX_PADDING 4 +#define CMD_BOX_PADDING_TOP 10 + +#define ShellClass_SmViewShell +#include <smslots.hxx> + +using namespace css; +using namespace css::accessibility; +using namespace css::uno; + +SmGraphicWindow::SmGraphicWindow(SmViewShell* pShell) + : ScrollableWindow(&pShell->GetViewFrame()->GetWindow()) + , pViewShell(pShell) + , nZoom(100) +{ + assert(pViewShell); + // docking windows are usually hidden (often already done in the + // resource) and will be shown by the sfx framework. + Hide(); + + const Fraction aFraction(1, 1); + SetMapMode(MapMode(MapUnit::Map100thMM, Point(), aFraction, aFraction)); + + SetTotalSize(); + + SetHelpId(HID_SMA_WIN_DOCUMENT); + + ShowLine(false); + CaretBlinkInit(); +} + +SmGraphicWindow::~SmGraphicWindow() +{ + disposeOnce(); +} + +void SmGraphicWindow::dispose() +{ + if (mxAccessible.is()) + mxAccessible->ClearWin(); // make Accessible nonfunctional + mxAccessible.clear(); + CaretBlinkStop(); + ScrollableWindow::dispose(); +} + +void SmGraphicWindow::StateChanged(StateChangedType eType) +{ + if (eType == StateChangedType::InitShow) + Show(); + ScrollableWindow::StateChanged(eType); +} + +void SmGraphicWindow::MouseButtonDown(const MouseEvent& rMEvt) +{ + ScrollableWindow::MouseButtonDown(rMEvt); + + GrabFocus(); + + // set formula-cursor and selection of edit window according to the + // position clicked at + + SAL_WARN_IF( rMEvt.GetClicks() == 0, "starmath", "0 clicks" ); + if ( !rMEvt.IsLeft() ) + return; + + // get click position relative to formula + Point aPos (PixelToLogic(rMEvt.GetPosPixel()) + - GetFormulaDrawPos()); + + const SmNode *pTree = pViewShell->GetDoc()->GetFormulaTree(); + if (!pTree) + return; + + if (IsInlineEditEnabled()) { + pViewShell->GetDoc()->GetCursor().MoveTo(this, aPos, !rMEvt.IsShift()); + return; + } + const SmNode *pNode = nullptr; + // if it was clicked inside the formula then get the appropriate node + if (pTree->OrientedDist(aPos) <= 0) + pNode = pTree->FindRectClosestTo(aPos); + + if (!pNode) + return; + + SmEditWindow *pEdit = pViewShell->GetEditWindow(); + if (!pEdit) + return; + const SmToken aToken (pNode->GetToken()); + + // set selection to the beginning of the token + ESelection aSel (aToken.nRow - 1, aToken.nCol - 1); + + if (rMEvt.GetClicks() != 1 || aToken.eType == TPLACE) + aSel.nEndPos = aSel.nEndPos + sal::static_int_cast< sal_uInt16 >(aToken.aText.getLength()); + + pEdit->SetSelection(aSel); + SetCursor(pNode); + + // allow for immediate editing and + //! implicitly synchronize the cursor position mark in this window + pEdit->GrabFocus(); +} + +void SmGraphicWindow::MouseMove(const MouseEvent &rMEvt) +{ + ScrollableWindow::MouseMove(rMEvt); + + if (rMEvt.IsLeft() && IsInlineEditEnabled()) + { + Point aPos(PixelToLogic(rMEvt.GetPosPixel()) - GetFormulaDrawPos()); + pViewShell->GetDoc()->GetCursor().MoveTo(this, aPos, false); + + CaretBlinkStop(); + SetIsCursorVisible(true); + CaretBlinkStart(); + RepaintViewShellDoc(); + } +} + +bool SmGraphicWindow::IsInlineEditEnabled() const +{ + return pViewShell->IsInlineEditEnabled(); +} + +void SmGraphicWindow::GetFocus() +{ + if (!IsInlineEditEnabled()) + return; + if (pViewShell->GetEditWindow()) + pViewShell->GetEditWindow()->Flush(); + //Let view shell know what insertions should be done in visual editor + pViewShell->SetInsertIntoEditWindow(false); + SetIsCursorVisible(true); + ShowLine(true); + CaretBlinkStart(); + RepaintViewShellDoc(); +} + +void SmGraphicWindow::LoseFocus() +{ + ScrollableWindow::LoseFocus(); + if (mxAccessible.is()) + { + uno::Any aOldValue, aNewValue; + aOldValue <<= AccessibleStateType::FOCUSED; + // aNewValue remains empty + mxAccessible->LaunchEvent( AccessibleEventId::STATE_CHANGED, + aOldValue, aNewValue ); + } + if (!IsInlineEditEnabled()) + return; + SetIsCursorVisible(false); + ShowLine(false); + CaretBlinkStop(); + RepaintViewShellDoc(); +} + +void SmGraphicWindow::RepaintViewShellDoc() +{ + SmDocShell* pDoc = pViewShell->GetDoc(); + if (pDoc) + pDoc->Repaint(); +} + +IMPL_LINK_NOARG(SmGraphicWindow, CaretBlinkTimerHdl, Timer *, void) +{ + if (IsCursorVisible()) + SetIsCursorVisible(false); + else + SetIsCursorVisible(true); + + RepaintViewShellDoc(); +} + +void SmGraphicWindow::CaretBlinkInit() +{ + aCaretBlinkTimer.SetInvokeHandler(LINK(this, SmGraphicWindow, CaretBlinkTimerHdl)); + aCaretBlinkTimer.SetTimeout( ScrollableWindow::GetSettings().GetStyleSettings().GetCursorBlinkTime() ); +} + +void SmGraphicWindow::CaretBlinkStart() +{ + if (!IsInlineEditEnabled()) + return; + if (aCaretBlinkTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME) + aCaretBlinkTimer.Start(); +} + +void SmGraphicWindow::CaretBlinkStop() +{ + if (!IsInlineEditEnabled()) + return; + aCaretBlinkTimer.Stop(); +} + +void SmGraphicWindow::ShowCursor(bool bShow) + // shows or hides the formula-cursor depending on 'bShow' is true or not +{ + if (IsInlineEditEnabled()) + return; + + bool bInvert = bShow != IsCursorVisible(); + + if (bInvert) + InvertTracking(aCursorRect, ShowTrackFlags::Small | ShowTrackFlags::TrackWindow); + + SetIsCursorVisible(bShow); +} + +void SmGraphicWindow::ShowLine(bool bShow) +{ + if (!IsInlineEditEnabled()) + return; + + bIsLineVisible = bShow; +} + +void SmGraphicWindow::SetCursor(const SmNode *pNode) +{ + if (IsInlineEditEnabled()) + return; + + const SmNode *pTree = pViewShell->GetDoc()->GetFormulaTree(); + + // get appropriate rectangle + Point aOffset (pNode->GetTopLeft() - pTree->GetTopLeft()), + aTLPos (GetFormulaDrawPos() + aOffset); + aTLPos.AdjustX( -(pNode->GetItalicLeftSpace()) ); + Size aSize (pNode->GetItalicSize()); + + SetCursor(tools::Rectangle(aTLPos, aSize)); +} + +void SmGraphicWindow::SetCursor(const tools::Rectangle &rRect) + // sets cursor to new position (rectangle) 'rRect'. + // The old cursor will be removed, and the new one will be shown if + // that is activated in the ConfigItem +{ + if (IsInlineEditEnabled()) + return; + + SmModule *pp = SM_MOD(); + + if (IsCursorVisible()) + ShowCursor(false); // clean up remainings of old cursor + aCursorRect = rRect; + if (pp->GetConfig()->IsShowFormulaCursor()) + ShowCursor(true); // draw new cursor +} + +const SmNode * SmGraphicWindow::SetCursorPos(sal_uInt16 nRow, sal_uInt16 nCol) + // looks for a VISIBLE node in the formula tree with its token at + // (or around) the position 'nRow', 'nCol' in the edit window + // (row and column numbering starts with 1 there!). + // If there is such a node the formula-cursor is set to cover that nodes + // rectangle. If not the formula-cursor will be hidden. + // In any case the search result is being returned. +{ + if (IsInlineEditEnabled()) + return nullptr; + + // find visible node with token at nRow, nCol + const SmNode *pTree = pViewShell->GetDoc()->GetFormulaTree(), + *pNode = nullptr; + if (pTree) + pNode = pTree->FindTokenAt(nRow, nCol); + + if (pNode) + SetCursor(pNode); + else + ShowCursor(false); + + return pNode; +} + +void SmGraphicWindow::ApplySettings(vcl::RenderContext& rRenderContext) +{ + rRenderContext.SetBackground(SM_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor); +} + +void SmGraphicWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + SmDocShell& rDoc = *pViewShell->GetDoc(); + Point aPoint; + + rDoc.DrawFormula(rRenderContext, aPoint, true); //! modifies aPoint to be the topleft + //! corner of the formula + aFormulaDrawPos = aPoint; + if (IsInlineEditEnabled()) + { + //Draw cursor if any... + if (pViewShell->GetDoc()->HasCursor() && IsLineVisible()) + pViewShell->GetDoc()->GetCursor().Draw(rRenderContext, aPoint, IsCursorVisible()); + } + else + { + SetIsCursorVisible(false); // (old) cursor must be drawn again + + const SmEditWindow* pEdit = pViewShell->GetEditWindow(); + if (pEdit) + { // get new position for formula-cursor (for possible altered formula) + sal_Int32 nRow; + sal_uInt16 nCol; + SmGetLeftSelectionPart(pEdit->GetSelection(), nRow, nCol); + nRow++; + nCol++; + const SmNode *pFound = SetCursorPos(static_cast<sal_uInt16>(nRow), nCol); + + SmModule *pp = SM_MOD(); + if (pFound && pp->GetConfig()->IsShowFormulaCursor()) + ShowCursor(true); + } + } +} + + +void SmGraphicWindow::SetTotalSize () +{ + SmDocShell &rDoc = *pViewShell->GetDoc(); + const Size aTmp( PixelToLogic( LogicToPixel( rDoc.GetSize() ))); + if ( aTmp != ScrollableWindow::GetTotalSize() ) + ScrollableWindow::SetTotalSize( aTmp ); +} + +void SmGraphicWindow::KeyInput(const KeyEvent& rKEvt) +{ + if (!IsInlineEditEnabled()) { + if (! (GetView() && GetView()->KeyInput(rKEvt)) ) + ScrollableWindow::KeyInput(rKEvt); + return; + } + + SmCursor& rCursor = pViewShell->GetDoc()->GetCursor(); + KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction(); + if (eFunc == KeyFuncType::COPY) + rCursor.Copy(); + else if (eFunc == KeyFuncType::CUT) + rCursor.Cut(); + else if (eFunc == KeyFuncType::PASTE) + rCursor.Paste(); + else { + sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode(); + switch(nCode) + { + case KEY_LEFT: + { + rCursor.Move(this, MoveLeft, !rKEvt.GetKeyCode().IsShift()); + }break; + case KEY_RIGHT: + { + rCursor.Move(this, MoveRight, !rKEvt.GetKeyCode().IsShift()); + }break; + case KEY_UP: + { + rCursor.Move(this, MoveUp, !rKEvt.GetKeyCode().IsShift()); + }break; + case KEY_DOWN: + { + rCursor.Move(this, MoveDown, !rKEvt.GetKeyCode().IsShift()); + }break; + case KEY_RETURN: + { + if(!rKEvt.GetKeyCode().IsShift()) + rCursor.InsertRow(); + }break; + case KEY_DELETE: + { + if(!rCursor.HasSelection()){ + rCursor.Move(this, MoveRight, false); + if(rCursor.HasComplexSelection()) break; + } + rCursor.Delete(); + }break; + case KEY_BACKSPACE: + { + rCursor.DeletePrev(this); + }break; + case KEY_ADD: + rCursor.InsertElement(PlusElement); + break; + case KEY_SUBTRACT: + if(rKEvt.GetKeyCode().IsShift()) + rCursor.InsertSubSup(RSUB); + else + rCursor.InsertElement(MinusElement); + break; + case KEY_MULTIPLY: + rCursor.InsertElement(CDotElement); + break; + case KEY_DIVIDE: + rCursor.InsertFraction(); + break; + case KEY_LESS: + rCursor.InsertElement(LessThanElement); + break; + case KEY_GREATER: + rCursor.InsertElement(GreaterThanElement); + break; + case KEY_EQUAL: + rCursor.InsertElement(EqualElement); + break; + default: + { + sal_Unicode code = rKEvt.GetCharCode(); + SmBraceNode* pBraceNode = nullptr; + + if(code == ' ') { + rCursor.InsertElement(BlankElement); + }else if(code == '^') { + rCursor.InsertSubSup(RSUP); + }else if(code == '(') { + rCursor.InsertBrackets(SmBracketType::Round); + }else if(code == '[') { + rCursor.InsertBrackets(SmBracketType::Square); + }else if(code == '{') { + rCursor.InsertBrackets(SmBracketType::Curly); + }else if(code == '!') { + rCursor.InsertElement(FactorialElement); + }else if(code == '%') { + rCursor.InsertElement(PercentElement); + }else if(code == ')' && rCursor.IsAtTailOfBracket(SmBracketType::Round, &pBraceNode)) { + rCursor.MoveAfterBracket(pBraceNode); + }else if(code == ']' && rCursor.IsAtTailOfBracket(SmBracketType::Square, &pBraceNode)) { + rCursor.MoveAfterBracket(pBraceNode); + }else if(code == '}' && rCursor.IsAtTailOfBracket(SmBracketType::Curly, &pBraceNode)) { + rCursor.MoveAfterBracket(pBraceNode); + }else{ + if(code != 0){ + rCursor.InsertText(OUString(code)); + }else if (! (GetView() && GetView()->KeyInput(rKEvt)) ) + ScrollableWindow::KeyInput(rKEvt); + } + } + } + } + CaretBlinkStop(); + CaretBlinkStart(); + SetIsCursorVisible(true); + RepaintViewShellDoc(); +} + + +void SmGraphicWindow::Command(const CommandEvent& rCEvt) +{ + bool bCallBase = true; + if ( !pViewShell->GetViewFrame()->GetFrame().IsInPlace() ) + { + switch ( rCEvt.GetCommand() ) + { + case CommandEventId::ContextMenu: + { + GetParent()->ToTop(); + Point aPos(5, 5); + if (rCEvt.IsMouseEvent()) + aPos = rCEvt.GetMousePosPixel(); + + // added for replaceability of context menus + SfxDispatcher::ExecutePopup( this, &aPos ); + + bCallBase = false; + } + break; + + case CommandEventId::Wheel: + { + const CommandWheelData* pWData = rCEvt.GetWheelData(); + if ( pWData && CommandWheelMode::ZOOM == pWData->GetMode() ) + { + sal_uInt16 nTmpZoom = GetZoom(); + if( 0 > pWData->GetDelta() ) + nTmpZoom -= 10; + else + nTmpZoom += 10; + SetZoom( nTmpZoom ); + bCallBase = false; + } + } + break; + + default: break; + } + } + if ( bCallBase ) + ScrollableWindow::Command (rCEvt); +} + + +void SmGraphicWindow::SetZoom(sal_uInt16 Factor) +{ + nZoom = std::min(std::max(Factor, MINZOOM), MAXZOOM); + Fraction aFraction (nZoom, 100); + SetMapMode( MapMode(MapUnit::Map100thMM, Point(), aFraction, aFraction) ); + SetTotalSize(); + SmViewShell *pViewSh = GetView(); + if (pViewSh) + { + pViewSh->GetViewFrame()->GetBindings().Invalidate(SID_ATTR_ZOOM); + pViewSh->GetViewFrame()->GetBindings().Invalidate(SID_ATTR_ZOOMSLIDER); + } + Invalidate(); +} + + +void SmGraphicWindow::ZoomToFitInWindow() +{ + SmDocShell &rDoc = *pViewShell->GetDoc(); + + // set defined mapmode before calling 'LogicToPixel' below + SetMapMode(MapMode(MapUnit::Map100thMM)); + + Size aSize (LogicToPixel(rDoc.GetSize())); + Size aWindowSize (GetSizePixel()); + + if (!aSize.IsEmpty()) + { + long nVal = std::min ((85 * aWindowSize.Width()) / aSize.Width(), + (85 * aWindowSize.Height()) / aSize.Height()); + SetZoom ( sal::static_int_cast< sal_uInt16 >(nVal) ); + } +} + +uno::Reference< XAccessible > SmGraphicWindow::CreateAccessible() +{ + if (!mxAccessible.is()) + { + mxAccessible = new SmGraphicAccessible( this ); + } + return mxAccessible.get(); +} + +/**************************************************************************/ + + +SmGraphicController::SmGraphicController(SmGraphicWindow &rSmGraphic, + sal_uInt16 nId_, + SfxBindings &rBindings) : + SfxControllerItem(nId_, rBindings), + rGraphic(rSmGraphic) +{ +} + + +void SmGraphicController::StateChanged(sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState) +{ + rGraphic.SetTotalSize(); + rGraphic.Invalidate(); + SfxControllerItem::StateChanged (nSID, eState, pState); +} + + +/**************************************************************************/ + + +SmEditController::SmEditController(SmEditWindow &rSmEdit, + sal_uInt16 nId_, + SfxBindings &rBindings) : + SfxControllerItem(nId_, rBindings), + rEdit(rSmEdit) +{ +} + + + +void SmEditController::StateChanged(sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState) +{ + const SfxStringItem *pItem = dynamic_cast<const SfxStringItem*>( pState); + + if ((pItem != nullptr) && (rEdit.GetText() != pItem->GetValue())) + rEdit.SetText(pItem->GetValue()); + SfxControllerItem::StateChanged (nSID, eState, pState); +} + +/**************************************************************************/ +SmCmdBoxWindow::SmCmdBoxWindow(SfxBindings *pBindings_, SfxChildWindow *pChildWindow, + vcl::Window *pParent) : + SfxDockingWindow(pBindings_, pChildWindow, pParent, WB_MOVEABLE|WB_CLOSEABLE|WB_SIZEABLE|WB_DOCKABLE), + aEdit (VclPtr<SmEditWindow>::Create(*this)), + aController (*aEdit, SID_TEXT, *pBindings_), + bExiting (false) +{ + SetHelpId( HID_SMA_COMMAND_WIN ); + SetSizePixel(LogicToPixel(Size(292 , 94), MapMode(MapUnit::MapAppFont))); + SetText(SmResId(STR_CMDBOXWINDOW)); + + Hide(); + + aInitialFocusTimer.SetInvokeHandler(LINK(this, SmCmdBoxWindow, InitialFocusTimerHdl)); + aInitialFocusTimer.SetTimeout(100); +} + +SmCmdBoxWindow::~SmCmdBoxWindow () +{ + disposeOnce(); +} + +void SmCmdBoxWindow::dispose() +{ + aInitialFocusTimer.Stop(); + bExiting = true; + aController.dispose(); + aEdit.disposeAndClear(); + SfxDockingWindow::dispose(); +} + +SmViewShell * SmCmdBoxWindow::GetView() +{ + SfxDispatcher *pDispatcher = GetBindings().GetDispatcher(); + SfxViewShell *pView = pDispatcher ? pDispatcher->GetFrame()->GetViewShell() : nullptr; + return dynamic_cast<SmViewShell*>( pView); +} + +void SmCmdBoxWindow::Resize() +{ + tools::Rectangle aRect(Point(0, 0), GetOutputSizePixel()); + aRect.AdjustLeft(CMD_BOX_PADDING ); + aRect.AdjustTop(CMD_BOX_PADDING_TOP ); + aRect.AdjustRight( -(CMD_BOX_PADDING) ); + aRect.AdjustBottom( -(CMD_BOX_PADDING) ); + + DecorationView aView(this); + aRect = aView.DrawFrame(aRect, DrawFrameStyle::In, DrawFrameFlags::NoDraw); + + aEdit->SetPosSizePixel(aRect.TopLeft(), aRect.GetSize()); + SfxDockingWindow::Resize(); + Invalidate(); +} + +void SmCmdBoxWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/) +{ + tools::Rectangle aRect(Point(0, 0), GetOutputSizePixel()); + aRect.AdjustLeft(CMD_BOX_PADDING ); + aRect.AdjustTop(CMD_BOX_PADDING_TOP ); + aRect.AdjustRight( -(CMD_BOX_PADDING) ); + aRect.AdjustBottom( -(CMD_BOX_PADDING) ); + + aEdit->SetPosSizePixel(aRect.TopLeft(), aRect.GetSize()); + + DecorationView aView(&rRenderContext); + aView.DrawFrame( aRect, DrawFrameStyle::In ); +} + +Size SmCmdBoxWindow::CalcDockingSize(SfxChildAlignment eAlign) +{ + switch (eAlign) + { + case SfxChildAlignment::LEFT: + case SfxChildAlignment::RIGHT: + return Size(); + default: + break; + } + return SfxDockingWindow::CalcDockingSize(eAlign); +} + +SfxChildAlignment SmCmdBoxWindow::CheckAlignment(SfxChildAlignment eActual, + SfxChildAlignment eWish) +{ + switch (eWish) + { + case SfxChildAlignment::TOP: + case SfxChildAlignment::BOTTOM: + case SfxChildAlignment::NOALIGNMENT: + return eWish; + default: + break; + } + + return eActual; +} + +void SmCmdBoxWindow::StateChanged( StateChangedType nStateChange ) +{ + if (StateChangedType::InitShow == nStateChange) + { + Resize(); // avoid SmEditWindow not being painted correctly + + // set initial position of window in floating mode + if (IsFloatingMode()) + AdjustPosition(); //! don't change pos in docking-mode ! + + aInitialFocusTimer.Start(); + } + + SfxDockingWindow::StateChanged( nStateChange ); +} + +IMPL_LINK_NOARG( SmCmdBoxWindow, InitialFocusTimerHdl, Timer *, void ) +{ + // We want to have the focus in the edit window once Math has been opened + // to allow for immediate typing. + // Problem: There is no proper way to do this + // Thus: this timer based solution has been implemented (see GrabFocus below) + + // Follow-up problem (#i114910): grabbing the focus may bust the help system since + // it relies on getting the current frame which conflicts with grabbing the focus. + // Thus aside from the 'GrabFocus' call everything else is to get the + // help reliably working despite using 'GrabFocus'. + + try + { + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( comphelper::getProcessComponentContext() ); + + aEdit->GrabFocus(); + + SmViewShell* pView = GetView(); + assert(pView); + bool bInPlace = pView->GetViewFrame()->GetFrame().IsInPlace(); + uno::Reference< frame::XFrame > xFrame( GetBindings().GetDispatcher()->GetFrame()->GetFrame().GetFrameInterface()); + if ( bInPlace ) + { + uno::Reference<container::XChild> xModel(pView->GetDoc()->GetModel(), + uno::UNO_QUERY_THROW); + uno::Reference< frame::XModel > xParent( xModel->getParent(), uno::UNO_QUERY_THROW ); + uno::Reference< frame::XController > xParentCtrler( xParent->getCurrentController() ); + uno::Reference< frame::XFramesSupplier > xParentFrame( xParentCtrler->getFrame(), uno::UNO_QUERY_THROW ); + xParentFrame->setActiveFrame( xFrame ); + } + else + { + xDesktop->setActiveFrame( xFrame ); + } + } + catch (uno::Exception &) + { + SAL_WARN( "starmath", "failed to properly set initial focus to edit window" ); + } +} + +void SmCmdBoxWindow::AdjustPosition() +{ + const tools::Rectangle aRect( Point(), GetParent()->GetOutputSizePixel() ); + Point aTopLeft( Point( aRect.Left(), + aRect.Bottom() - GetSizePixel().Height() ) ); + Point aPos( GetParent()->OutputToScreenPixel( aTopLeft ) ); + if (aPos.X() < 0) + aPos.setX( 0 ); + if (aPos.Y() < 0) + aPos.setY( 0 ); + SetPosPixel( aPos ); +} + +void SmCmdBoxWindow::ToggleFloatingMode() +{ + SfxDockingWindow::ToggleFloatingMode(); + + if (GetFloatingWindow()) + GetFloatingWindow()->SetMinOutputSizePixel(Size (200, 50)); +} + +void SmCmdBoxWindow::GetFocus() +{ + if (!bExiting) + aEdit->GrabFocus(); +} + +SFX_IMPL_DOCKINGWINDOW_WITHID(SmCmdBoxWrapper, SID_CMDBOXWINDOW); + +SmCmdBoxWrapper::SmCmdBoxWrapper(vcl::Window *pParentWindow, sal_uInt16 nId, + SfxBindings *pBindings, + SfxChildWinInfo *pInfo) : + SfxChildWindow(pParentWindow, nId) +{ + SetWindow(VclPtr<SmCmdBoxWindow>::Create(pBindings, this, pParentWindow)); + + // make window docked to the bottom initially (after first start) + SetAlignment(SfxChildAlignment::BOTTOM); + static_cast<SfxDockingWindow *>(GetWindow())->Initialize(pInfo); +} + +struct SmViewShell_Impl +{ + std::unique_ptr<sfx2::DocumentInserter> pDocInserter; + std::unique_ptr<SfxRequest> pRequest; + SvtMiscOptions aOpts; +}; + +SFX_IMPL_SUPERCLASS_INTERFACE(SmViewShell, SfxViewShell) + +void SmViewShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_TOOLS, + SfxVisibilityFlags::Standard | SfxVisibilityFlags::FullScreen | SfxVisibilityFlags::Server, + ToolbarId::Math_Toolbox); + //Dummy-Objectbar, to avoid quiver while activating + + GetStaticInterface()->RegisterChildWindow(SmCmdBoxWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(SmElementsDockingWindowWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(SfxInfoBarContainerChild::GetChildWindowId()); +} + +SFX_IMPL_NAMED_VIEWFACTORY(SmViewShell, "Default") +{ + SFX_VIEW_REGISTRATION(SmDocShell); +} + +void SmViewShell::InnerResizePixel(const Point &rOfs, const Size &rSize, bool) +{ + Size aObjSize = GetObjectShell()->GetVisArea().GetSize(); + if ( !aObjSize.IsEmpty() ) + { + Size aProvidedSize = GetWindow()->PixelToLogic(rSize, MapMode(MapUnit::Map100thMM)); + SfxViewShell::SetZoomFactor( Fraction( aProvidedSize.Width(), aObjSize.Width() ), + Fraction( aProvidedSize.Height(), aObjSize.Height() ) ); + } + + SetBorderPixel( SvBorder() ); + GetGraphicWindow().SetPosSizePixel(rOfs, rSize); + GetGraphicWindow().SetTotalSize(); +} + +void SmViewShell::OuterResizePixel(const Point &rOfs, const Size &rSize) +{ + SmGraphicWindow &rWin = GetGraphicWindow(); + rWin.SetPosSizePixel(rOfs, rSize); + if (GetDoc()->IsPreview()) + rWin.ZoomToFitInWindow(); + rWin.PaintImmediately(); +} + +void SmViewShell::QueryObjAreaPixel( tools::Rectangle& rRect ) const +{ + rRect.SetSize( GetGraphicWindow().GetSizePixel() ); +} + +void SmViewShell::SetZoomFactor( const Fraction &rX, const Fraction &rY ) +{ + const Fraction &rFrac = std::min(rX, rY); + GetGraphicWindow().SetZoom(sal::static_int_cast<sal_uInt16>(long(rFrac * Fraction( 100, 1 )))); + + //To avoid rounding errors base class regulates crooked values too + //if necessary + SfxViewShell::SetZoomFactor( rX, rY ); +} + +Size SmViewShell::GetTextLineSize(OutputDevice const & rDevice, const OUString& rLine) +{ + Size aSize(rDevice.GetTextWidth(rLine), rDevice.GetTextHeight()); + const long nTabPos = rLine.isEmpty() ? 0 : rDevice.approximate_digit_width() * 8; + + if (nTabPos) + { + aSize.setWidth( 0 ); + sal_Int32 nPos = 0; + do + { + if (nPos > 0) + aSize.setWidth( ((aSize.Width() / nTabPos) + 1) * nTabPos ); + + const OUString aText = rLine.getToken(0, '\t', nPos); + aSize.AdjustWidth(rDevice.GetTextWidth(aText) ); + } + while (nPos >= 0); + } + + return aSize; +} + +Size SmViewShell::GetTextSize(OutputDevice const & rDevice, const OUString& rText, long MaxWidth) +{ + Size aSize; + Size aTextSize; + if (rText.isEmpty()) + return aTextSize; + + sal_Int32 nPos = 0; + do + { + OUString aLine = rText.getToken(0, '\n', nPos); + aLine = aLine.replaceAll("\r", ""); + + aSize = GetTextLineSize(rDevice, aLine); + + if (aSize.Width() > MaxWidth) + { + do + { + OUString aText; + sal_Int32 m = aLine.getLength(); + sal_Int32 nLen = m; + + for (sal_Int32 n = 0; n < nLen; n++) + { + sal_Unicode cLineChar = aLine[n]; + if ((cLineChar == ' ') || (cLineChar == '\t')) + { + aText = aLine.copy(0, n); + if (GetTextLineSize(rDevice, aText).Width() < MaxWidth) + m = n; + else + break; + } + } + + aText = aLine.copy(0, m); + aLine = aLine.replaceAt(0, m, ""); + aSize = GetTextLineSize(rDevice, aText); + aTextSize.AdjustHeight(aSize.Height() ); + aTextSize.setWidth( std::max(aTextSize.Width(), std::min(aSize.Width(), MaxWidth)) ); + + aLine = comphelper::string::stripStart(aLine, ' '); + aLine = comphelper::string::stripStart(aLine, '\t'); + aLine = comphelper::string::stripStart(aLine, ' '); + } + while (!aLine.isEmpty()); + } + else + { + aTextSize.AdjustHeight(aSize.Height() ); + aTextSize.setWidth( std::max(aTextSize.Width(), aSize.Width()) ); + } + } + while (nPos >= 0); + + return aTextSize; +} + +void SmViewShell::DrawTextLine(OutputDevice& rDevice, const Point& rPosition, const OUString& rLine) +{ + Point aPoint(rPosition); + const long nTabPos = rLine.isEmpty() ? 0 : rDevice.approximate_digit_width() * 8; + + if (nTabPos) + { + sal_Int32 nPos = 0; + do + { + if (nPos > 0) + aPoint.setX( ((aPoint.X() / nTabPos) + 1) * nTabPos ); + + OUString aText = rLine.getToken(0, '\t', nPos); + rDevice.DrawText(aPoint, aText); + aPoint.AdjustX(rDevice.GetTextWidth(aText) ); + } + while ( nPos >= 0 ); + } + else + rDevice.DrawText(aPoint, rLine); +} + + +void SmViewShell::DrawText(OutputDevice& rDevice, const Point& rPosition, const OUString& rText, sal_uInt16 MaxWidth) +{ + if (rText.isEmpty()) + return; + + Point aPoint(rPosition); + Size aSize; + + sal_Int32 nPos = 0; + do + { + OUString aLine = rText.getToken(0, '\n', nPos); + aLine = aLine.replaceAll("\r", ""); + aSize = GetTextLineSize(rDevice, aLine); + if (aSize.Width() > MaxWidth) + { + do + { + OUString aText; + sal_Int32 m = aLine.getLength(); + sal_Int32 nLen = m; + + for (sal_Int32 n = 0; n < nLen; n++) + { + sal_Unicode cLineChar = aLine[n]; + if ((cLineChar == ' ') || (cLineChar == '\t')) + { + aText = aLine.copy(0, n); + if (GetTextLineSize(rDevice, aText).Width() < MaxWidth) + m = n; + else + break; + } + } + aText = aLine.copy(0, m); + aLine = aLine.replaceAt(0, m, ""); + + DrawTextLine(rDevice, aPoint, aText); + aPoint.AdjustY(aSize.Height() ); + + aLine = comphelper::string::stripStart(aLine, ' '); + aLine = comphelper::string::stripStart(aLine, '\t'); + aLine = comphelper::string::stripStart(aLine, ' '); + } + while (GetTextLineSize(rDevice, aLine).Width() > MaxWidth); + + // print the remaining text + if (!aLine.isEmpty()) + { + DrawTextLine(rDevice, aPoint, aLine); + aPoint.AdjustY(aSize.Height() ); + } + } + else + { + DrawTextLine(rDevice, aPoint, aLine); + aPoint.AdjustY(aSize.Height() ); + } + } + while ( nPos >= 0 ); +} + +void SmViewShell::Impl_Print(OutputDevice &rOutDev, const SmPrintUIOptions &rPrintUIOptions, tools::Rectangle aOutRect ) +{ + const bool bIsPrintTitle = rPrintUIOptions.getBoolValue( PRTUIOPT_TITLE_ROW, true ); + const bool bIsPrintFrame = rPrintUIOptions.getBoolValue( PRTUIOPT_BORDER, true ); + const bool bIsPrintFormulaText = rPrintUIOptions.getBoolValue( PRTUIOPT_FORMULA_TEXT, true ); + SmPrintSize ePrintSize( static_cast< SmPrintSize >( rPrintUIOptions.getIntValue( PRTUIOPT_PRINT_FORMAT, PRINT_SIZE_NORMAL ) )); + const sal_uInt16 nZoomFactor = static_cast< sal_uInt16 >(rPrintUIOptions.getIntValue( PRTUIOPT_PRINT_SCALE, 100 )); + + rOutDev.Push(); + rOutDev.SetLineColor( COL_BLACK ); + + // output text on top + if (bIsPrintTitle) + { + Size aSize600 (0, 600); + Size aSize650 (0, 650); + vcl::Font aFont(FAMILY_DONTKNOW, aSize600); + + aFont.SetAlignment(ALIGN_TOP); + aFont.SetWeight(WEIGHT_BOLD); + aFont.SetFontSize(aSize650); + aFont.SetColor( COL_BLACK ); + rOutDev.SetFont(aFont); + + Size aTitleSize (GetTextSize(rOutDev, GetDoc()->GetTitle(), aOutRect.GetWidth() - 200)); + + aFont.SetWeight(WEIGHT_NORMAL); + aFont.SetFontSize(aSize600); + rOutDev.SetFont(aFont); + + Size aDescSize (GetTextSize(rOutDev, GetDoc()->GetComment(), aOutRect.GetWidth() - 200)); + + if (bIsPrintFrame) + rOutDev.DrawRect(tools::Rectangle(aOutRect.TopLeft(), + Size(aOutRect.GetWidth(), 100 + aTitleSize.Height() + 200 + aDescSize.Height() + 100))); + aOutRect.AdjustTop(200 ); + + // output title + aFont.SetWeight(WEIGHT_BOLD); + aFont.SetFontSize(aSize650); + rOutDev.SetFont(aFont); + Point aPoint(aOutRect.Left() + (aOutRect.GetWidth() - aTitleSize.Width()) / 2, + aOutRect.Top()); + DrawText(rOutDev, aPoint, GetDoc()->GetTitle(), + sal::static_int_cast< sal_uInt16 >(aOutRect.GetWidth() - 200)); + aOutRect.AdjustTop(aTitleSize.Height() + 200 ); + + // output description + aFont.SetWeight(WEIGHT_NORMAL); + aFont.SetFontSize(aSize600); + rOutDev.SetFont(aFont); + aPoint.setX( aOutRect.Left() + (aOutRect.GetWidth() - aDescSize.Width()) / 2 ); + aPoint.setY( aOutRect.Top() ); + DrawText(rOutDev, aPoint, GetDoc()->GetComment(), + sal::static_int_cast< sal_uInt16 >(aOutRect.GetWidth() - 200)); + aOutRect.AdjustTop(aDescSize.Height() + 300 ); + } + + // output text on bottom + if (bIsPrintFormulaText) + { + vcl::Font aFont(FAMILY_DONTKNOW, Size(0, 600)); + aFont.SetAlignment(ALIGN_TOP); + aFont.SetColor( COL_BLACK ); + + // get size + rOutDev.SetFont(aFont); + + Size aSize (GetTextSize(rOutDev, GetDoc()->GetText(), aOutRect.GetWidth() - 200)); + + aOutRect.AdjustBottom( -(aSize.Height() + 600) ); + + if (bIsPrintFrame) + rOutDev.DrawRect(tools::Rectangle(aOutRect.BottomLeft(), + Size(aOutRect.GetWidth(), 200 + aSize.Height() + 200))); + + Point aPoint (aOutRect.Left() + (aOutRect.GetWidth() - aSize.Width()) / 2, + aOutRect.Bottom() + 300); + DrawText(rOutDev, aPoint, GetDoc()->GetText(), + sal::static_int_cast< sal_uInt16 >(aOutRect.GetWidth() - 200)); + aOutRect.AdjustBottom( -200 ); + } + + if (bIsPrintFrame) + rOutDev.DrawRect(aOutRect); + + aOutRect.AdjustTop(100 ); + aOutRect.AdjustLeft(100 ); + aOutRect.AdjustBottom( -100 ); + aOutRect.AdjustRight( -100 ); + + Size aSize (GetDoc()->GetSize()); + + MapMode OutputMapMode; + // PDF export should always use PRINT_SIZE_NORMAL ... + if (!rPrintUIOptions.getBoolValue( "IsPrinter" ) ) + ePrintSize = PRINT_SIZE_NORMAL; + switch (ePrintSize) + { + case PRINT_SIZE_NORMAL: + OutputMapMode = MapMode(MapUnit::Map100thMM); + break; + + case PRINT_SIZE_SCALED: + if (!aSize.IsEmpty()) + { + Size OutputSize (rOutDev.LogicToPixel(Size(aOutRect.GetWidth(), + aOutRect.GetHeight()), MapMode(MapUnit::Map100thMM))); + Size GraphicSize (rOutDev.LogicToPixel(aSize, MapMode(MapUnit::Map100thMM))); + sal_uInt16 nZ = sal::static_int_cast<sal_uInt16>(std::min(long(Fraction(OutputSize.Width() * 100, GraphicSize.Width())), + long(Fraction(OutputSize.Height() * 100, GraphicSize.Height())))); + nZ -= 10; + Fraction aFraction (std::max(MINZOOM, std::min(MAXZOOM, nZ)), 100); + + OutputMapMode = MapMode(MapUnit::Map100thMM, Point(), aFraction, aFraction); + } + else + OutputMapMode = MapMode(MapUnit::Map100thMM); + break; + + case PRINT_SIZE_ZOOMED: + { + Fraction aFraction( nZoomFactor, 100 ); + + OutputMapMode = MapMode(MapUnit::Map100thMM, Point(), aFraction, aFraction); + break; + } + } + + aSize = rOutDev.PixelToLogic(rOutDev.LogicToPixel(aSize, OutputMapMode), + MapMode(MapUnit::Map100thMM)); + + Point aPos (aOutRect.Left() + (aOutRect.GetWidth() - aSize.Width()) / 2, + aOutRect.Top() + (aOutRect.GetHeight() - aSize.Height()) / 2); + + aPos = rOutDev.PixelToLogic(rOutDev.LogicToPixel(aPos, MapMode(MapUnit::Map100thMM)), + OutputMapMode); + aOutRect = rOutDev.PixelToLogic(rOutDev.LogicToPixel(aOutRect, MapMode(MapUnit::Map100thMM)), + OutputMapMode); + + rOutDev.SetMapMode(OutputMapMode); + rOutDev.SetClipRegion(vcl::Region(aOutRect)); + GetDoc()->DrawFormula(rOutDev, aPos); + rOutDev.SetClipRegion(); + + rOutDev.Pop(); +} + +SfxPrinter* SmViewShell::GetPrinter(bool bCreate) +{ + SmDocShell* pDoc = GetDoc(); + if (pDoc->HasPrinter() || bCreate) + return pDoc->GetPrinter(); + return nullptr; +} + +sal_uInt16 SmViewShell::SetPrinter(SfxPrinter *pNewPrinter, SfxPrinterChangeFlags nDiffFlags ) +{ + SfxPrinter *pOld = GetDoc()->GetPrinter(); + if ( pOld && pOld->IsPrinting() ) + return SFX_PRINTERROR_BUSY; + + if ((nDiffFlags & SfxPrinterChangeFlags::PRINTER) == SfxPrinterChangeFlags::PRINTER) + GetDoc()->SetPrinter( pNewPrinter ); + + if ((nDiffFlags & SfxPrinterChangeFlags::OPTIONS) == SfxPrinterChangeFlags::OPTIONS) + { + SmModule *pp = SM_MOD(); + pp->GetConfig()->ItemSetToConfig(pNewPrinter->GetOptions()); + } + return 0; +} + +bool SmViewShell::HasPrintOptionsPage() const +{ + return true; +} + +std::unique_ptr<SfxTabPage> SmViewShell::CreatePrintOptionsPage(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet &rOptions) +{ + return SmPrintOptionsTabPage::Create(pPage, pController, rOptions); +} + +SmEditWindow *SmViewShell::GetEditWindow() +{ + SmCmdBoxWrapper* pWrapper = static_cast<SmCmdBoxWrapper*>( + GetViewFrame()->GetChildWindow(SmCmdBoxWrapper::GetChildWindowId())); + + if (pWrapper != nullptr) + { + SmEditWindow& rEditWin = pWrapper->GetEditWindow(); + return &rEditWin; + } + + return nullptr; +} + +void SmViewShell::SetStatusText(const OUString& rText) +{ + maStatusText = rText; + GetViewFrame()->GetBindings().Invalidate(SID_TEXTSTATUS); +} + +void SmViewShell::ShowError(const SmErrorDesc* pErrorDesc) +{ + assert(GetDoc()); + if (pErrorDesc || nullptr != (pErrorDesc = GetDoc()->GetParser().GetError()) ) + { + SetStatusText( pErrorDesc->m_aText ); + GetEditWindow()->MarkError( Point( pErrorDesc->m_pNode->GetColumn(), + pErrorDesc->m_pNode->GetRow())); + } +} + +void SmViewShell::NextError() +{ + assert(GetDoc()); + const SmErrorDesc *pErrorDesc = GetDoc()->GetParser().NextError(); + + if (pErrorDesc) + ShowError( pErrorDesc ); +} + +void SmViewShell::PrevError() +{ + assert(GetDoc()); + const SmErrorDesc *pErrorDesc = GetDoc()->GetParser().PrevError(); + + if (pErrorDesc) + ShowError( pErrorDesc ); +} + +void SmViewShell::Insert( SfxMedium& rMedium ) +{ + SmDocShell *pDoc = GetDoc(); + bool bRet = false; + + uno::Reference <embed::XStorage> xStorage = rMedium.GetStorage(); + if (xStorage.is() && xStorage->getElementNames().hasElements()) + { + if (xStorage->hasByName("content.xml")) + { + // is this a fabulous math package ? + Reference<css::frame::XModel> xModel(pDoc->GetModel()); + SmXMLImportWrapper aEquation(xModel); //!! modifies the result of pDoc->GetText() !! + bRet = ERRCODE_NONE == aEquation.Import(rMedium); + } + } + + if (!bRet) + return; + + OUString aText = pDoc->GetText(); + SmEditWindow *pEditWin = GetEditWindow(); + if (pEditWin) + pEditWin->InsertText( aText ); + else + { + SAL_WARN( "starmath", "EditWindow missing" ); + } + + pDoc->Parse(); + pDoc->SetModified(); + + SfxBindings &rBnd = GetViewFrame()->GetBindings(); + rBnd.Invalidate(SID_GAPHIC_SM); + rBnd.Invalidate(SID_TEXT); +} + +void SmViewShell::InsertFrom(SfxMedium &rMedium) +{ + bool bSuccess = false; + SmDocShell* pDoc = GetDoc(); + SvStream* pStream = rMedium.GetInStream(); + + if (pStream) + { + const OUString& rFltName = rMedium.GetFilter()->GetFilterName(); + if ( rFltName == MATHML_XML ) + { + Reference<css::frame::XModel> xModel(pDoc->GetModel()); + SmXMLImportWrapper aEquation(xModel); //!! modifies the result of pDoc->GetText() !! + bSuccess = ERRCODE_NONE == aEquation.Import(rMedium); + } + } + + if (!bSuccess) + return; + + OUString aText = pDoc->GetText(); + SmEditWindow *pEditWin = GetEditWindow(); + if (pEditWin) + pEditWin->InsertText(aText); + else + SAL_WARN( "starmath", "EditWindow missing" ); + + pDoc->Parse(); + pDoc->SetModified(); + + SfxBindings& rBnd = GetViewFrame()->GetBindings(); + rBnd.Invalidate(SID_GAPHIC_SM); + rBnd.Invalidate(SID_TEXT); +} + +void SmViewShell::Execute(SfxRequest& rReq) +{ + SmEditWindow *pWin = GetEditWindow(); + + switch (rReq.GetSlot()) + { + case SID_FORMULACURSOR: + { + SmModule *pp = SM_MOD(); + + const SfxItemSet *pArgs = rReq.GetArgs(); + const SfxPoolItem *pItem; + + bool bVal; + if ( pArgs && + SfxItemState::SET == pArgs->GetItemState( SID_FORMULACURSOR, false, &pItem)) + bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + else + bVal = !pp->GetConfig()->IsShowFormulaCursor(); + + pp->GetConfig()->SetShowFormulaCursor(bVal); + if (!IsInlineEditEnabled()) + GetGraphicWindow().ShowCursor(bVal); + break; + } + case SID_DRAW: + if (pWin) + { + GetDoc()->SetText( pWin->GetText() ); + SetStatusText(OUString()); + ShowError( nullptr ); + GetDoc()->Repaint(); + } + break; + + case SID_ZOOM_OPTIMAL: + mpGraphic->ZoomToFitInWindow(); + break; + + case SID_ZOOMIN: + mpGraphic->SetZoom(mpGraphic->GetZoom() + 25); + break; + + case SID_ZOOMOUT: + SAL_WARN_IF( mpGraphic->GetZoom() < 25, "starmath", "incorrect sal_uInt16 argument" ); + mpGraphic->SetZoom(mpGraphic->GetZoom() - 25); + break; + + case SID_COPYOBJECT: + { + //TODO/LATER: does not work because of UNO Tunneling - will be fixed later + Reference< datatransfer::XTransferable > xTrans( GetDoc()->GetModel(), uno::UNO_QUERY ); + if( xTrans.is() ) + { + auto pTrans = comphelper::getUnoTunnelImplementation<TransferableHelper>(xTrans); + if( pTrans ) + pTrans->CopyToClipboard(GetEditWindow()); + } + } + break; + + case SID_PASTEOBJECT: + { + TransferableDataHelper aData( TransferableDataHelper::CreateFromSystemClipboard(GetEditWindow()) ); + uno::Reference < io::XInputStream > xStrm; + SotClipboardFormatId nId; + if( aData.GetTransferable().is() && + ( aData.HasFormat( nId = SotClipboardFormatId::EMBEDDED_OBJ ) || + (aData.HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) && + aData.HasFormat( nId = SotClipboardFormatId::EMBED_SOURCE )))) + xStrm = aData.GetInputStream(nId, OUString()); + + if (xStrm.is()) + { + try + { + uno::Reference < embed::XStorage > xStorage = + ::comphelper::OStorageHelper::GetStorageFromInputStream( xStrm, ::comphelper::getProcessComponentContext() ); + SfxMedium aMedium( xStorage, OUString() ); + Insert( aMedium ); + GetDoc()->UpdateText(); + } + catch (uno::Exception &) + { + SAL_WARN( "starmath", "SmViewShell::Execute (SID_PASTEOBJECT): failed to get storage from input stream" ); + } + } + } + break; + + + case SID_CUT: + if (pWin) + pWin->Cut(); + break; + + case SID_COPY: + if (pWin) + { + if (pWin->IsAllSelected()) + { + GetViewFrame()->GetDispatcher()->ExecuteList( + SID_COPYOBJECT, SfxCallMode::RECORD, + { new SfxVoidItem(SID_COPYOBJECT) }); + } + else + pWin->Copy(); + } + break; + + case SID_PASTE: + { + bool bCallExec = nullptr == pWin; + if( !bCallExec ) + { + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromSystemClipboard( + GetEditWindow()) ); + + if( aDataHelper.GetTransferable().is() && + aDataHelper.HasFormat( SotClipboardFormatId::STRING )) + pWin->Paste(); + else + bCallExec = true; + } + if( bCallExec ) + { + GetViewFrame()->GetDispatcher()->ExecuteList( + SID_PASTEOBJECT, SfxCallMode::RECORD, + { new SfxVoidItem(SID_PASTEOBJECT) }); + } + } + break; + + case SID_DELETE: + if (pWin) + pWin->Delete(); + break; + + case SID_SELECT: + if (pWin) + pWin->SelectAll(); + break; + + case SID_INSERTCOMMANDTEXT: + { + const SfxStringItem& rItem = static_cast<const SfxStringItem&>(rReq.GetArgs()->Get(SID_INSERTCOMMANDTEXT)); + + if (pWin && (mbInsertIntoEditWindow || !IsInlineEditEnabled())) + { + pWin->InsertText(rItem.GetValue()); + } + if (IsInlineEditEnabled() && (GetDoc() && !mbInsertIntoEditWindow)) + { + GetDoc()->GetCursor().InsertCommandText(rItem.GetValue()); + GetGraphicWindow().GrabFocus(); + } + break; + + } + + case SID_INSERTSPECIAL: + { + const SfxStringItem& rItem = + static_cast<const SfxStringItem&>(rReq.GetArgs()->Get(SID_INSERTSPECIAL)); + + if (pWin && (mbInsertIntoEditWindow || !IsInlineEditEnabled())) + pWin->InsertText(rItem.GetValue()); + if (IsInlineEditEnabled() && (GetDoc() && !mbInsertIntoEditWindow)) + GetDoc()->GetCursor().InsertSpecial(rItem.GetValue()); + break; + } + + case SID_IMPORT_FORMULA: + { + mpImpl->pRequest.reset(new SfxRequest( rReq )); + mpImpl->pDocInserter.reset(new ::sfx2::DocumentInserter(pWin ? pWin->GetFrameWeld() : nullptr, + GetDoc()->GetFactory().GetFactoryName())); + mpImpl->pDocInserter->StartExecuteModal( LINK( this, SmViewShell, DialogClosedHdl ) ); + break; + } + + case SID_IMPORT_MATHML_CLIPBOARD: + { + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard(GetEditWindow()) ); + uno::Reference < io::XInputStream > xStrm; + SotClipboardFormatId nId = SOT_FORMAT_SYSTEM_START; //dummy initialize to avoid warning + if ( aDataHelper.GetTransferable().is() ) + { + nId = SotClipboardFormatId::MATHML; + if (aDataHelper.HasFormat(nId)) + { + xStrm = aDataHelper.GetInputStream(nId, ""); + if (xStrm.is()) + { + std::unique_ptr<SfxMedium> pClipboardMedium(new SfxMedium()); + pClipboardMedium->GetItemSet(); //generate initial itemset, not sure if necessary + std::shared_ptr<const SfxFilter> pMathFilter = + SfxFilter::GetFilterByName(MATHML_XML); + pClipboardMedium->SetFilter(pMathFilter); + pClipboardMedium->setStreamToLoadFrom(xStrm, true /*bIsReadOnly*/); + InsertFrom(*pClipboardMedium); + GetDoc()->UpdateText(); + } + } + else + { + nId = SotClipboardFormatId::STRING; + if (aDataHelper.HasFormat(nId)) + { + // In case of FORMAT_STRING no stream exists, need to generate one + OUString aString; + if (aDataHelper.GetString( nId, aString)) + { + // tdf#117091 force xml declaration to exist + if (!aString.startsWith("<?xml")) + aString = "<?xml version=\"1.0\"?>\n" + aString; + + std::unique_ptr<SfxMedium> pClipboardMedium(new SfxMedium()); + pClipboardMedium->GetItemSet(); //generates initial itemset, not sure if necessary + std::shared_ptr<const SfxFilter> pMathFilter = + SfxFilter::GetFilterByName(MATHML_XML); + pClipboardMedium->SetFilter(pMathFilter); + + std::unique_ptr<SvMemoryStream> pStrm; + // The text to be imported might asserts encoding like 'encoding="utf-8"' but FORMAT_STRING is UTF-16. + // Force encoding to UTF-16, if encoding exists. + bool bForceUTF16 = false; + sal_Int32 nPosL = aString.indexOf("encoding=\""); + sal_Int32 nPosU = -1; + if ( nPosL >= 0 && nPosL +10 < aString.getLength() ) + { + nPosL += 10; + nPosU = aString.indexOf( '"',nPosL); + if (nPosU > nPosL) + { + bForceUTF16 = true; + } + } + if ( bForceUTF16 ) + { + OUString aNewString = aString.replaceAt( nPosL,nPosU-nPosL,"UTF-16"); + pStrm.reset(new SvMemoryStream( const_cast<sal_Unicode *>(aNewString.getStr()), aNewString.getLength() * sizeof(sal_Unicode), StreamMode::READ)); + } + else + { + pStrm.reset(new SvMemoryStream( const_cast<sal_Unicode *>(aString.getStr()), aString.getLength() * sizeof(sal_Unicode), StreamMode::READ)); + } + uno::Reference<io::XInputStream> xStrm2( new ::utl::OInputStreamWrapper(*pStrm) ); + pClipboardMedium->setStreamToLoadFrom(xStrm2, true /*bIsReadOnly*/); + InsertFrom(*pClipboardMedium); + GetDoc()->UpdateText(); + } + } + } + } + break; + } + + case SID_NEXTERR: + NextError(); + if (pWin) + pWin->GrabFocus(); + break; + + case SID_PREVERR: + PrevError(); + if (pWin) + pWin->GrabFocus(); + break; + + case SID_NEXTMARK: + if (pWin) + { + pWin->SelNextMark(); + pWin->GrabFocus(); + } + break; + + case SID_PREVMARK: + if (pWin) + { + pWin->SelPrevMark(); + pWin->GrabFocus(); + } + break; + + case SID_TEXTSTATUS: + { + if (rReq.GetArgs() != nullptr) + { + const SfxStringItem& rItem = + static_cast<const SfxStringItem&>(rReq.GetArgs()->Get(SID_TEXTSTATUS)); + + SetStatusText(rItem.GetValue()); + } + + break; + } + + case SID_GETEDITTEXT: + if (pWin && !pWin->GetText().isEmpty()) + GetDoc()->SetText( pWin->GetText() ); + break; + + case SID_ATTR_ZOOM: + { + if ( !GetViewFrame()->GetFrame().IsInPlace() ) + { + const SfxItemSet *pSet = rReq.GetArgs(); + if ( pSet ) + { + ZoomByItemSet(pSet); + } + else + { + SfxItemSet aSet( SmDocShell::GetPool(), svl::Items<SID_ATTR_ZOOM, SID_ATTR_ZOOM>{}); + aSet.Put( SvxZoomItem( SvxZoomType::PERCENT, mpGraphic->GetZoom())); + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractSvxZoomDialog> xDlg(pFact->CreateSvxZoomDialog(GetViewFrame()->GetWindow().GetFrameWeld(), aSet)); + xDlg->SetLimits( MINZOOM, MAXZOOM ); + if (xDlg->Execute() != RET_CANCEL) + ZoomByItemSet(xDlg->GetOutputItemSet()); + } + } + } + break; + + case SID_ATTR_ZOOMSLIDER: + { + const SfxItemSet *pArgs = rReq.GetArgs(); + const SfxPoolItem* pItem; + + if ( pArgs && SfxItemState::SET == pArgs->GetItemState(SID_ATTR_ZOOMSLIDER, true, &pItem ) ) + { + const sal_uInt16 nCurrentZoom = static_cast<const SvxZoomSliderItem *>(pItem)->GetValue(); + mpGraphic->SetZoom( nCurrentZoom ); + } + } + break; + + case SID_ELEMENTSDOCKINGWINDOW: + { + GetViewFrame()->ToggleChildWindow( SmElementsDockingWindowWrapper::GetChildWindowId() ); + GetViewFrame()->GetBindings().Invalidate( SID_ELEMENTSDOCKINGWINDOW ); + + rReq.Ignore (); + } + break; + + case SID_UNICODE_NOTATION_TOGGLE: + { + EditEngine* pEditEngine = nullptr; + if( pWin ) + pEditEngine = pWin->GetEditEngine(); + + EditView* pEditView = nullptr; + if( pEditEngine ) + pEditView = pEditEngine->GetView(); + + if( pEditView ) + { + const OUString sInput = pEditView->GetSurroundingText(); + ESelection aSel( pWin->GetSelection() ); + + if ( aSel.nStartPos > aSel.nEndPos ) + aSel.nEndPos = aSel.nStartPos; + + //calculate a valid end-position by reading logical characters + sal_Int32 nUtf16Pos=0; + while( (nUtf16Pos < sInput.getLength()) && (nUtf16Pos < aSel.nEndPos) ) + { + sInput.iterateCodePoints(&nUtf16Pos); + if( nUtf16Pos > aSel.nEndPos ) + aSel.nEndPos = nUtf16Pos; + } + + ToggleUnicodeCodepoint aToggle; + while( nUtf16Pos && aToggle.AllowMoreInput( sInput[nUtf16Pos-1]) ) + --nUtf16Pos; + const OUString sReplacement = aToggle.ReplacementString(); + if( !sReplacement.isEmpty() ) + { + pEditView->SetSelection( aSel ); + pEditEngine->UndoActionStart(EDITUNDO_REPLACEALL); + aSel.nStartPos = aSel.nEndPos - aToggle.StringToReplace().getLength(); + pWin->SetSelection( aSel ); + pEditView->InsertText( sReplacement, true ); + pEditEngine->UndoActionEnd(); + pWin->Flush(); + } + } + } + break; + + case SID_SYMBOLS_CATALOGUE: + { + + // get device used to retrieve the FontList + SmDocShell *pDoc = GetDoc(); + OutputDevice *pDev = pDoc->GetPrinter(); + if (!pDev || pDev->GetDevFontCount() == 0) + pDev = &SM_MOD()->GetDefaultVirtualDev(); + SAL_WARN_IF( !pDev, "starmath", "device for font list missing" ); + + SmModule *pp = SM_MOD(); + SmSymbolDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, pDev, pp->GetSymbolManager(), *this); + aDialog.run(); + } + break; + } + rReq.Done(); +} + + +void SmViewShell::GetState(SfxItemSet &rSet) +{ + SfxWhichIter aIter(rSet); + + SmEditWindow *pEditWin = GetEditWindow(); + for (sal_uInt16 nWh = aIter.FirstWhich(); nWh != 0; nWh = aIter.NextWhich()) + { + switch (nWh) + { + case SID_CUT: + case SID_COPY: + case SID_DELETE: + if (! pEditWin || ! pEditWin->IsSelected()) + rSet.DisableItem(nWh); + break; + + case SID_PASTE: + if (pEditWin) + { + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromSystemClipboard( + pEditWin) ); + + mbPasteState = aDataHelper.GetTransferable().is() && + ( aDataHelper.HasFormat( SotClipboardFormatId::STRING ) || + aDataHelper.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ ) || + (aDataHelper.HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) + && aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE ))); + } + if( !mbPasteState ) + rSet.DisableItem( nWh ); + break; + + case SID_ATTR_ZOOM: + rSet.Put(SvxZoomItem( SvxZoomType::PERCENT, mpGraphic->GetZoom())); + [[fallthrough]]; + case SID_ZOOMIN: + case SID_ZOOMOUT: + case SID_ZOOM_OPTIMAL: + if ( GetViewFrame()->GetFrame().IsInPlace() ) + rSet.DisableItem( nWh ); + break; + + case SID_ATTR_ZOOMSLIDER : + { + const sal_uInt16 nCurrentZoom = mpGraphic->GetZoom(); + SvxZoomSliderItem aZoomSliderItem( nCurrentZoom, MINZOOM, MAXZOOM ); + aZoomSliderItem.AddSnappingPoint( 100 ); + rSet.Put( aZoomSliderItem ); + } + break; + + case SID_NEXTERR: + case SID_PREVERR: + case SID_NEXTMARK: + case SID_PREVMARK: + case SID_DRAW: + case SID_SELECT: + if (! pEditWin || pEditWin->IsEmpty()) + rSet.DisableItem(nWh); + break; + + case SID_TEXTSTATUS: + { + rSet.Put(SfxStringItem(nWh, maStatusText)); + } + break; + + case SID_FORMULACURSOR: + { + SmModule *pp = SM_MOD(); + rSet.Put(SfxBoolItem(nWh, pp->GetConfig()->IsShowFormulaCursor())); + } + break; + case SID_ELEMENTSDOCKINGWINDOW: + { + bool bState = false; + SfxChildWindow *pChildWnd = GetViewFrame()-> + GetChildWindow( SmElementsDockingWindowWrapper::GetChildWindowId() ); + if (pChildWnd && pChildWnd->GetWindow()->IsVisible()) + bState = true; + rSet.Put(SfxBoolItem(SID_ELEMENTSDOCKINGWINDOW, bState)); + } + break; + } + } +} + +SmViewShell::SmViewShell(SfxViewFrame *pFrame_, SfxViewShell *) + : SfxViewShell(pFrame_, SfxViewShellFlags::HAS_PRINTOPTIONS) + , mpImpl(new SmViewShell_Impl) + , mpGraphic(VclPtr<SmGraphicWindow>::Create(this)) + , maGraphicController(*mpGraphic, SID_GAPHIC_SM, pFrame_->GetBindings()) + , mbPasteState(false) + , mbInsertIntoEditWindow(false) +{ + SetStatusText(OUString()); + SetWindow(mpGraphic.get()); + SfxShell::SetName("SmView"); + SfxShell::SetUndoManager( &GetDoc()->GetEditEngine().GetUndoManager() ); +} + + +SmViewShell::~SmViewShell() +{ + //!! this view shell is not active anymore !! + // Thus 'SmGetActiveView' will give a 0 pointer. + // Thus we need to supply this view as argument + SmEditWindow *pEditWin = GetEditWindow(); + if (pEditWin) + pEditWin->DeleteEditView(); + mpGraphic.disposeAndClear(); +} + +void SmViewShell::Deactivate( bool bIsMDIActivate ) +{ + SmEditWindow *pEdit = GetEditWindow(); + if ( pEdit ) + pEdit->Flush(); + + SfxViewShell::Deactivate( bIsMDIActivate ); +} + +void SmViewShell::Activate( bool bIsMDIActivate ) +{ + SfxViewShell::Activate( bIsMDIActivate ); + + SmEditWindow *pEdit = GetEditWindow(); + if ( pEdit ) + { + //! Since there is no way to be informed if a "drag and drop" + //! event has taken place, we call SetText here in order to + //! synchronize the GraphicWindow display with the text in the + //! EditEngine. + SmDocShell *pDoc = GetDoc(); + pDoc->SetText( pDoc->GetEditEngine().GetText() ); + + if ( bIsMDIActivate ) + pEdit->GrabFocus(); + } +} + +IMPL_LINK( SmViewShell, DialogClosedHdl, sfx2::FileDialogHelper*, _pFileDlg, void ) +{ + assert(_pFileDlg && "SmViewShell::DialogClosedHdl(): no file dialog"); + assert(mpImpl->pDocInserter && "ScDocShell::DialogClosedHdl(): no document inserter"); + + if ( ERRCODE_NONE == _pFileDlg->GetError() ) + { + std::unique_ptr<SfxMedium> pMedium = mpImpl->pDocInserter->CreateMedium(); + + if ( pMedium ) + { + if ( pMedium->IsStorage() ) + Insert( *pMedium ); + else + InsertFrom( *pMedium ); + pMedium.reset(); + + SmDocShell* pDoc = GetDoc(); + pDoc->UpdateText(); + pDoc->ArrangeFormula(); + pDoc->Repaint(); + // adjust window, repaint, increment ModifyCount,... + GetViewFrame()->GetBindings().Invalidate(SID_GAPHIC_SM); + } + } + + mpImpl->pRequest->SetReturnValue( SfxBoolItem( mpImpl->pRequest->GetSlot(), true ) ); + mpImpl->pRequest->Done(); +} + +void SmViewShell::Notify( SfxBroadcaster& , const SfxHint& rHint ) +{ + switch( rHint.GetId() ) + { + case SfxHintId::ModeChanged: + case SfxHintId::DocChanged: + GetViewFrame()->GetBindings().InvalidateAll(false); + break; + default: + break; + } +} + +bool SmViewShell::IsInlineEditEnabled() const +{ + return mpImpl->aOpts.IsExperimentalMode(); +} + +void SmViewShell::ZoomByItemSet(const SfxItemSet *pSet) +{ + assert(pSet); + const SvxZoomItem &rZoom = pSet->Get(SID_ATTR_ZOOM); + switch( rZoom.GetType() ) + { + case SvxZoomType::PERCENT: + mpGraphic->SetZoom(sal::static_int_cast<sal_uInt16>(rZoom.GetValue ())); + break; + + case SvxZoomType::OPTIMAL: + mpGraphic->ZoomToFitInWindow(); + break; + + case SvxZoomType::PAGEWIDTH: + case SvxZoomType::WHOLEPAGE: + { + const MapMode aMap( MapUnit::Map100thMM ); + SfxPrinter *pPrinter = GetPrinter( true ); + tools::Rectangle OutputRect(Point(), pPrinter->GetOutputSize()); + Size OutputSize(pPrinter->LogicToPixel(Size(OutputRect.GetWidth(), + OutputRect.GetHeight()), aMap)); + Size GraphicSize(pPrinter->LogicToPixel(GetDoc()->GetSize(), aMap)); + sal_uInt16 nZ = sal::static_int_cast<sal_uInt16>(std::min(long(Fraction(OutputSize.Width() * 100, GraphicSize.Width())), + long(Fraction(OutputSize.Height() * 100, GraphicSize.Height())))); + mpGraphic->SetZoom (nZ); + break; + } + default: + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/visitors.cxx b/starmath/source/visitors.cxx new file mode 100644 index 000000000..5362551fe --- /dev/null +++ b/starmath/source/visitors.cxx @@ -0,0 +1,2425 @@ +/* -*- 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 <rtl/math.hxx> +#include <sal/log.hxx> +#include <tools/gen.hxx> +#include <vcl/lineinfo.hxx> +#include <visitors.hxx> +#include "tmpdevice.hxx" +#include <cursor.hxx> +#include <cassert> + +// SmDefaultingVisitor + +void SmDefaultingVisitor::Visit( SmTableNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBraceNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBracebodyNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmOperNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmAlignNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmAttributNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmFontNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmUnHorNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBinHorNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBinVerNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmSubSupNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmMatrixNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmPlaceNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmTextNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmSpecialNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmMathSymbolNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBlankNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmErrorNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmLineNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmExpressionNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmPolyLineNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmRootNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmRootSymbolNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmRectangleNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + DefaultVisit( pNode ); +} + +// SmCaretDrawingVisitor + +SmCaretDrawingVisitor::SmCaretDrawingVisitor( OutputDevice& rDevice, + SmCaretPos position, + Point offset, + bool caretVisible ) + : mrDev( rDevice ) + , maPos( position ) + , maOffset( offset ) + , mbCaretVisible( caretVisible ) +{ + SAL_WARN_IF( !position.IsValid(), "starmath", "Cannot draw invalid position!" ); + if( !position.IsValid( ) ) + return; + + //Save device state + mrDev.Push( PushFlags::FONT | PushFlags::MAPMODE | PushFlags::LINECOLOR | PushFlags::FILLCOLOR | PushFlags::TEXTCOLOR ); + + maPos.pSelectedNode->Accept( this ); + //Restore device state + mrDev.Pop( ); +} + +void SmCaretDrawingVisitor::Visit( SmTextNode* pNode ) +{ + long i = maPos.nIndex; + + mrDev.SetFont( pNode->GetFont( ) ); + + //Find the line + SmNode* pLine = SmCursor::FindTopMostNodeInLine( pNode ); + + //Find coordinates + long left = pNode->GetLeft( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, i ) + maOffset.X( ); + long top = pLine->GetTop( ) + maOffset.Y( ); + long height = pLine->GetHeight( ); + long left_line = pLine->GetLeft( ) + maOffset.X( ); + long right_line = pLine->GetRight( ) + maOffset.X( ); + + //Set color + mrDev.SetLineColor( COL_BLACK ); + + if ( mbCaretVisible ) { + //Draw vertical line + Point p1( left, top ); + Point p2( left, top + height ); + mrDev.DrawLine( p1, p2 ); + } + + //Underline the line + Point aLeft( left_line, top + height ); + Point aRight( right_line, top + height ); + mrDev.DrawLine( aLeft, aRight ); +} + +void SmCaretDrawingVisitor::DefaultVisit( SmNode* pNode ) +{ + //Find the line + SmNode* pLine = SmCursor::FindTopMostNodeInLine( pNode ); + + //Find coordinates + long left = pNode->GetLeft( ) + maOffset.X( ) + ( maPos.nIndex == 1 ? pNode->GetWidth( ) : 0 ); + long top = pLine->GetTop( ) + maOffset.Y( ); + long height = pLine->GetHeight( ); + long left_line = pLine->GetLeft( ) + maOffset.X( ); + long right_line = pLine->GetRight( ) + maOffset.X( ); + + //Set color + mrDev.SetLineColor( COL_BLACK ); + + if ( mbCaretVisible ) { + //Draw vertical line + Point p1( left, top ); + Point p2( left, top + height ); + mrDev.DrawLine( p1, p2 ); + } + + //Underline the line + Point aLeft( left_line, top + height ); + Point aRight( right_line, top + height ); + mrDev.DrawLine( aLeft, aRight ); +} + +// SmCaretPos2LineVisitor + +void SmCaretPos2LineVisitor::Visit( SmTextNode* pNode ) +{ + //Save device state + mpDev->Push( PushFlags::FONT | PushFlags::TEXTCOLOR ); + + long i = maPos.nIndex; + + mpDev->SetFont( pNode->GetFont( ) ); + + //Find coordinates + long left = pNode->GetLeft( ) + mpDev->GetTextWidth( pNode->GetText( ), 0, i ); + long top = pNode->GetTop( ); + long height = pNode->GetHeight( ); + + maLine = SmCaretLine( left, top, height ); + + //Restore device state + mpDev->Pop( ); +} + +void SmCaretPos2LineVisitor::DefaultVisit( SmNode* pNode ) +{ + //Vertical line ( code from SmCaretDrawingVisitor ) + Point p1 = pNode->GetTopLeft( ); + if( maPos.nIndex == 1 ) + p1.Move( pNode->GetWidth( ), 0 ); + + maLine = SmCaretLine( p1.X( ), p1.Y( ), pNode->GetHeight( ) ); +} + + +// SmDrawingVisitor + +void SmDrawingVisitor::Visit( SmTableNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBraceNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBracebodyNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmOperNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmAlignNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmAttributNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmFontNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmUnHorNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBinHorNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBinVerNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmSubSupNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmMatrixNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmPlaceNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmTextNode* pNode ) +{ + DrawTextNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmSpecialNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmMathSymbolNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmBlankNode* ) +{ +} + +void SmDrawingVisitor::Visit( SmErrorNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmLineNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmExpressionNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmRootNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmRootSymbolNode* pNode ) +{ + if ( pNode->IsPhantom( ) ) + return; + + // draw root-sign itself + DrawSpecialNode( pNode ); + + SmTmpDevice aTmpDev( mrDev, true ); + aTmpDev.SetFillColor( pNode->GetFont( ).GetColor( ) ); + mrDev.SetLineColor( ); + aTmpDev.SetFont( pNode->GetFont( ) ); + + // since the width is always unscaled it corresponds to the _original_ + // _unscaled_ font height to be used, we use that to calculate the + // bar height. Thus it is independent of the arguments height. + // ( see display of sqrt QQQ versus sqrt stack{Q#Q#Q#Q} ) + long nBarHeight = pNode->GetWidth( ) * 7 / 100; + long nBarWidth = pNode->GetBodyWidth( ) + pNode->GetBorderWidth( ); + Point aBarOffset( pNode->GetWidth( ), +pNode->GetBorderWidth( ) ); + Point aBarPos( maPosition + aBarOffset ); + + tools::Rectangle aBar( aBarPos, Size( nBarWidth, nBarHeight ) ); + //! avoid GROWING AND SHRINKING of drawn rectangle when constantly + //! increasing zoomfactor. + // This is done by shifting its output-position to a point that + // corresponds exactly to a pixel on the output device. + Point aDrawPos( mrDev.PixelToLogic( mrDev.LogicToPixel( aBar.TopLeft( ) ) ) ); + aBar.SetPos( aDrawPos ); + + mrDev.DrawRect( aBar ); +} + +void SmDrawingVisitor::Visit( SmPolyLineNode* pNode ) +{ + if ( pNode->IsPhantom( ) ) + return; + + long nBorderwidth = pNode->GetFont( ).GetBorderWidth( ); + + LineInfo aInfo; + aInfo.SetWidth( pNode->GetWidth( ) - 2 * nBorderwidth ); + + Point aOffset ( Point( ) - pNode->GetPolygon( ).GetBoundRect( ).TopLeft( ) + + Point( nBorderwidth, nBorderwidth ) ), + aPos ( maPosition + aOffset ); + pNode->GetPolygon( ).Move( aPos.X( ), aPos.Y( ) ); //Works because Polygon wraps a pointer + + SmTmpDevice aTmpDev ( mrDev, false ); + aTmpDev.SetLineColor( pNode->GetFont( ).GetColor( ) ); + + mrDev.DrawPolyLine( pNode->GetPolygon( ), aInfo ); +} + +void SmDrawingVisitor::Visit( SmRectangleNode* pNode ) +{ + if ( pNode->IsPhantom( ) ) + return; + + SmTmpDevice aTmpDev ( mrDev, false ); + aTmpDev.SetFillColor( pNode->GetFont( ).GetColor( ) ); + mrDev.SetLineColor( ); + aTmpDev.SetFont( pNode->GetFont( ) ); + + sal_uLong nTmpBorderWidth = pNode->GetFont( ).GetBorderWidth( ); + + // get rectangle and remove borderspace + tools::Rectangle aTmp ( pNode->AsRectangle( ) + maPosition - pNode->GetTopLeft( ) ); + aTmp.AdjustLeft(nTmpBorderWidth ); + aTmp.AdjustRight( -sal_Int32(nTmpBorderWidth) ); + aTmp.AdjustTop(nTmpBorderWidth ); + aTmp.AdjustBottom( -sal_Int32(nTmpBorderWidth) ); + + SAL_WARN_IF( aTmp.IsEmpty(), "starmath", "Empty rectangle" ); + + //! avoid GROWING AND SHRINKING of drawn rectangle when constantly + //! increasing zoomfactor. + // This is done by shifting its output-position to a point that + // corresponds exactly to a pixel on the output device. + Point aPos ( mrDev.PixelToLogic( mrDev.LogicToPixel( aTmp.TopLeft( ) ) ) ); + aTmp.SetPos( aPos ); + + mrDev.DrawRect( aTmp ); +} + +void SmDrawingVisitor::DrawTextNode( SmTextNode* pNode ) +{ + if ( pNode->IsPhantom() || pNode->GetText().isEmpty() || pNode->GetText()[0] == '\0' ) + return; + + SmTmpDevice aTmpDev ( mrDev, false ); + aTmpDev.SetFont( pNode->GetFont( ) ); + + Point aPos ( maPosition ); + aPos.AdjustY(pNode->GetBaselineOffset( ) ); + // round to pixel coordinate + aPos = mrDev.PixelToLogic( mrDev.LogicToPixel( aPos ) ); + + mrDev.DrawStretchText( aPos, pNode->GetWidth( ), pNode->GetText( ) ); +} + +void SmDrawingVisitor::DrawSpecialNode( SmSpecialNode* pNode ) +{ + //! since this chars might come from any font, that we may not have + //! set to ALIGN_BASELINE yet, we do it now. + pNode->GetFont( ).SetAlignment( ALIGN_BASELINE ); + + DrawTextNode( pNode ); +} + +void SmDrawingVisitor::DrawChildren( SmStructureNode* pNode ) +{ + if ( pNode->IsPhantom( ) ) + return; + + Point rPosition = maPosition; + + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + Point aOffset ( pChild->GetTopLeft( ) - pNode->GetTopLeft( ) ); + maPosition = rPosition + aOffset; + pChild->Accept( this ); + } +} + +// SmSetSelectionVisitor + +SmSetSelectionVisitor::SmSetSelectionVisitor( SmCaretPos startPos, SmCaretPos endPos, SmNode* pTree) + : maStartPos(startPos) + , maEndPos(endPos) + , mbSelecting(false) +{ + //Assume that pTree is a SmTableNode + SAL_WARN_IF(pTree->GetType() != SmNodeType::Table, "starmath", "pTree should be a SmTableNode!"); + //Visit root node, this is special as this node cannot be selected, but its children can! + if(pTree->GetType() == SmNodeType::Table){ + //Change state if maStartPos is in front of this node + if( maStartPos.pSelectedNode == pTree && maStartPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + //Change state if maEndPos is in front of this node + if( maEndPos.pSelectedNode == pTree && maEndPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + SAL_WARN_IF(mbSelecting, "starmath", "Caret positions needed to set mbSelecting about, shouldn't be possible!"); + + //Visit lines + for( auto pChild : *static_cast<SmStructureNode*>(pTree) ) + { + if(!pChild) + continue; + pChild->Accept( this ); + //If we started a selection in this line and it haven't ended, we do that now! + if(mbSelecting) { + mbSelecting = false; + SetSelectedOnAll(pChild); + //Set maStartPos and maEndPos to invalid positions, this ensures that an unused + //start or end (because we forced end above), doesn't start a new selection. + maStartPos = maEndPos = SmCaretPos(); + } + } + //Check if pTree isn't selected + SAL_WARN_IF(pTree->IsSelected(), "starmath", "pTree should never be selected!"); + //Discard the selection if there's a bug (it's better than crashing) + if(pTree->IsSelected()) + SetSelectedOnAll(pTree, false); + }else //This shouldn't happen, but I don't see any reason to die if it does + pTree->Accept(this); +} + +void SmSetSelectionVisitor::SetSelectedOnAll( SmNode* pSubTree, bool IsSelected ) { + pSubTree->SetSelected( IsSelected ); + + if(pSubTree->GetNumSubNodes() == 0) + return; + //Quick BFS to set all selections + for( auto pChild : *static_cast<SmStructureNode*>(pSubTree) ) + { + if(!pChild) + continue; + SetSelectedOnAll( pChild, IsSelected ); + } +} + +void SmSetSelectionVisitor::DefaultVisit( SmNode* pNode ) { + //Change state if maStartPos is in front of this node + if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + //Change state if maEndPos is in front of this node + if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + + //Cache current state + bool WasSelecting = mbSelecting; + bool ChangedState = false; + + //Set selected + pNode->SetSelected( mbSelecting ); + + //Visit children + if(pNode->GetNumSubNodes() > 0) + { + for( auto pChild : *static_cast<SmStructureNode*>(pNode) ) + { + if(!pChild) + continue; + pChild->Accept( this ); + ChangedState = ( WasSelecting != mbSelecting ) || ChangedState; + } + } + + //If state changed + if( ChangedState ) + { + //Select this node and all of its children + //(Make exception for SmBracebodyNode) + if( pNode->GetType() != SmNodeType::Bracebody || + !pNode->GetParent() || + pNode->GetParent()->GetType() != SmNodeType::Brace ) + SetSelectedOnAll( pNode ); + else + SetSelectedOnAll( pNode->GetParent() ); + /* If the equation is: sqrt{2 + 4} + 5 + * And the selection is: sqrt{2 + [4} +] 5 + * Where [ denotes maStartPos and ] denotes maEndPos + * Then the sqrt node should be selected, so that the + * effective selection is: [sqrt{2 + 4} +] 5 + * The same is the case if we swap maStartPos and maEndPos. + */ + } + + //Change state if maStartPos is after this node + if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 1 ) + { + mbSelecting = !mbSelecting; + } + //Change state if maEndPos is after of this node + if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 1 ) + { + mbSelecting = !mbSelecting; + } +} + +void SmSetSelectionVisitor::VisitCompositionNode( SmStructureNode* pNode ) +{ + //Change state if maStartPos is in front of this node + if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + //Change state if maEndPos is in front of this node + if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + + //Cache current state + bool WasSelecting = mbSelecting; + + //Visit children + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } + + //Set selected, if everything was selected + pNode->SetSelected( WasSelecting && mbSelecting ); + + //Change state if maStartPos is after this node + if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 1 ) + mbSelecting = !mbSelecting; + //Change state if maEndPos is after of this node + if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 1 ) + mbSelecting = !mbSelecting; +} + +void SmSetSelectionVisitor::Visit( SmTextNode* pNode ) { + long i1 = -1, + i2 = -1; + if( maStartPos.pSelectedNode == pNode ) + i1 = maStartPos.nIndex; + if( maEndPos.pSelectedNode == pNode ) + i2 = maEndPos.nIndex; + + long start, end; + pNode->SetSelected(true); + if( i1 != -1 && i2 != -1 ) { + start = std::min(i1, i2); + end = std::max(i1, i2); + } else if( mbSelecting && i1 != -1 ) { + start = 0; + end = i1; + mbSelecting = false; + } else if( mbSelecting && i2 != -1 ) { + start = 0; + end = i2; + mbSelecting = false; + } else if( !mbSelecting && i1 != -1 ) { + start = i1; + end = pNode->GetText().getLength(); + mbSelecting = true; + } else if( !mbSelecting && i2 != -1 ) { + start = i2; + end = pNode->GetText().getLength(); + mbSelecting = true; + } else if( mbSelecting ) { + start = 0; + end = pNode->GetText().getLength(); + } else { + pNode->SetSelected( false ); + start = 0; + end = 0; + } + pNode->SetSelected( start != end ); + pNode->SetSelectionStart( start ); + pNode->SetSelectionEnd( end ); +} + +void SmSetSelectionVisitor::Visit( SmExpressionNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmLineNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmAlignNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmBinHorNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmUnHorNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmFontNode* pNode ) { + VisitCompositionNode( pNode ); +} + +// SmCaretPosGraphBuildingVisitor + +SmCaretPosGraphBuildingVisitor::SmCaretPosGraphBuildingVisitor( SmNode* pRootNode ) + : mpRightMost(nullptr) + , mpGraph(new SmCaretPosGraph) +{ + //pRootNode should always be a table + SAL_WARN_IF( pRootNode->GetType( ) != SmNodeType::Table, "starmath", "pRootNode must be a table node"); + //Handle the special case where SmNodeType::Table is used a rootnode + if( pRootNode->GetType( ) == SmNodeType::Table ){ + //Children are SmLineNodes + //Or so I thought... Apparently, the children can be instances of SmExpression + //especially if there's an error in the formula... So here we go, a simple work around. + for( auto pChild : *static_cast<SmStructureNode*>(pRootNode) ) + { + if(!pChild) + continue; + mpRightMost = mpGraph->Add( SmCaretPos( pChild, 0 ) ); + pChild->Accept( this ); + } + }else + pRootNode->Accept(this); +} + +SmCaretPosGraphBuildingVisitor::~SmCaretPosGraphBuildingVisitor() +{ +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmLineNode* pNode ){ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +/** Build SmCaretPosGraph for SmTableNode + * This method covers cases where SmTableNode is used in a binom or stack, + * the special case where it is used as root node for the entire formula is + * handled in the constructor. + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmTableNode* pNode ){ + SmCaretPosGraphEntry *left = mpRightMost, + *right = mpGraph->Add( SmCaretPos( pNode, 1) ); + bool bIsFirst = true; + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + mpRightMost = mpGraph->Add( SmCaretPos( pChild, 0 ), left); + if(bIsFirst) + left->SetRight(mpRightMost); + pChild->Accept( this ); + mpRightMost->SetRight(right); + if(bIsFirst) + right->SetLeft(mpRightMost); + bIsFirst = false; + } + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmSubSupNode + * + * The child positions in a SubSupNode, where H is the body: + * \code + * CSUP + * + * LSUP H H RSUP + * H H + * HHHH + * H H + * LSUB H H RSUB + * + * CSUB + * \endcode + * + * Graph over these, where "left" is before the SmSubSupNode and "right" is after: + * \dot + * digraph Graph{ + * left -> H; + * H -> right; + * LSUP -> H; + * LSUB -> H; + * CSUP -> right; + * CSUB -> right; + * RSUP -> right; + * RSUB -> right; + * }; + * \enddot + * + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmSubSupNode* pNode ) +{ + SmCaretPosGraphEntry *left, + *right, + *bodyLeft, + *bodyRight; + + assert(mpRightMost); + left = mpRightMost; + + //Create bodyLeft + SAL_WARN_IF( !pNode->GetBody(), "starmath", "SmSubSupNode Doesn't have a body!" ); + bodyLeft = mpGraph->Add( SmCaretPos( pNode->GetBody( ), 0 ), left ); + left->SetRight( bodyLeft ); //TODO: Don't make this if LSUP or LSUB are NULL ( not sure??? ) + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Visit the body, to get bodyRight + mpRightMost = bodyLeft; + pNode->GetBody( )->Accept( this ); + bodyRight = mpRightMost; + bodyRight->SetRight( right ); + right->SetLeft( bodyRight ); + + //If there's an LSUP + SmNode* pChild = pNode->GetSubSup( LSUP ); + if( pChild ){ + SmCaretPosGraphEntry *cLeft; //Child left + cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + + mpRightMost = cLeft; + pChild->Accept( this ); + + mpRightMost->SetRight( bodyLeft ); + } + //If there's an LSUB + pChild = pNode->GetSubSup( LSUB ); + if( pChild ){ + SmCaretPosGraphEntry *cLeft; //Child left + cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + + mpRightMost = cLeft; + pChild->Accept( this ); + + mpRightMost->SetRight( bodyLeft ); + } + //If there's a CSUP + pChild = pNode->GetSubSup( CSUP ); + if( pChild ){ + SmCaretPosGraphEntry *cLeft; //Child left + cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + + mpRightMost = cLeft; + pChild->Accept( this ); + + mpRightMost->SetRight( right ); + } + //If there's a CSUB + pChild = pNode->GetSubSup( CSUB ); + if( pChild ){ + SmCaretPosGraphEntry *cLeft; //Child left + cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + + mpRightMost = cLeft; + pChild->Accept( this ); + + mpRightMost->SetRight( right ); + } + //If there's an RSUP + pChild = pNode->GetSubSup( RSUP ); + if( pChild ){ + SmCaretPosGraphEntry *cLeft; //Child left + cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), bodyRight ); + + mpRightMost = cLeft; + pChild->Accept( this ); + + mpRightMost->SetRight( right ); + } + //If there's an RSUB + pChild = pNode->GetSubSup( RSUB ); + if( pChild ){ + SmCaretPosGraphEntry *cLeft; //Child left + cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), bodyRight ); + + mpRightMost = cLeft; + pChild->Accept( this ); + + mpRightMost->SetRight( right ); + } + + //Set return parameters + mpRightMost = right; +} + +/** Build caret position for SmOperNode + * + * If first child is an SmSubSupNode we will ignore its + * body, as this body is a SmMathSymbol, for SUM, INT or similar + * that shouldn't be subject to modification. + * If first child is not a SmSubSupNode, ignore it completely + * as it is a SmMathSymbol. + * + * The child positions in a SmOperNode, where H is symbol, e.g. int, sum or similar: + * \code + * TO + * + * LSUP H H RSUP BBB BB BBB B B + * H H B B B B B B B B + * HHHH BBB B B B B B + * H H B B B B B B B + * LSUB H H RSUB BBB BB BBB B + * + * FROM + * \endcode + * Notice, CSUP, etc. are actually grandchildren, but inorder to ignore H, these are visited + * from here. If they are present, that is if pOper is an instance of SmSubSupNode. + * + * Graph over these, where "left" is before the SmOperNode and "right" is after: + * \dot + * digraph Graph{ + * left -> BODY; + * BODY -> right; + * LSUP -> BODY; + * LSUB -> BODY; + * TO -> BODY; + * FROM -> BODY; + * RSUP -> BODY; + * RSUB -> BODY; + * }; + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmOperNode* pNode ) +{ + SmNode *pOper = pNode->GetSubNode( 0 ), + *pBody = pNode->GetSubNode( 1 ); + + SmCaretPosGraphEntry *left = mpRightMost, + *bodyLeft, + *bodyRight, + *right; + //Create body left + bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( bodyLeft ); + + //Visit body, get bodyRight + mpRightMost = bodyLeft; + pBody->Accept( this ); + bodyRight = mpRightMost; + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ), bodyRight ); + bodyRight->SetRight( right ); + + //Get subsup pNode if any + SmSubSupNode* pSubSup = pOper->GetType( ) == SmNodeType::SubSup ? static_cast<SmSubSupNode*>(pOper) : nullptr; + + SmNode* pChild; + SmCaretPosGraphEntry *childLeft; + if( pSubSup ) { + pChild = pSubSup->GetSubSup( LSUP ); + if( pChild ) { + //Create position in front of pChild + childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + //Visit pChild + mpRightMost = childLeft; + pChild->Accept( this ); + //Set right on mpRightMost from pChild + mpRightMost->SetRight( bodyLeft ); + } + + pChild = pSubSup->GetSubSup( LSUB ); + if( pChild ) { + //Create position in front of pChild + childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + //Visit pChild + mpRightMost = childLeft; + pChild->Accept( this ); + //Set right on mpRightMost from pChild + mpRightMost->SetRight( bodyLeft ); + } + + pChild = pSubSup->GetSubSup( CSUP ); + if ( pChild ) {//TO + //Create position in front of pChild + childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + //Visit pChild + mpRightMost = childLeft; + pChild->Accept( this ); + //Set right on mpRightMost from pChild + mpRightMost->SetRight( bodyLeft ); + } + + pChild = pSubSup->GetSubSup( CSUB ); + if( pChild ) { //FROM + //Create position in front of pChild + childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + //Visit pChild + mpRightMost = childLeft; + pChild->Accept( this ); + //Set right on mpRightMost from pChild + mpRightMost->SetRight( bodyLeft ); + } + + pChild = pSubSup->GetSubSup( RSUP ); + if ( pChild ) { + //Create position in front of pChild + childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + //Visit pChild + mpRightMost = childLeft; + pChild->Accept( this ); + //Set right on mpRightMost from pChild + mpRightMost->SetRight( bodyLeft ); + } + + pChild = pSubSup->GetSubSup( RSUB ); + if ( pChild ) { + //Create position in front of pChild + childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + //Visit pChild + mpRightMost = childLeft; + pChild->Accept( this ); + //Set right on mpRightMost from pChild + mpRightMost->SetRight( bodyLeft ); + } + } + + //Return right + mpRightMost = right; +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmMatrixNode* pNode ) +{ + SmCaretPosGraphEntry *left = mpRightMost, + *right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + for (size_t i = 0; i < pNode->GetNumRows(); ++i) + { + SmCaretPosGraphEntry* r = left; + for (size_t j = 0; j < pNode->GetNumCols(); ++j) + { + SmNode* pSubNode = pNode->GetSubNode( i * pNode->GetNumCols( ) + j ); + + mpRightMost = mpGraph->Add( SmCaretPos( pSubNode, 0 ), r ); + if( j != 0 || ( pNode->GetNumRows() - 1U ) / 2 == i ) + r->SetRight( mpRightMost ); + + pSubNode->Accept( this ); + + r = mpRightMost; + } + mpRightMost->SetRight( right ); + if( ( pNode->GetNumRows() - 1U ) / 2 == i ) + right->SetLeft( mpRightMost ); + } + + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmTextNode + * + * Lines in an SmTextNode: + * \code + * A B C + * \endcode + * Where A B and C are characters in the text. + * + * Graph over these, where "left" is before the SmTextNode and "right" is after: + * \dot + * digraph Graph{ + * left -> A; + * A -> B + * B -> right; + * }; + * \enddot + * Notice that C and right is the same position here. + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmTextNode* pNode ) +{ + SAL_WARN_IF( pNode->GetText().isEmpty(), "starmath", "Empty SmTextNode is bad" ); + + int size = pNode->GetText().getLength(); + for( int i = 1; i <= size; i++ ){ + SmCaretPosGraphEntry* pRight = mpRightMost; + mpRightMost = mpGraph->Add( SmCaretPos( pNode, i ), pRight ); + pRight->SetRight( mpRightMost ); + } +} + +/** Build SmCaretPosGraph for SmBinVerNode + * + * Lines in an SmBinVerNode: + * \code + * A + * ----- + * B + * \endcode + * + * Graph over these, where "left" is before the SmBinVerNode and "right" is after: + * \dot + * digraph Graph{ + * left -> A; + * A -> right; + * B -> right; + * }; + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBinVerNode* pNode ) +{ + //None if these children can be NULL, see SmBinVerNode::Arrange + SmNode *pNum = pNode->GetSubNode( 0 ), + *pDenom = pNode->GetSubNode( 2 ); + + SmCaretPosGraphEntry *left, + *right, + *numLeft, + *denomLeft; + + assert(mpRightMost); + //Set left + left = mpRightMost; + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Create numLeft + numLeft = mpGraph->Add( SmCaretPos( pNum, 0 ), left ); + left->SetRight( numLeft ); + + //Visit pNum + mpRightMost = numLeft; + pNum->Accept( this ); + mpRightMost->SetRight( right ); + right->SetLeft( mpRightMost ); + + //Create denomLeft + denomLeft = mpGraph->Add( SmCaretPos( pDenom, 0 ), left ); + + //Visit pDenom + mpRightMost = denomLeft; + pDenom->Accept( this ); + mpRightMost->SetRight( right ); + + //Set return parameter + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmVerticalBraceNode + * + * Lines in an SmVerticalBraceNode: + * \code + * pScript + * ________ + * / \ + * pBody + * \endcode + * + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + SmNode *pBody = pNode->Body(), + *pScript = pNode->Script(); + //None of these children can be NULL + + SmCaretPosGraphEntry *left, + *bodyLeft, + *scriptLeft, + *right; + + left = mpRightMost; + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Create bodyLeft + bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( bodyLeft ); + mpRightMost = bodyLeft; + pBody->Accept( this ); + mpRightMost->SetRight( right ); + right->SetLeft( mpRightMost ); + + //Create script + scriptLeft = mpGraph->Add( SmCaretPos( pScript, 0 ), left ); + mpRightMost = scriptLeft; + pScript->Accept( this ); + mpRightMost->SetRight( right ); + + //Set return value + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmBinDiagonalNode + * + * Lines in an SmBinDiagonalNode: + * \code + * A / + * / + * / B + * \endcode + * Where A and B are lines. + * + * Used in formulas such as "A wideslash B" + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + SmNode *A = pNode->GetSubNode( 0 ), + *B = pNode->GetSubNode( 1 ); + + SmCaretPosGraphEntry *left, + *leftA, + *rightA, + *leftB, + *right; + left = mpRightMost; + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Create left A + leftA = mpGraph->Add( SmCaretPos( A, 0 ), left ); + left->SetRight( leftA ); + + //Visit A + mpRightMost = leftA; + A->Accept( this ); + rightA = mpRightMost; + + //Create left B + leftB = mpGraph->Add( SmCaretPos( B, 0 ), rightA ); + rightA->SetRight( leftB ); + + //Visit B + mpRightMost = leftB; + B->Accept( this ); + mpRightMost->SetRight( right ); + right->SetLeft( mpRightMost ); + + //Set return value + mpRightMost = right; +} + +//Straight forward ( I think ) +void SmCaretPosGraphBuildingVisitor::Visit( SmBinHorNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} +void SmCaretPosGraphBuildingVisitor::Visit( SmUnHorNode* pNode ) +{ + // Unary operator node + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmExpressionNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmFontNode* pNode ) +{ + //Has only got one child, should act as an expression if possible + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +/** Build SmCaretPosGraph for SmBracebodyNode + * Acts as an SmExpressionNode + * + * Below is an example of a formula tree that has multiple children for SmBracebodyNode + * \dot + * digraph { + * labelloc = "t"; + * label= "Equation: \"lbrace i mline i in setZ rbrace\""; + * n0 [label="SmTableNode"]; + * n0 -> n1 [label="0"]; + * n1 [label="SmLineNode"]; + * n1 -> n2 [label="0"]; + * n2 [label="SmExpressionNode"]; + * n2 -> n3 [label="0"]; + * n3 [label="SmBraceNode"]; + * n3 -> n4 [label="0"]; + * n4 [label="SmMathSymbolNode: {"]; + * n3 -> n5 [label="1"]; + * n5 [label="SmBracebodyNode"]; + * n5 -> n6 [label="0"]; + * n6 [label="SmExpressionNode"]; + * n6 -> n7 [label="0"]; + * n7 [label="SmTextNode: i"]; + * n5 -> n8 [label="1"]; + * n8 [label="SmMathSymbolNode: |"]; // Unicode "VERTICAL LINE" + * n5 -> n9 [label="2"]; + * n9 [label="SmExpressionNode"]; + * n9 -> n10 [label="0"]; + * n10 [label="SmBinHorNode"]; + * n10 -> n11 [label="0"]; + * n11 [label="SmTextNode: i"]; + * n10 -> n12 [label="1"]; + * n12 [label="SmMathSymbolNode: ∈"]; // Unicode "ELEMENT OF" + * n10 -> n13 [label="2"]; + * n13 [label="SmMathSymbolNode: ℤ"]; // Unicode "DOUBLE-STRUCK CAPITAL Z" + * n3 -> n14 [label="2"]; + * n14 [label="SmMathSymbolNode: }"]; + * } + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBracebodyNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + SmCaretPosGraphEntry* pStart = mpGraph->Add( SmCaretPos( pChild, 0), mpRightMost ); + mpRightMost->SetRight( pStart ); + mpRightMost = pStart; + pChild->Accept( this ); + } +} + +/** Build SmCaretPosGraph for SmAlignNode + * Acts as an SmExpressionNode, as it only has one child this okay + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmAlignNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +/** Build SmCaretPosGraph for SmRootNode + * + * Lines in an SmRootNode: + * \code + * _________ + * A/ + * \/ B + * + * \endcode + * A: pExtra ( optional, can be NULL ), + * B: pBody + * + * Graph over these, where "left" is before the SmRootNode and "right" is after: + * \dot + * digraph Graph{ + * left -> B; + * B -> right; + * A -> B; + * } + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmRootNode* pNode ) +{ + SmNode *pExtra = pNode->GetSubNode( 0 ), //Argument, NULL for sqrt, and SmTextNode if cubicroot + *pBody = pNode->GetSubNode( 2 ); //Body of the root + assert(pBody); + + SmCaretPosGraphEntry *left, + *right, + *bodyLeft, + *bodyRight; + + //Get left and save it + assert(mpRightMost); + left = mpRightMost; + + //Create body left + bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( bodyLeft ); + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Visit body + mpRightMost = bodyLeft; + pBody->Accept( this ); + bodyRight = mpRightMost; + bodyRight->SetRight( right ); + right->SetLeft( bodyRight ); + + //Visit pExtra + if( pExtra ){ + mpRightMost = mpGraph->Add( SmCaretPos( pExtra, 0 ), left ); + pExtra->Accept( this ); + mpRightMost->SetRight( bodyLeft ); + } + + mpRightMost = right; +} + + +/** Build SmCaretPosGraph for SmPlaceNode + * Consider this a single character. + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmPlaceNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} + +/** SmErrorNode is context dependent metadata, it can't be selected + * + * @remarks There's no point in deleting, copying and/or moving an instance + * of SmErrorNode as it may not exist in another context! Thus there are no + * positions to select an SmErrorNode. + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmErrorNode* ) +{ +} + +/** Build SmCaretPosGraph for SmBlankNode + * Consider this a single character, as it is only a blank space + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBlankNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmBraceNode + * + * Lines in an SmBraceNode: + * \code + * | | + * | B | + * | | + * \endcode + * B: Body + * + * Graph over these, where "left" is before the SmBraceNode and "right" is after: + * \dot + * digraph Graph{ + * left -> B; + * B -> right; + * } + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBraceNode* pNode ) +{ + SmNode* pBody = pNode->Body(); + + SmCaretPosGraphEntry *left = mpRightMost, + *right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + if( pBody->GetType() != SmNodeType::Bracebody ) { + mpRightMost = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( mpRightMost ); + }else + mpRightMost = left; + + pBody->Accept( this ); + mpRightMost->SetRight( right ); + right->SetLeft( mpRightMost ); + + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmAttributNode + * + * Lines in an SmAttributNode: + * \code + * Attr + * Body + * \endcode + * + * There's a body and an attribute, the construction is used for "widehat A", where "A" is the body + * and "^" is the attribute ( note GetScaleMode( ) on SmAttributNode tells how the attribute should be + * scaled ). + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmAttributNode* pNode ) +{ + SmNode *pAttr = pNode->Attribute(), + *pBody = pNode->Body(); + assert(pAttr); + assert(pBody); + + SmCaretPosGraphEntry *left = mpRightMost, + *attrLeft, + *bodyLeft, + *bodyRight, + *right; + + //Creating bodyleft + bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( bodyLeft ); + + //Creating right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Visit the body + mpRightMost = bodyLeft; + pBody->Accept( this ); + bodyRight = mpRightMost; + bodyRight->SetRight( right ); + right->SetLeft( bodyRight ); + + //Create attrLeft + attrLeft = mpGraph->Add( SmCaretPos( pAttr, 0 ), left ); + + //Visit attribute + mpRightMost = attrLeft; + pAttr->Accept( this ); + mpRightMost->SetRight( right ); + + //Set return value + mpRightMost = right; +} + +//Consider these single symbols +void SmCaretPosGraphBuildingVisitor::Visit( SmSpecialNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} +void SmCaretPosGraphBuildingVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} +void SmCaretPosGraphBuildingVisitor::Visit( SmMathSymbolNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmRootSymbolNode* ) +{ + //Do nothing +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmRectangleNode* ) +{ + //Do nothing +} +void SmCaretPosGraphBuildingVisitor::Visit( SmPolyLineNode* ) +{ + //Do nothing +} + +// SmCloningVisitor + +SmNode* SmCloningVisitor::Clone( SmNode* pNode ) +{ + SmNode* pCurrResult = mpResult; + pNode->Accept( this ); + SmNode* pClone = mpResult; + mpResult = pCurrResult; + return pClone; +} + +void SmCloningVisitor::CloneNodeAttr( SmNode const * pSource, SmNode* pTarget ) +{ + pTarget->SetScaleMode( pSource->GetScaleMode( ) ); + //Other attributes are set when prepare or arrange is executed + //and may depend on stuff not being cloned here. +} + +void SmCloningVisitor::CloneKids( SmStructureNode* pSource, SmStructureNode* pTarget ) +{ + //Cache current result + SmNode* pCurrResult = mpResult; + + //Create array for holding clones + size_t nSize = pSource->GetNumSubNodes( ); + SmNodeArray aNodes( nSize ); + + //Clone children + for (size_t i = 0; i < nSize; ++i) + { + SmNode* pKid; + if( nullptr != ( pKid = pSource->GetSubNode( i ) ) ) + pKid->Accept( this ); + else + mpResult = nullptr; + aNodes[i] = mpResult; + } + + //Set subnodes of pTarget + pTarget->SetSubNodes( std::move(aNodes) ); + + //Restore result as where prior to call + mpResult = pCurrResult; +} + +void SmCloningVisitor::Visit( SmTableNode* pNode ) +{ + SmTableNode* pClone = new SmTableNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBraceNode* pNode ) +{ + SmBraceNode* pClone = new SmBraceNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBracebodyNode* pNode ) +{ + SmBracebodyNode* pClone = new SmBracebodyNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmOperNode* pNode ) +{ + SmOperNode* pClone = new SmOperNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmAlignNode* pNode ) +{ + SmAlignNode* pClone = new SmAlignNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmAttributNode* pNode ) +{ + SmAttributNode* pClone = new SmAttributNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmFontNode* pNode ) +{ + SmFontNode* pClone = new SmFontNode( pNode->GetToken( ) ); + pClone->SetSizeParameter( pNode->GetSizeParameter( ), pNode->GetSizeType( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmUnHorNode* pNode ) +{ + SmUnHorNode* pClone = new SmUnHorNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBinHorNode* pNode ) +{ + SmBinHorNode* pClone = new SmBinHorNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBinVerNode* pNode ) +{ + SmBinVerNode* pClone = new SmBinVerNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + SmBinDiagonalNode *pClone = new SmBinDiagonalNode( pNode->GetToken( ) ); + pClone->SetAscending( pNode->IsAscending( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmSubSupNode* pNode ) +{ + SmSubSupNode *pClone = new SmSubSupNode( pNode->GetToken( ) ); + pClone->SetUseLimits( pNode->IsUseLimits( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmMatrixNode* pNode ) +{ + SmMatrixNode *pClone = new SmMatrixNode( pNode->GetToken( ) ); + pClone->SetRowCol( pNode->GetNumRows( ), pNode->GetNumCols( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmPlaceNode* pNode ) +{ + mpResult = new SmPlaceNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmTextNode* pNode ) +{ + SmTextNode* pClone = new SmTextNode( pNode->GetToken( ), pNode->GetFontDesc( ) ); + pClone->ChangeText( pNode->GetText( ) ); + CloneNodeAttr( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmSpecialNode* pNode ) +{ + mpResult = new SmSpecialNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + mpResult = new SmGlyphSpecialNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmMathSymbolNode* pNode ) +{ + mpResult = new SmMathSymbolNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmBlankNode* pNode ) +{ + SmBlankNode* pClone = new SmBlankNode( pNode->GetToken( ) ); + pClone->SetBlankNum( pNode->GetBlankNum( ) ); + mpResult = pClone; + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmErrorNode* pNode ) +{ + mpResult = new SmErrorNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmLineNode* pNode ) +{ + SmLineNode* pClone = new SmLineNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmExpressionNode* pNode ) +{ + SmExpressionNode* pClone = new SmExpressionNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmPolyLineNode* pNode ) +{ + mpResult = new SmPolyLineNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmRootNode* pNode ) +{ + SmRootNode* pClone = new SmRootNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmRootSymbolNode* pNode ) +{ + mpResult = new SmRootSymbolNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmRectangleNode* pNode ) +{ + mpResult = new SmRectangleNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + SmVerticalBraceNode* pClone = new SmVerticalBraceNode( pNode->GetToken( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +// SmSelectionDrawingVisitor + +SmSelectionDrawingVisitor::SmSelectionDrawingVisitor( OutputDevice& rDevice, SmNode* pTree, const Point& rOffset ) + : mrDev( rDevice ) + , mbHasSelectionArea( false ) +{ + //Visit everything + SAL_WARN_IF( !pTree, "starmath", "pTree can't be null!" ); + if( pTree ) + pTree->Accept( this ); + + //Draw selection if there's any + if( !mbHasSelectionArea ) return; + + maSelectionArea.Move( rOffset.X( ), rOffset.Y( ) ); + + //Save device state + mrDev.Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + //Change colors + mrDev.SetLineColor( ); + mrDev.SetFillColor( COL_LIGHTGRAY ); + + //Draw rectangle + mrDev.DrawRect( maSelectionArea ); + + //Restore device state + mrDev.Pop( ); +} + +void SmSelectionDrawingVisitor::ExtendSelectionArea(const tools::Rectangle& rArea) +{ + if ( ! mbHasSelectionArea ) { + maSelectionArea = rArea; + mbHasSelectionArea = true; + } else + maSelectionArea.Union(rArea); +} + +void SmSelectionDrawingVisitor::DefaultVisit( SmNode* pNode ) +{ + if( pNode->IsSelected( ) ) + ExtendSelectionArea( pNode->AsRectangle( ) ); + VisitChildren( pNode ); +} + +void SmSelectionDrawingVisitor::VisitChildren( SmNode* pNode ) +{ + if(pNode->GetNumSubNodes() == 0) + return; + for( auto pChild : *static_cast<SmStructureNode*>(pNode) ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +void SmSelectionDrawingVisitor::Visit( SmTextNode* pNode ) +{ + if( !pNode->IsSelected()) + return; + + mrDev.Push( PushFlags::TEXTCOLOR | PushFlags::FONT ); + + mrDev.SetFont( pNode->GetFont( ) ); + Point Position = pNode->GetTopLeft( ); + long left = Position.getX( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, pNode->GetSelectionStart( ) ); + long right = Position.getX( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, pNode->GetSelectionEnd( ) ); + long top = Position.getY( ); + long bottom = top + pNode->GetHeight( ); + tools::Rectangle rect( left, top, right, bottom ); + + ExtendSelectionArea( rect ); + + mrDev.Pop( ); +} + +// SmNodeToTextVisitor + +SmNodeToTextVisitor::SmNodeToTextVisitor( SmNode* pNode, OUString &rText ) +{ + pNode->Accept( this ); + rText = maCmdText.makeStringAndClear(); +} + +void SmNodeToTextVisitor::Visit( SmTableNode* pNode ) +{ + if( pNode->GetToken( ).eType == TBINOM ) { + Append( "{ binom" ); + LineToText( pNode->GetSubNode( 0 ) ); + LineToText( pNode->GetSubNode( 1 ) ); + Append("} "); + } else if( pNode->GetToken( ).eType == TSTACK ) { + Append( "stack{ " ); + bool bFirst = true; + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + if(bFirst) + bFirst = false; + else + { + Separate( ); + Append( "# " ); + } + LineToText( pChild ); + } + Separate( ); + Append( "}" ); + } else { //Assume it's a toplevel table, containing lines + bool bFirst = true; + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + if(bFirst) + bFirst = false; + else + { + Separate( ); + Append( "newline" ); + } + Separate( ); + pChild->Accept( this ); + } + } +} + +void SmNodeToTextVisitor::Visit( SmBraceNode* pNode ) +{ + SmNode *pLeftBrace = pNode->OpeningBrace(), + *pBody = pNode->Body(), + *pRightBrace = pNode->ClosingBrace(); + //Handle special case where it's absolute function + if( pNode->GetToken( ).eType == TABS ) { + Append( "abs" ); + LineToText( pBody ); + } else { + if( pNode->GetScaleMode( ) == SmScaleMode::Height ) + Append( "left " ); + pLeftBrace->Accept( this ); + Separate( ); + pBody->Accept( this ); + Separate( ); + if( pNode->GetScaleMode( ) == SmScaleMode::Height ) + Append( "right " ); + pRightBrace->Accept( this ); + } +} + +void SmNodeToTextVisitor::Visit( SmBracebodyNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + Separate( ); + pChild->Accept( this ); + } +} + +void SmNodeToTextVisitor::Visit( SmOperNode* pNode ) +{ + Append( pNode->GetToken( ).aText ); + Separate( ); + if( pNode->GetToken( ).eType == TOPER ){ + //There's an SmGlyphSpecialNode if eType == TOPER + if( pNode->GetSubNode( 0 )->GetType( ) == SmNodeType::SubSup ) + Append( pNode->GetSubNode( 0 )->GetSubNode( 0 )->GetToken( ).aText ); + else + Append( pNode->GetSubNode( 0 )->GetToken( ).aText ); + } + if( pNode->GetSubNode( 0 )->GetType( ) == SmNodeType::SubSup ) { + SmSubSupNode *pSubSup = static_cast<SmSubSupNode*>( pNode->GetSubNode( 0 ) ); + SmNode* pChild = pSubSup->GetSubSup( LSUP ); + if( pChild ) { + Separate( ); + Append( "lsup { " ); + LineToText( pChild ); + Append( "} " ); + } + pChild = pSubSup->GetSubSup( LSUB ); + if( pChild ) { + Separate( ); + Append( "lsub { " ); + LineToText( pChild ); + Append( "} " ); + } + pChild = pSubSup->GetSubSup( RSUP ); + if( pChild ) { + Separate( ); + Append( "^ { " ); + LineToText( pChild ); + Append( "} " ); + } + pChild = pSubSup->GetSubSup( RSUB ); + if( pChild ) { + Separate( ); + Append( "_ { " ); + LineToText( pChild ); + Append( "} " ); + } + pChild = pSubSup->GetSubSup( CSUP ); + if( pChild ) { + Separate( ); + if (pSubSup->IsUseLimits()) + Append( "to { " ); + else + Append( "csup { " ); + LineToText( pChild ); + Append( "} " ); + } + pChild = pSubSup->GetSubSup( CSUB ); + if( pChild ) { + Separate( ); + if (pSubSup->IsUseLimits()) + Append( "from { " ); + else + Append( "csub { " ); + LineToText( pChild ); + Append( "} " ); + } + } + LineToText( pNode->GetSubNode( 1 ) ); +} + +void SmNodeToTextVisitor::Visit( SmAlignNode* pNode ) +{ + Append( pNode->GetToken( ).aText ); + LineToText( pNode->GetSubNode( 0 ) ); +} + +void SmNodeToTextVisitor::Visit( SmAttributNode* pNode ) +{ + Append( pNode->GetToken( ).aText ); + LineToText( pNode->Body() ); +} + +void SmNodeToTextVisitor::Visit( SmFontNode* pNode ) +{ + switch ( pNode->GetToken( ).eType ) + { + case TBOLD: + Append( "bold " ); + break; + case TNBOLD: + Append( "nbold " ); + break; + case TITALIC: + Append( "italic " ); + break; + case TNITALIC: + Append( "nitalic " ); + break; + case TPHANTOM: + Append( "phantom " ); + break; + case TSIZE: + { + Append( "size " ); + switch ( pNode->GetSizeType( ) ) + { + case FontSizeType::PLUS: + Append( "+" ); + break; + case FontSizeType::MINUS: + Append( "-" ); + break; + case FontSizeType::MULTIPLY: + Append( "*" ); + break; + case FontSizeType::DIVIDE: + Append( "/" ); + break; + case FontSizeType::ABSOLUT: + default: + break; + } + Append( ::rtl::math::doubleToUString( + static_cast<double>( pNode->GetSizeParameter( ) ), + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, '.', true ) ); + Append( " " ); + } + break; + case TBLACK: + Append( "color black " ); + break; + case TWHITE: + Append( "color white " ); + break; + case TRED: + Append( "color red " ); + break; + case TGREEN: + Append( "color green " ); + break; + case TBLUE: + Append( "color blue " ); + break; + case TCYAN: + Append( "color cyan " ); + break; + case TMAGENTA: + Append( "color magenta " ); + break; + case TYELLOW: + Append( "color yellow " ); + break; + case TSANS: + Append( "font sans " ); + break; + case TSERIF: + Append( "font serif " ); + break; + case TFIXED: + Append( "font fixed " ); + break; + default: + break; + } + LineToText( pNode->GetSubNode( 1 ) ); +} + +void SmNodeToTextVisitor::Visit( SmUnHorNode* pNode ) +{ + if(pNode->GetSubNode( 1 )->GetToken( ).eType == TFACT) + { + // visit children in the reverse order + for( auto it = pNode->rbegin(); it != pNode->rend(); ++it ) + { + auto pChild = *it; + if(!pChild) + continue; + Separate( ); + pChild->Accept( this ); + } + } + else + { + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + Separate( ); + pChild->Accept( this ); + } + } +} + +void SmNodeToTextVisitor::Visit( SmBinHorNode* pNode ) +{ + const SmNode *pParent = pNode->GetParent(); + bool bBraceNeeded = pParent && pParent->GetType() == SmNodeType::Font; + SmNode *pLeft = pNode->LeftOperand(), + *pOper = pNode->Symbol(), + *pRight = pNode->RightOperand(); + Separate( ); + if (bBraceNeeded) + Append( "{ " ); + pLeft->Accept( this ); + Separate( ); + pOper->Accept( this ); + Separate( ); + pRight->Accept( this ); + Separate( ); + if (bBraceNeeded) + Append( "} " ); +} + +void SmNodeToTextVisitor::Visit( SmBinVerNode* pNode ) +{ + SmNode *pNum = pNode->GetSubNode( 0 ), + *pDenom = pNode->GetSubNode( 2 ); + Append( "{ " ); + LineToText( pNum ); + Append( "over" ); + LineToText( pDenom ); + Append( "} " ); +} + +void SmNodeToTextVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + SmNode *pLeftOperand = pNode->GetSubNode( 0 ), + *pRightOperand = pNode->GetSubNode( 1 ); + Append( "{ " ); + LineToText( pLeftOperand ); + Separate( ); + Append( "wideslash " ); + LineToText( pRightOperand ); + Append( "} " ); +} + +void SmNodeToTextVisitor::Visit( SmSubSupNode* pNode ) +{ + LineToText( pNode->GetBody( ) ); + SmNode *pChild = pNode->GetSubSup( LSUP ); + if( pChild ) { + Separate( ); + Append( "lsup " ); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( LSUB ); + if( pChild ) { + Separate( ); + Append( "lsub " ); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( RSUP ); + if( pChild ) { + Separate( ); + Append( "^ " ); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( RSUB ); + if( pChild ) { + Separate( ); + Append( "_ " ); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( CSUP ); + if( pChild ) { + Separate( ); + if (pNode->IsUseLimits()) + Append( "to " ); + else + Append( "csup " ); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( CSUB ); + if( pChild ) { + Separate( ); + if (pNode->IsUseLimits()) + Append( "from " ); + else + Append( "csub " ); + LineToText( pChild ); + } +} + +void SmNodeToTextVisitor::Visit( SmMatrixNode* pNode ) +{ + Append( "matrix{" ); + for (size_t i = 0; i < pNode->GetNumRows(); ++i) + { + for (size_t j = 0; j < pNode->GetNumCols( ); ++j) + { + SmNode* pSubNode = pNode->GetSubNode( i * pNode->GetNumCols( ) + j ); + Separate( ); + pSubNode->Accept( this ); + Separate( ); + if (j != pNode->GetNumCols() - 1U) + Append( "#" ); + } + Separate( ); + if (i != pNode->GetNumRows() - 1U) + Append( "##" ); + } + Append( "} " ); +} + +void SmNodeToTextVisitor::Visit( SmPlaceNode* ) +{ + Append( "<?>" ); +} + +void SmNodeToTextVisitor::Visit( SmTextNode* pNode ) +{ + //TODO: This method might need improvements, see SmTextNode::CreateTextFromNode + if( pNode->GetToken( ).eType == TTEXT ) + Append( "\"" ); + Append( pNode->GetText( ) ); + if( pNode->GetToken( ).eType == TTEXT ) + Append( "\"" ); +} + +void SmNodeToTextVisitor::Visit( SmSpecialNode* pNode ) +{ + Append( pNode->GetToken( ).aText ); +} + +void SmNodeToTextVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + if( pNode->GetToken( ).eType == TBOPER ) + Append( "boper " ); + else + Append( "uoper " ); + Append( pNode->GetToken( ).aText ); +} + +void SmNodeToTextVisitor::Visit( SmMathSymbolNode* pNode ) +{ + Append( pNode->GetToken( ).aText ); +} + +void SmNodeToTextVisitor::Visit( SmBlankNode* pNode ) +{ + sal_uInt16 nNum = pNode->GetBlankNum(); + if (nNum <= 0) + return; + sal_uInt16 nWide = nNum / 4; + sal_uInt16 nNarrow = nNum % 4; + for (sal_uInt16 i = 0; i < nWide; i++) + Append( "~" ); + for (sal_uInt16 i = 0; i < nNarrow; i++) + Append( "`" ); + Append( " " ); +} + +void SmNodeToTextVisitor::Visit( SmErrorNode* ) +{ +} + +void SmNodeToTextVisitor::Visit( SmLineNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + Separate( ); + pChild->Accept( this ); + } +} + +void SmNodeToTextVisitor::Visit( SmExpressionNode* pNode ) +{ + bool bracketsNeeded = pNode->GetNumSubNodes() != 1 || pNode->GetSubNode(0)->GetType() == SmNodeType::BinHor; + if (!bracketsNeeded) + { + const SmNode *pParent = pNode->GetParent(); + // nested subsups + bracketsNeeded = + pParent && pParent->GetType() == SmNodeType::SubSup && + pNode->GetNumSubNodes() == 1 && + pNode->GetSubNode(0)->GetType() == SmNodeType::SubSup; + } + + if (bracketsNeeded) { + Append( "{ " ); + } + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + Separate( ); + } + if (bracketsNeeded) { + Append( "} " ); + } +} + +void SmNodeToTextVisitor::Visit( SmPolyLineNode* ) +{ +} + +void SmNodeToTextVisitor::Visit( SmRootNode* pNode ) +{ + SmNode *pExtra = pNode->GetSubNode( 0 ), + *pBody = pNode->GetSubNode( 2 ); + if( pExtra ) { + Append( "nroot" ); + LineToText( pExtra ); + } else + Append( "sqrt" ); + LineToText( pBody ); +} + +void SmNodeToTextVisitor::Visit( SmRootSymbolNode* ) +{ +} + +void SmNodeToTextVisitor::Visit( SmRectangleNode* ) +{ +} + +void SmNodeToTextVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + SmNode *pBody = pNode->Body(), + *pScript = pNode->Script(); + LineToText( pBody ); + Append( pNode->GetToken( ).aText ); + LineToText( pScript ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/wordexportbase.cxx b/starmath/source/wordexportbase.cxx new file mode 100644 index 000000000..7f4699dfb --- /dev/null +++ b/starmath/source/wordexportbase.cxx @@ -0,0 +1,185 @@ +/* -*- 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 "wordexportbase.hxx" +#include <node.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +SmWordExportBase::SmWordExportBase(const SmNode* pIn) + : m_pTree(pIn) +{ +} + +SmWordExportBase::~SmWordExportBase() = default; + +void SmWordExportBase::HandleNode(const SmNode* pNode, int nLevel) +{ + SAL_INFO("starmath.wordbase", + "Node: " << nLevel << " " << int(pNode->GetType()) << " " << pNode->GetNumSubNodes()); + switch (pNode->GetType()) + { + case SmNodeType::Attribut: + HandleAttribute(static_cast<const SmAttributNode*>(pNode), nLevel); + break; + case SmNodeType::Text: + HandleText(pNode, nLevel); + break; + case SmNodeType::VerticalBrace: + HandleVerticalBrace(static_cast<const SmVerticalBraceNode*>(pNode), nLevel); + break; + case SmNodeType::Brace: + HandleBrace(static_cast<const SmBraceNode*>(pNode), nLevel); + break; + case SmNodeType::Oper: + HandleOperator(static_cast<const SmOperNode*>(pNode), nLevel); + break; + case SmNodeType::UnHor: + HandleUnaryOperation(static_cast<const SmUnHorNode*>(pNode), nLevel); + break; + case SmNodeType::BinHor: + HandleBinaryOperation(static_cast<const SmBinHorNode*>(pNode), nLevel); + break; + case SmNodeType::BinVer: + HandleFractions(pNode, nLevel, nullptr); + break; + case SmNodeType::Root: + HandleRoot(static_cast<const SmRootNode*>(pNode), nLevel); + break; + case SmNodeType::Special: + { + auto pText = static_cast<const SmTextNode*>(pNode); + //if the token str and the result text are the same then this + //is to be seen as text, else assume it's a mathchar + if (pText->GetText() == pText->GetToken().aText) + HandleText(pText, nLevel); + else + HandleMath(pText, nLevel); + break; + } + case SmNodeType::Math: + case SmNodeType::MathIdent: + HandleMath(pNode, nLevel); + break; + case SmNodeType::SubSup: + HandleSubSupScript(static_cast<const SmSubSupNode*>(pNode), nLevel); + break; + case SmNodeType::Expression: + HandleAllSubNodes(pNode, nLevel); + break; + case SmNodeType::Table: + //Root Node, PILE equivalent, i.e. vertical stack + HandleTable(pNode, nLevel); + break; + case SmNodeType::Matrix: + HandleMatrix(static_cast<const SmMatrixNode*>(pNode), nLevel); + break; + case SmNodeType::Line: + { + // TODO + HandleAllSubNodes(pNode, nLevel); + } + break; +#if 0 + case SmNodeType::Align: + HandleMAlign(pNode,nLevel); + break; +#endif + case SmNodeType::Place: + // explicitly do nothing, MSOffice treats that as a placeholder if item is missing + break; + case SmNodeType::Blank: + HandleBlank(); + break; + default: + HandleAllSubNodes(pNode, nLevel); + break; + } +} + +//Root Node, PILE equivalent, i.e. vertical stack +void SmWordExportBase::HandleTable(const SmNode* pNode, int nLevel) +{ + //The root of the starmath is a table, if + //we convert this them each iteration of + //conversion from starmath to Word will + //add an extra unnecessary level to the + //Word output stack which would grow + //without bound in a multi step conversion + if (nLevel || pNode->GetNumSubNodes() > 1) + HandleVerticalStack(pNode, nLevel); + else + HandleAllSubNodes(pNode, nLevel); +} + +void SmWordExportBase::HandleAllSubNodes(const SmNode* pNode, int nLevel) +{ + int size = pNode->GetNumSubNodes(); + for (int i = 0; i < size; ++i) + { + // TODO remove when all types of nodes are handled properly + if (pNode->GetSubNode(i) == nullptr) + { + SAL_WARN("starmath.wordbase", "Subnode is NULL, parent node not handled properly"); + continue; + } + HandleNode(pNode->GetSubNode(i), nLevel + 1); + } +} + +void SmWordExportBase::HandleUnaryOperation(const SmUnHorNode* pNode, int nLevel) +{ + // update HandleMath() when adding new items + SAL_INFO("starmath.wordbase", "Unary: " << int(pNode->GetToken().eType)); + + HandleAllSubNodes(pNode, nLevel); +} + +void SmWordExportBase::HandleBinaryOperation(const SmBinHorNode* pNode, int nLevel) +{ + SAL_INFO("starmath.wordbase", "Binary: " << int(pNode->Symbol()->GetToken().eType)); + // update HandleMath() when adding new items + switch (pNode->Symbol()->GetToken().eType) + { + case TDIVIDEBY: + return HandleFractions(pNode, nLevel, "lin"); + default: + HandleAllSubNodes(pNode, nLevel); + break; + } +} + +void SmWordExportBase::HandleMath(const SmNode* pNode, int nLevel) +{ + SAL_INFO("starmath.wordbase", "Math: " << int(pNode->GetToken().eType)); + switch (pNode->GetToken().eType) + { + case TDIVIDEBY: + case TACUTE: + OSL_ASSERT(false); + [[fallthrough]]; // the above are handled elsewhere, e.g. when handling BINHOR + default: + HandleText(pNode, nLevel); + break; + } +} + +void SmWordExportBase::HandleSubSupScript(const SmSubSupNode* pNode, int nLevel) +{ + // set flags to a bitfield of which sub/sup items exists + int flags = (pNode->GetSubSup(CSUB) != nullptr ? (1 << CSUB) : 0) + | (pNode->GetSubSup(CSUP) != nullptr ? (1 << CSUP) : 0) + | (pNode->GetSubSup(RSUB) != nullptr ? (1 << RSUB) : 0) + | (pNode->GetSubSup(RSUP) != nullptr ? (1 << RSUP) : 0) + | (pNode->GetSubSup(LSUB) != nullptr ? (1 << LSUB) : 0) + | (pNode->GetSubSup(LSUP) != nullptr ? (1 << LSUP) : 0); + HandleSubSupScriptInternal(pNode, nLevel, flags); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/wordexportbase.hxx b/starmath/source/wordexportbase.hxx new file mode 100644 index 000000000..4f191df2a --- /dev/null +++ b/starmath/source/wordexportbase.hxx @@ -0,0 +1,57 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_STARMATH_SOURCE_WORDEXPORTBASE_HXX +#define INCLUDED_STARMATH_SOURCE_WORDEXPORTBASE_HXX + +class SmAttributNode; +class SmBinHorNode; +class SmBraceNode; +class SmMatrixNode; +class SmNode; +class SmOperNode; +class SmRootNode; +class SmSubSupNode; +class SmUnHorNode; +class SmVerticalBraceNode; + +/** + Base class implementing writing of formulas to Word. + */ +class SmWordExportBase +{ +public: + explicit SmWordExportBase(const SmNode* pIn); + virtual ~SmWordExportBase(); + +protected: + void HandleNode(const SmNode* pNode, int nLevel); + void HandleAllSubNodes(const SmNode* pNode, int nLevel); + void HandleTable(const SmNode* pNode, int nLevel); + virtual void HandleVerticalStack(const SmNode* pNode, int nLevel) = 0; + virtual void HandleText(const SmNode* pNode, int nLevel) = 0; + void HandleMath(const SmNode* pNode, int nLevel); + virtual void HandleFractions(const SmNode* pNode, int nLevel, const char* type) = 0; + void HandleUnaryOperation(const SmUnHorNode* pNode, int nLevel); + void HandleBinaryOperation(const SmBinHorNode* pNode, int nLevel); + virtual void HandleRoot(const SmRootNode* pNode, int nLevel) = 0; + virtual void HandleAttribute(const SmAttributNode* pNode, int nLevel) = 0; + virtual void HandleOperator(const SmOperNode* pNode, int nLevel) = 0; + void HandleSubSupScript(const SmSubSupNode* pNode, int nLevel); + virtual void HandleSubSupScriptInternal(const SmSubSupNode* pNode, int nLevel, int flags) = 0; + virtual void HandleMatrix(const SmMatrixNode* pNode, int nLevel) = 0; + virtual void HandleBrace(const SmBraceNode* pNode, int nLevel) = 0; + virtual void HandleVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) = 0; + virtual void HandleBlank() = 0; + const SmNode* const m_pTree; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |