Branch libreoffice-6-3
[LibreOffice.git] / starmath / source / rect.cxx
blobab32022aac2a2bcc3eddbcac83034bd9577a0026
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <osl/diagnose.h>
21 #include <vcl/metric.hxx>
22 #include <vcl/svapp.hxx>
23 #include <vcl/virdev.hxx>
24 #include <sal/log.hxx>
26 #include <format.hxx>
27 #include <rect.hxx>
28 #include <types.hxx>
29 #include <smmod.hxx>
31 #include <cassert>
32 #include <unordered_set>
34 namespace {
36 bool SmGetGlyphBoundRect(const vcl::RenderContext &rDev,
37 const OUString &rText, tools::Rectangle &rRect)
38 // basically the same as 'GetTextBoundRect' (in class 'OutputDevice')
39 // but with a string as argument.
41 // handle special case first
42 if (rText.isEmpty())
44 rRect.SetEmpty();
45 return true;
48 // get a device where 'OutputDevice::GetTextBoundRect' will be successful
49 OutputDevice *pGlyphDev;
50 if (rDev.GetOutDevType() != OUTDEV_PRINTER)
51 pGlyphDev = const_cast<OutputDevice *>(&rDev);
52 else
54 // since we format for the printer (where GetTextBoundRect will fail)
55 // we need a virtual device here.
56 pGlyphDev = &SM_MOD()->GetDefaultVirtualDev();
59 const FontMetric aDevFM (rDev.GetFontMetric());
61 pGlyphDev->Push(PushFlags::FONT | PushFlags::MAPMODE);
62 vcl::Font aFnt(rDev.GetFont());
63 aFnt.SetAlignment(ALIGN_TOP);
65 // use scale factor when calling GetTextBoundRect to counter
66 // negative effects from antialiasing which may otherwise result
67 // in significant incorrect bounding rectangles for some characters.
68 Size aFntSize = aFnt.GetFontSize();
70 // Workaround to avoid HUGE font sizes and resulting problems
71 long nScaleFactor = 1;
72 while( aFntSize.Height() > 2000 * nScaleFactor )
73 nScaleFactor *= 2;
75 aFnt.SetFontSize( Size( aFntSize.Width() / nScaleFactor, aFntSize.Height() / nScaleFactor ) );
76 pGlyphDev->SetFont(aFnt);
78 long nTextWidth = rDev.GetTextWidth(rText);
79 tools::Rectangle aResult (Point(), Size(nTextWidth, rDev.GetTextHeight())),
80 aTmp;
82 bool bSuccess = pGlyphDev->GetTextBoundRect(aTmp, rText);
83 OSL_ENSURE( bSuccess, "GetTextBoundRect failed" );
86 if (!aTmp.IsEmpty())
88 aResult = tools::Rectangle(aTmp.Left() * nScaleFactor, aTmp.Top() * nScaleFactor,
89 aTmp.Right() * nScaleFactor, aTmp.Bottom() * nScaleFactor);
90 if (&rDev != pGlyphDev) /* only when rDev is a printer... */
92 long nGDTextWidth = pGlyphDev->GetTextWidth(rText);
93 if (nGDTextWidth != 0 &&
94 nTextWidth != nGDTextWidth)
96 aResult.SetRight( aResult.Right() * nTextWidth );
97 aResult.SetRight( aResult.Right() / ( nGDTextWidth * nScaleFactor) );
102 // move rectangle to match possibly different baselines
103 // (because of different devices)
104 long nDelta = aDevFM.GetAscent() - pGlyphDev->GetFontMetric().GetAscent() * nScaleFactor;
105 aResult.Move(0, nDelta);
107 pGlyphDev->Pop();
109 rRect = aResult;
110 return bSuccess;
113 bool SmIsMathAlpha(const OUString &rText)
114 // true iff symbol (from StarMath Font) should be treated as letter
116 // Set of symbols, which should be treated as letters in StarMath Font
117 // (to get a normal (non-clipped) SmRect in contrast to the other operators
118 // and symbols).
119 static std::unordered_set<sal_Unicode> const aMathAlpha({
120 MS_ALEPH, MS_IM, MS_RE,
121 MS_WP, u'\xE070', MS_EMPTYSET,
122 u'\x2113', u'\xE0D6', u'\x2107',
123 u'\x2127', u'\x210A', MS_HBAR,
124 MS_LAMBDABAR, MS_SETN, MS_SETZ,
125 MS_SETQ, MS_SETR, MS_SETC,
126 u'\x2373', u'\xE0A5', u'\x2112',
127 u'\x2130', u'\x2131'
130 if (rText.isEmpty())
131 return false;
133 OSL_ENSURE(rText.getLength() == 1, "Sm : string must be exactly one character long");
134 sal_Unicode cChar = rText[0];
136 // is it a greek symbol?
137 if (u'\xE0AC' <= cChar && cChar <= u'\xE0D4')
138 return true;
139 // or, does it appear in 'aMathAlpha'?
140 return aMathAlpha.find(cChar) != aMathAlpha.end();
146 SmRect::SmRect()
147 // constructs empty rectangle at (0, 0) with width and height 0.
148 : aTopLeft(0, 0)
149 , aSize(0, 0)
150 , nBaseline(0)
151 , nAlignT(0)
152 , nAlignM(0)
153 , nAlignB(0)
154 , nGlyphTop(0)
155 , nGlyphBottom(0)
156 , nItalicLeftSpace(0)
157 , nItalicRightSpace(0)
158 , nLoAttrFence(0)
159 , nHiAttrFence(0)
160 , nBorderWidth(0)
161 , bHasBaseline(false)
162 , bHasAlignInfo(false)
167 void SmRect::CopyAlignInfo(const SmRect &rRect)
169 nBaseline = rRect.nBaseline;
170 bHasBaseline = rRect.bHasBaseline;
171 nAlignT = rRect.nAlignT;
172 nAlignM = rRect.nAlignM;
173 nAlignB = rRect.nAlignB;
174 bHasAlignInfo = rRect.bHasAlignInfo;
175 nLoAttrFence = rRect.nLoAttrFence;
176 nHiAttrFence = rRect.nHiAttrFence;
180 SmRect::SmRect(const OutputDevice &rDev, const SmFormat *pFormat,
181 const OUString &rText, sal_uInt16 nBorder)
182 // get rectangle fitting for drawing 'rText' on OutputDevice 'rDev'
183 : aTopLeft(0, 0)
184 , aSize(rDev.GetTextWidth(rText), rDev.GetTextHeight())
186 const FontMetric aFM (rDev.GetFontMetric());
187 bool bIsMath = aFM.GetFamilyName().equalsIgnoreAsciiCase( FONTNAME_MATH );
188 bool bAllowSmaller = bIsMath && !SmIsMathAlpha(rText);
189 const long nFontHeight = rDev.GetFont().GetFontSize().Height();
191 nBorderWidth = nBorder;
192 bHasAlignInfo = true;
193 bHasBaseline = true;
194 nBaseline = aFM.GetAscent();
195 nAlignT = nBaseline - nFontHeight * 750 / 1000;
196 nAlignM = nBaseline - nFontHeight * 121 / 422;
197 // that's where the horizontal bars of '+', '-', ... are
198 // (1/3 of ascent over baseline)
199 // (121 = 1/3 of 12pt ascent, 422 = 12pt fontheight)
200 nAlignB = nBaseline;
202 // workaround for printer fonts with very small (possible 0 or even
203 // negative(!)) leading
204 if (aFM.GetInternalLeading() < 5 && rDev.GetOutDevType() == OUTDEV_PRINTER)
206 OutputDevice *pWindow = Application::GetDefaultDevice();
208 pWindow->Push(PushFlags::MAPMODE | PushFlags::FONT);
210 pWindow->SetMapMode(rDev.GetMapMode());
211 pWindow->SetFont(rDev.GetFontMetric());
213 long nDelta = pWindow->GetFontMetric().GetInternalLeading();
214 if (nDelta == 0)
215 { // this value approx. fits a Leading of 80 at a
216 // Fontheight of 422 (12pt)
217 nDelta = nFontHeight * 8 / 43;
219 SetTop(GetTop() - nDelta);
221 pWindow->Pop();
224 // get GlyphBoundRect
225 tools::Rectangle aGlyphRect;
226 bool bSuccess = SmGetGlyphBoundRect(rDev, rText, aGlyphRect);
227 if (!bSuccess)
228 SAL_WARN("starmath", "Ooops... (Font missing?)");
230 nItalicLeftSpace = GetLeft() - aGlyphRect.Left() + nBorderWidth;
231 nItalicRightSpace = aGlyphRect.Right() - GetRight() + nBorderWidth;
232 if (nItalicLeftSpace < 0 && !bAllowSmaller)
233 nItalicLeftSpace = 0;
234 if (nItalicRightSpace < 0 && !bAllowSmaller)
235 nItalicRightSpace = 0;
237 long nDist = 0;
238 if (pFormat)
239 nDist = (rDev.GetFont().GetFontSize().Height()
240 * pFormat->GetDistance(DIS_ORNAMENTSIZE)) / 100;
242 nHiAttrFence = aGlyphRect.TopLeft().Y() - 1 - nBorderWidth - nDist;
243 nLoAttrFence = SmFromTo(GetAlignB(), GetBottom(), 0.0);
245 nGlyphTop = aGlyphRect.Top() - nBorderWidth;
246 nGlyphBottom = aGlyphRect.Bottom() + nBorderWidth;
248 if (bAllowSmaller)
250 // for symbols and operators from the StarMath Font
251 // we adjust upper and lower margin of the symbol
252 SetTop(nGlyphTop);
253 SetBottom(nGlyphBottom);
256 if (nHiAttrFence < GetTop())
257 nHiAttrFence = GetTop();
259 if (nLoAttrFence > GetBottom())
260 nLoAttrFence = GetBottom();
262 OSL_ENSURE(rText.isEmpty() || !IsEmpty(),
263 "Sm: empty rectangle created");
267 SmRect::SmRect(long nWidth, long nHeight)
268 // this constructor should never be used for anything textlike because
269 // it will not provide useful values for baseline, AlignT and AlignB!
270 // It's purpose is to get a 'SmRect' for the horizontal line in fractions
271 // as used in 'SmBinVerNode'.
272 : aTopLeft(0, 0)
273 , aSize(nWidth, nHeight)
274 , nBaseline(0)
275 , nItalicLeftSpace(0)
276 , nItalicRightSpace(0)
277 , nBorderWidth(0)
278 , bHasBaseline(false)
279 , bHasAlignInfo(true)
281 nAlignT = nGlyphTop = nHiAttrFence = GetTop();
282 nAlignB = nGlyphBottom = nLoAttrFence = GetBottom();
283 nAlignM = (nAlignT + nAlignB) / 2; // this is the default
287 void SmRect::SetLeft(long nLeft)
289 if (nLeft <= GetRight())
290 { aSize.setWidth( GetRight() - nLeft + 1 );
291 aTopLeft.setX( nLeft );
296 void SmRect::SetRight(long nRight)
298 if (nRight >= GetLeft())
299 aSize.setWidth( nRight - GetLeft() + 1 );
303 void SmRect::SetBottom(long nBottom)
305 if (nBottom >= GetTop())
306 aSize.setHeight( nBottom - GetTop() + 1 );
310 void SmRect::SetTop(long nTop)
312 if (nTop <= GetBottom())
313 { aSize.setHeight( GetBottom() - nTop + 1 );
314 aTopLeft.setY( nTop );
319 void SmRect::Move(const Point &rPosition)
320 // move rectangle by position 'rPosition'.
322 aTopLeft += rPosition;
324 long nDelta = rPosition.Y();
325 nBaseline += nDelta;
326 nAlignT += nDelta;
327 nAlignM += nDelta;
328 nAlignB += nDelta;
329 nGlyphTop += nDelta;
330 nGlyphBottom += nDelta;
331 nHiAttrFence += nDelta;
332 nLoAttrFence += nDelta;
336 const Point SmRect::AlignTo(const SmRect &rRect, RectPos ePos,
337 RectHorAlign eHor, RectVerAlign eVer) const
338 { Point aPos (GetTopLeft());
339 // will become the topleft point of the new rectangle position
341 // set horizontal or vertical new rectangle position depending on ePos
342 switch (ePos)
343 { case RectPos::Left:
344 aPos.setX( rRect.GetItalicLeft() - GetItalicRightSpace()
345 - GetWidth() );
346 break;
347 case RectPos::Right:
348 aPos.setX( rRect.GetItalicRight() + 1 + GetItalicLeftSpace() );
349 break;
350 case RectPos::Top:
351 aPos.setY( rRect.GetTop() - GetHeight() );
352 break;
353 case RectPos::Bottom:
354 aPos.setY( rRect.GetBottom() + 1 );
355 break;
356 case RectPos::Attribute:
357 aPos.setX( rRect.GetItalicCenterX() - GetItalicWidth() / 2
358 + GetItalicLeftSpace() );
359 break;
360 default:
361 assert(false);
364 // check if horizontal position is already set
365 if (ePos == RectPos::Left || ePos == RectPos::Right || ePos == RectPos::Attribute)
366 // correct error in current vertical position
367 switch (eVer)
368 { case RectVerAlign::Top :
369 aPos.AdjustY(rRect.GetAlignT() - GetAlignT() );
370 break;
371 case RectVerAlign::Mid :
372 aPos.AdjustY(rRect.GetAlignM() - GetAlignM() );
373 break;
374 case RectVerAlign::Baseline :
375 // align baselines if possible else align mid's
376 if (HasBaseline() && rRect.HasBaseline())
377 aPos.AdjustY(rRect.GetBaseline() - GetBaseline() );
378 else
379 aPos.AdjustY(rRect.GetAlignM() - GetAlignM() );
380 break;
381 case RectVerAlign::Bottom :
382 aPos.AdjustY(rRect.GetAlignB() - GetAlignB() );
383 break;
384 case RectVerAlign::CenterY :
385 aPos.AdjustY(rRect.GetCenterY() - GetCenterY() );
386 break;
387 case RectVerAlign::AttributeHi:
388 aPos.AdjustY(rRect.GetHiAttrFence() - GetBottom() );
389 break;
390 case RectVerAlign::AttributeMid :
391 aPos.AdjustY(SmFromTo(rRect.GetAlignB(), rRect.GetAlignT(), 0.4)
392 - GetCenterY() );
393 break;
394 case RectVerAlign::AttributeLo :
395 aPos.AdjustY(rRect.GetLoAttrFence() - GetTop() );
396 break;
397 default :
398 assert(false);
401 // check if vertical position is already set
402 if (ePos == RectPos::Top || ePos == RectPos::Bottom)
403 // correct error in current horizontal position
404 switch (eHor)
405 { case RectHorAlign::Left:
406 aPos.AdjustX(rRect.GetItalicLeft() - GetItalicLeft() );
407 break;
408 case RectHorAlign::Center:
409 aPos.AdjustX(rRect.GetItalicCenterX() - GetItalicCenterX() );
410 break;
411 case RectHorAlign::Right:
412 aPos.AdjustX(rRect.GetItalicRight() - GetItalicRight() );
413 break;
414 default:
415 assert(false);
418 return aPos;
422 void SmRect::Union(const SmRect &rRect)
423 // rectangle union of current one with 'rRect'. The result is to be the
424 // smallest rectangles that covers the space of both rectangles.
425 // (empty rectangles cover no space)
426 //! Italic correction is NOT taken into account here!
428 if (rRect.IsEmpty())
429 return;
431 long nL = rRect.GetLeft(),
432 nR = rRect.GetRight(),
433 nT = rRect.GetTop(),
434 nB = rRect.GetBottom(),
435 nGT = rRect.nGlyphTop,
436 nGB = rRect.nGlyphBottom;
437 if (!IsEmpty())
438 { long nTmp;
440 if ((nTmp = GetLeft()) < nL)
441 nL = nTmp;
442 if ((nTmp = GetRight()) > nR)
443 nR = nTmp;
444 if ((nTmp = GetTop()) < nT)
445 nT = nTmp;
446 if ((nTmp = GetBottom()) > nB)
447 nB = nTmp;
448 if ((nTmp = nGlyphTop) < nGT)
449 nGT = nTmp;
450 if ((nTmp = nGlyphBottom) > nGB)
451 nGB = nTmp;
454 SetLeft(nL);
455 SetRight(nR);
456 SetTop(nT);
457 SetBottom(nB);
458 nGlyphTop = nGT;
459 nGlyphBottom = nGB;
463 SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode)
464 // let current rectangle be the union of itself and 'rRect'
465 // (the smallest rectangle surrounding both). Also adapt values for
466 // 'AlignT', 'AlignM', 'AlignB', baseline and italic-spaces.
467 // The baseline is set according to 'eCopyMode'.
468 // If one of the rectangles has no relevant info the other one is copied.
470 // get some values used for (italic) spaces adaption
471 // ! (need to be done before changing current SmRect) !
472 long nL = std::min(GetItalicLeft(), rRect.GetItalicLeft()),
473 nR = std::max(GetItalicRight(), rRect.GetItalicRight());
475 Union(rRect);
477 SetItalicSpaces(GetLeft() - nL, nR - GetRight());
479 if (!HasAlignInfo())
480 CopyAlignInfo(rRect);
481 else if (rRect.HasAlignInfo())
483 assert(HasAlignInfo());
484 nAlignT = std::min(GetAlignT(), rRect.GetAlignT());
485 nAlignB = std::max(GetAlignB(), rRect.GetAlignB());
486 nHiAttrFence = std::min(GetHiAttrFence(), rRect.GetHiAttrFence());
487 nLoAttrFence = std::max(GetLoAttrFence(), rRect.GetLoAttrFence());
489 switch (eCopyMode)
490 { case RectCopyMBL::This:
491 // already done
492 break;
493 case RectCopyMBL::Arg:
494 CopyMBL(rRect);
495 break;
496 case RectCopyMBL::None:
497 bHasBaseline = false;
498 nAlignM = (nAlignT + nAlignB) / 2;
499 break;
500 case RectCopyMBL::Xor:
501 if (!HasBaseline())
502 CopyMBL(rRect);
503 break;
504 default :
505 assert(false);
509 return *this;
513 void SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode,
514 long nNewAlignM)
515 // as 'ExtendBy' but sets AlignM value to 'nNewAlignM'.
516 // (this version will be used in 'SmBinVerNode' to provide means to
517 // align eg "{a over b} over c" correctly where AlignM should not
518 // be (AlignT + AlignB) / 2)
520 OSL_ENSURE(HasAlignInfo(), "Sm: no align info");
522 ExtendBy(rRect, eCopyMode);
523 nAlignM = nNewAlignM;
527 SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode,
528 bool bKeepVerAlignParams)
529 // as 'ExtendBy' but keeps original values for AlignT, -M and -B and
530 // baseline.
531 // (this is used in 'SmSupSubNode' where the sub-/supscripts shouldn't
532 // be allowed to modify these values.)
534 long nOldAlignT = GetAlignT(),
535 nOldAlignM = GetAlignM(),
536 nOldAlignB = GetAlignB(),
537 nOldBaseline = nBaseline; //! depends not on 'HasBaseline'
538 bool bOldHasAlignInfo = HasAlignInfo();
540 ExtendBy(rRect, eCopyMode);
542 if (bKeepVerAlignParams)
543 { nAlignT = nOldAlignT;
544 nAlignM = nOldAlignM;
545 nAlignB = nOldAlignB;
546 nBaseline = nOldBaseline;
547 bHasAlignInfo = bOldHasAlignInfo;
550 return *this;
554 long SmRect::OrientedDist(const Point &rPoint) const
555 // return oriented distance of rPoint to the current rectangle,
556 // especially the return value is <= 0 iff the point is inside the
557 // rectangle.
558 // For simplicity the maximum-norm is used.
560 bool bIsInside = IsInsideItalicRect(rPoint);
562 // build reference point to define the distance
563 Point aRef;
564 if (bIsInside)
565 { Point aIC (GetItalicCenterX(), GetCenterY());
567 aRef.setX( rPoint.X() >= aIC.X() ? GetItalicRight() : GetItalicLeft() );
568 aRef.setY( rPoint.Y() >= aIC.Y() ? GetBottom() : GetTop() );
570 else
572 // x-coordinate
573 if (rPoint.X() > GetItalicRight())
574 aRef.setX( GetItalicRight() );
575 else if (rPoint.X() < GetItalicLeft())
576 aRef.setX( GetItalicLeft() );
577 else
578 aRef.setX( rPoint.X() );
579 // y-coordinate
580 if (rPoint.Y() > GetBottom())
581 aRef.setY( GetBottom() );
582 else if (rPoint.Y() < GetTop())
583 aRef.setY( GetTop() );
584 else
585 aRef.setY( rPoint.Y() );
588 // build distance vector
589 Point aDist (aRef - rPoint);
591 long nAbsX = labs(aDist.X()),
592 nAbsY = labs(aDist.Y());
594 return bIsInside ? - std::min(nAbsX, nAbsY) : std::max (nAbsX, nAbsY);
598 bool SmRect::IsInsideRect(const Point &rPoint) const
600 return rPoint.Y() >= GetTop()
601 && rPoint.Y() <= GetBottom()
602 && rPoint.X() >= GetLeft()
603 && rPoint.X() <= GetRight();
607 bool SmRect::IsInsideItalicRect(const Point &rPoint) const
609 return rPoint.Y() >= GetTop()
610 && rPoint.Y() <= GetBottom()
611 && rPoint.X() >= GetItalicLeft()
612 && rPoint.X() <= GetItalicRight();
615 SmRect SmRect::AsGlyphRect() const
617 SmRect aRect (*this);
618 aRect.SetTop(nGlyphTop);
619 aRect.SetBottom(nGlyphBottom);
620 return aRect;
623 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */