diff options
Diffstat (limited to '')
-rw-r--r-- | starmath/source/rect.cxx | 623 |
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: */ |