summaryrefslogtreecommitdiffstats
path: root/starmath/source/rect.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /starmath/source/rect.cxx
parentInitial commit. (diff)
downloadlibreoffice-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/rect.cxx')
-rw-r--r--starmath/source/rect.cxx623
1 files changed, 623 insertions, 0 deletions
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: */