Bump version to 21.06.18.1
[LibreOffice.git] / starmath / source / rect.cxx
blobe3506c67de77a0d27a6a72af5fea229e36a1e882
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 <o3tl/sorted_vector.hxx>
22 #include <vcl/metric.hxx>
23 #include <vcl/svapp.hxx>
24 #include <vcl/virdev.hxx>
25 #include <sal/log.hxx>
27 #include <format.hxx>
28 #include <rect.hxx>
29 #include <types.hxx>
30 #include <smmod.hxx>
32 #include <cassert>
33 #include <cstdlib>
35 namespace {
37 bool SmGetGlyphBoundRect(const vcl::RenderContext &rDev,
38 const OUString &rText, tools::Rectangle &rRect)
39 // basically the same as 'GetTextBoundRect' (in class 'OutputDevice')
40 // but with a string as argument.
42 // handle special case first
43 if (rText.isEmpty())
45 rRect.SetEmpty();
46 return true;
49 // get a device where 'OutputDevice::GetTextBoundRect' will be successful
50 OutputDevice *pGlyphDev;
51 if (rDev.GetOutDevType() != OUTDEV_PRINTER)
52 pGlyphDev = const_cast<OutputDevice *>(&rDev);
53 else
55 // since we format for the printer (where GetTextBoundRect will fail)
56 // we need a virtual device here.
57 pGlyphDev = &SM_MOD()->GetDefaultVirtualDev();
60 const FontMetric aDevFM (rDev.GetFontMetric());
62 pGlyphDev->Push(PushFlags::FONT | PushFlags::MAPMODE);
63 vcl::Font aFnt(rDev.GetFont());
64 aFnt.SetAlignment(ALIGN_TOP);
66 // use scale factor when calling GetTextBoundRect to counter
67 // negative effects from antialiasing which may otherwise result
68 // in significant incorrect bounding rectangles for some characters.
69 Size aFntSize = aFnt.GetFontSize();
71 // Workaround to avoid HUGE font sizes and resulting problems
72 tools::Long nScaleFactor = 1;
73 while( aFntSize.Height() > 2000 * nScaleFactor )
74 nScaleFactor *= 2;
76 aFnt.SetFontSize( Size( aFntSize.Width() / nScaleFactor, aFntSize.Height() / nScaleFactor ) );
77 pGlyphDev->SetFont(aFnt);
79 tools::Long nTextWidth = rDev.GetTextWidth(rText);
80 tools::Rectangle aResult (Point(), Size(nTextWidth, rDev.GetTextHeight())),
81 aTmp;
83 bool bSuccess = pGlyphDev->GetTextBoundRect(aTmp, rText);
84 OSL_ENSURE( bSuccess, "GetTextBoundRect failed" );
87 if (!aTmp.IsEmpty())
89 aResult = tools::Rectangle(aTmp.Left() * nScaleFactor, aTmp.Top() * nScaleFactor,
90 aTmp.Right() * nScaleFactor, aTmp.Bottom() * nScaleFactor);
91 if (&rDev != pGlyphDev) /* only when rDev is a printer... */
93 tools::Long nGDTextWidth = pGlyphDev->GetTextWidth(rText);
94 if (nGDTextWidth != 0 &&
95 nTextWidth != nGDTextWidth)
97 aResult.SetRight( aResult.Right() * nTextWidth );
98 aResult.SetRight( aResult.Right() / ( nGDTextWidth * nScaleFactor) );
103 // move rectangle to match possibly different baselines
104 // (because of different devices)
105 tools::Long nDelta = aDevFM.GetAscent() - pGlyphDev->GetFontMetric().GetAscent() * nScaleFactor;
106 aResult.Move(0, nDelta);
108 pGlyphDev->Pop();
110 rRect = aResult;
111 return bSuccess;
114 bool SmIsMathAlpha(const OUString &rText)
115 // true iff symbol (from StarMath Font) should be treated as letter
117 // Set of symbols, which should be treated as letters in StarMath Font
118 // (to get a normal (non-clipped) SmRect in contrast to the other operators
119 // and symbols).
120 static o3tl::sorted_vector<sal_Unicode> const aMathAlpha({
121 MS_ALEPH, MS_IM, MS_RE,
122 MS_WP, u'\xE070', MS_EMPTYSET,
123 u'\x2113', u'\xE0D6', u'\x2107',
124 u'\x2127', u'\x210A', MS_HBAR,
125 MS_LAMBDABAR, MS_SETN, MS_SETZ,
126 MS_SETQ, MS_SETR, MS_SETC,
127 u'\x2373', u'\xE0A5', u'\x2112',
128 u'\x2130', u'\x2131'
131 if (rText.isEmpty())
132 return false;
134 OSL_ENSURE(rText.getLength() == 1, "Sm : string must be exactly one character long");
135 sal_Unicode cChar = rText[0];
137 // is it a greek symbol?
138 if (u'\xE0AC' <= cChar && cChar <= u'\xE0D4')
139 return true;
140 // or, does it appear in 'aMathAlpha'?
141 return aMathAlpha.find(cChar) != aMathAlpha.end();
147 SmRect::SmRect()
148 // constructs empty rectangle at (0, 0) with width and height 0.
149 : aTopLeft(0, 0)
150 , aSize(0, 0)
151 , nBaseline(0)
152 , nAlignT(0)
153 , nAlignM(0)
154 , nAlignB(0)
155 , nGlyphTop(0)
156 , nGlyphBottom(0)
157 , nItalicLeftSpace(0)
158 , nItalicRightSpace(0)
159 , nLoAttrFence(0)
160 , nHiAttrFence(0)
161 , nBorderWidth(0)
162 , bHasBaseline(false)
163 , bHasAlignInfo(false)
168 void SmRect::CopyAlignInfo(const SmRect &rRect)
170 nBaseline = rRect.nBaseline;
171 bHasBaseline = rRect.bHasBaseline;
172 nAlignT = rRect.nAlignT;
173 nAlignM = rRect.nAlignM;
174 nAlignB = rRect.nAlignB;
175 bHasAlignInfo = rRect.bHasAlignInfo;
176 nLoAttrFence = rRect.nLoAttrFence;
177 nHiAttrFence = rRect.nHiAttrFence;
181 SmRect::SmRect(const OutputDevice &rDev, const SmFormat *pFormat,
182 const OUString &rText, sal_uInt16 nBorder)
183 // get rectangle fitting for drawing 'rText' on OutputDevice 'rDev'
184 : aTopLeft(0, 0)
185 , aSize(rDev.GetTextWidth(rText), rDev.GetTextHeight())
187 const FontMetric aFM (rDev.GetFontMetric());
188 bool bIsMath = aFM.GetFamilyName().equalsIgnoreAsciiCase( FONTNAME_MATH );
189 bool bAllowSmaller = bIsMath && !SmIsMathAlpha(rText);
190 const tools::Long nFontHeight = rDev.GetFont().GetFontSize().Height();
192 nBorderWidth = nBorder;
193 bHasAlignInfo = true;
194 bHasBaseline = true;
195 nBaseline = aFM.GetAscent();
196 nAlignT = nBaseline - nFontHeight * 750 / 1000;
197 nAlignM = nBaseline - nFontHeight * 121 / 422;
198 // that's where the horizontal bars of '+', '-', ... are
199 // (1/3 of ascent over baseline)
200 // (121 = 1/3 of 12pt ascent, 422 = 12pt fontheight)
201 nAlignB = nBaseline;
203 // workaround for printer fonts with very small (possible 0 or even
204 // negative(!)) leading
205 if (aFM.GetInternalLeading() < 5 && rDev.GetOutDevType() == OUTDEV_PRINTER)
207 OutputDevice *pWindow = Application::GetDefaultDevice();
209 pWindow->Push(PushFlags::MAPMODE | PushFlags::FONT);
211 pWindow->SetMapMode(rDev.GetMapMode());
212 pWindow->SetFont(rDev.GetFontMetric());
214 tools::Long nDelta = pWindow->GetFontMetric().GetInternalLeading();
215 if (nDelta == 0)
216 { // this value approx. fits a Leading of 80 at a
217 // Fontheight of 422 (12pt)
218 nDelta = nFontHeight * 8 / 43;
220 SetTop(GetTop() - nDelta);
222 pWindow->Pop();
225 // get GlyphBoundRect
226 tools::Rectangle aGlyphRect;
227 bool bSuccess = SmGetGlyphBoundRect(rDev, rText, aGlyphRect);
228 if (!bSuccess)
229 SAL_WARN("starmath", "Ooops... (Font missing?)");
231 nItalicLeftSpace = GetLeft() - aGlyphRect.Left() + nBorderWidth;
232 nItalicRightSpace = aGlyphRect.Right() - GetRight() + nBorderWidth;
233 if (nItalicLeftSpace < 0 && !bAllowSmaller)
234 nItalicLeftSpace = 0;
235 if (nItalicRightSpace < 0 && !bAllowSmaller)
236 nItalicRightSpace = 0;
238 tools::Long nDist = 0;
239 if (pFormat)
240 nDist = (rDev.GetFont().GetFontSize().Height()
241 * pFormat->GetDistance(DIS_ORNAMENTSIZE)) / 100;
243 nHiAttrFence = aGlyphRect.TopLeft().Y() - 1 - nBorderWidth - nDist;
244 nLoAttrFence = SmFromTo(GetAlignB(), GetBottom(), 0.0);
246 nGlyphTop = aGlyphRect.Top() - nBorderWidth;
247 nGlyphBottom = aGlyphRect.Bottom() + nBorderWidth;
249 if (bAllowSmaller)
251 // for symbols and operators from the StarMath Font
252 // we adjust upper and lower margin of the symbol
253 SetTop(nGlyphTop);
254 SetBottom(nGlyphBottom);
257 if (nHiAttrFence < GetTop())
258 nHiAttrFence = GetTop();
260 if (nLoAttrFence > GetBottom())
261 nLoAttrFence = GetBottom();
263 OSL_ENSURE(rText.isEmpty() || !IsEmpty(),
264 "Sm: empty rectangle created");
268 SmRect::SmRect(tools::Long nWidth, tools::Long nHeight)
269 // this constructor should never be used for anything textlike because
270 // it will not provide useful values for baseline, AlignT and AlignB!
271 // It's purpose is to get a 'SmRect' for the horizontal line in fractions
272 // as used in 'SmBinVerNode'.
273 : aTopLeft(0, 0)
274 , aSize(nWidth, nHeight)
275 , nBaseline(0)
276 , nItalicLeftSpace(0)
277 , nItalicRightSpace(0)
278 , nBorderWidth(0)
279 , bHasBaseline(false)
280 , bHasAlignInfo(true)
282 nAlignT = nGlyphTop = nHiAttrFence = GetTop();
283 nAlignB = nGlyphBottom = nLoAttrFence = GetBottom();
284 nAlignM = (nAlignT + nAlignB) / 2; // this is the default
288 void SmRect::SetLeft(tools::Long nLeft)
290 if (nLeft <= GetRight())
291 { aSize.setWidth( GetRight() - nLeft + 1 );
292 aTopLeft.setX( nLeft );
297 void SmRect::SetRight(tools::Long nRight)
299 if (nRight >= GetLeft())
300 aSize.setWidth( nRight - GetLeft() + 1 );
304 void SmRect::SetBottom(tools::Long nBottom)
306 if (nBottom >= GetTop())
307 aSize.setHeight( nBottom - GetTop() + 1 );
311 void SmRect::SetTop(tools::Long nTop)
313 if (nTop <= GetBottom())
314 { aSize.setHeight( GetBottom() - nTop + 1 );
315 aTopLeft.setY( nTop );
320 void SmRect::Move(const Point &rPosition)
321 // move rectangle by position 'rPosition'.
323 aTopLeft += rPosition;
325 tools::Long nDelta = rPosition.Y();
326 nBaseline += nDelta;
327 nAlignT += nDelta;
328 nAlignM += nDelta;
329 nAlignB += nDelta;
330 nGlyphTop += nDelta;
331 nGlyphBottom += nDelta;
332 nHiAttrFence += nDelta;
333 nLoAttrFence += nDelta;
337 Point SmRect::AlignTo(const SmRect &rRect, RectPos ePos,
338 RectHorAlign eHor, RectVerAlign eVer) const
339 { Point aPos (GetTopLeft());
340 // will become the topleft point of the new rectangle position
342 // set horizontal or vertical new rectangle position depending on ePos
343 switch (ePos)
344 { case RectPos::Left:
345 aPos.setX( rRect.GetItalicLeft() - GetItalicRightSpace()
346 - GetWidth() );
347 break;
348 case RectPos::Right:
349 aPos.setX( rRect.GetItalicRight() + 1 + GetItalicLeftSpace() );
350 break;
351 case RectPos::Top:
352 aPos.setY( rRect.GetTop() - GetHeight() );
353 break;
354 case RectPos::Bottom:
355 aPos.setY( rRect.GetBottom() + 1 );
356 break;
357 case RectPos::Attribute:
358 aPos.setX( rRect.GetItalicCenterX() - GetItalicWidth() / 2
359 + GetItalicLeftSpace() );
360 break;
361 default:
362 assert(false);
365 // check if horizontal position is already set
366 if (ePos == RectPos::Left || ePos == RectPos::Right || ePos == RectPos::Attribute)
367 // correct error in current vertical position
368 switch (eVer)
369 { case RectVerAlign::Top :
370 aPos.AdjustY(rRect.GetAlignT() - GetAlignT() );
371 break;
372 case RectVerAlign::Mid :
373 aPos.AdjustY(rRect.GetAlignM() - GetAlignM() );
374 break;
375 case RectVerAlign::Baseline :
376 // align baselines if possible else align mid's
377 if (HasBaseline() && rRect.HasBaseline())
378 aPos.AdjustY(rRect.GetBaseline() - GetBaseline() );
379 else
380 aPos.AdjustY(rRect.GetAlignM() - GetAlignM() );
381 break;
382 case RectVerAlign::Bottom :
383 aPos.AdjustY(rRect.GetAlignB() - GetAlignB() );
384 break;
385 case RectVerAlign::CenterY :
386 aPos.AdjustY(rRect.GetCenterY() - GetCenterY() );
387 break;
388 case RectVerAlign::AttributeHi:
389 aPos.AdjustY(rRect.GetHiAttrFence() - GetBottom() );
390 break;
391 case RectVerAlign::AttributeMid :
392 aPos.AdjustY(SmFromTo(rRect.GetAlignB(), rRect.GetAlignT(), 0.4)
393 - GetCenterY() );
394 break;
395 case RectVerAlign::AttributeLo :
396 aPos.AdjustY(rRect.GetLoAttrFence() - GetTop() );
397 break;
398 default :
399 assert(false);
402 // check if vertical position is already set
403 if (ePos == RectPos::Top || ePos == RectPos::Bottom)
404 // correct error in current horizontal position
405 switch (eHor)
406 { case RectHorAlign::Left:
407 aPos.AdjustX(rRect.GetItalicLeft() - GetItalicLeft() );
408 break;
409 case RectHorAlign::Center:
410 aPos.AdjustX(rRect.GetItalicCenterX() - GetItalicCenterX() );
411 break;
412 case RectHorAlign::Right:
413 aPos.AdjustX(rRect.GetItalicRight() - GetItalicRight() );
414 break;
415 default:
416 assert(false);
419 return aPos;
423 void SmRect::Union(const SmRect &rRect)
424 // rectangle union of current one with 'rRect'. The result is to be the
425 // smallest rectangles that covers the space of both rectangles.
426 // (empty rectangles cover no space)
427 //! Italic correction is NOT taken into account here!
429 if (rRect.IsEmpty())
430 return;
432 tools::Long nL = rRect.GetLeft(),
433 nR = rRect.GetRight(),
434 nT = rRect.GetTop(),
435 nB = rRect.GetBottom(),
436 nGT = rRect.nGlyphTop,
437 nGB = rRect.nGlyphBottom;
438 if (!IsEmpty())
439 { tools::Long nTmp;
441 if ((nTmp = GetLeft()) < nL)
442 nL = nTmp;
443 if ((nTmp = GetRight()) > nR)
444 nR = nTmp;
445 if ((nTmp = GetTop()) < nT)
446 nT = nTmp;
447 if ((nTmp = GetBottom()) > nB)
448 nB = nTmp;
449 if ((nTmp = nGlyphTop) < nGT)
450 nGT = nTmp;
451 if ((nTmp = nGlyphBottom) > nGB)
452 nGB = nTmp;
455 SetLeft(nL);
456 SetRight(nR);
457 SetTop(nT);
458 SetBottom(nB);
459 nGlyphTop = nGT;
460 nGlyphBottom = nGB;
464 SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode)
465 // let current rectangle be the union of itself and 'rRect'
466 // (the smallest rectangle surrounding both). Also adapt values for
467 // 'AlignT', 'AlignM', 'AlignB', baseline and italic-spaces.
468 // The baseline is set according to 'eCopyMode'.
469 // If one of the rectangles has no relevant info the other one is copied.
471 // get some values used for (italic) spaces adaptation
472 // ! (need to be done before changing current SmRect) !
473 tools::Long nL = std::min(GetItalicLeft(), rRect.GetItalicLeft()),
474 nR = std::max(GetItalicRight(), rRect.GetItalicRight());
476 Union(rRect);
478 SetItalicSpaces(GetLeft() - nL, nR - GetRight());
480 if (!HasAlignInfo())
481 CopyAlignInfo(rRect);
482 else if (rRect.HasAlignInfo())
484 assert(HasAlignInfo());
485 nAlignT = std::min(GetAlignT(), rRect.GetAlignT());
486 nAlignB = std::max(GetAlignB(), rRect.GetAlignB());
487 nHiAttrFence = std::min(GetHiAttrFence(), rRect.GetHiAttrFence());
488 nLoAttrFence = std::max(GetLoAttrFence(), rRect.GetLoAttrFence());
490 switch (eCopyMode)
491 { case RectCopyMBL::This:
492 // already done
493 break;
494 case RectCopyMBL::Arg:
495 CopyMBL(rRect);
496 break;
497 case RectCopyMBL::None:
498 bHasBaseline = false;
499 nAlignM = (nAlignT + nAlignB) / 2;
500 break;
501 case RectCopyMBL::Xor:
502 if (!HasBaseline())
503 CopyMBL(rRect);
504 break;
505 default :
506 assert(false);
510 return *this;
514 void SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode,
515 tools::Long nNewAlignM)
516 // as 'ExtendBy' but sets AlignM value to 'nNewAlignM'.
517 // (this version will be used in 'SmBinVerNode' to provide means to
518 // align eg "{a over b} over c" correctly where AlignM should not
519 // be (AlignT + AlignB) / 2)
521 OSL_ENSURE(HasAlignInfo(), "Sm: no align info");
523 ExtendBy(rRect, eCopyMode);
524 nAlignM = nNewAlignM;
528 SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode,
529 bool bKeepVerAlignParams)
530 // as 'ExtendBy' but keeps original values for AlignT, -M and -B and
531 // baseline.
532 // (this is used in 'SmSupSubNode' where the sub-/supscripts shouldn't
533 // be allowed to modify these values.)
535 tools::Long nOldAlignT = GetAlignT(),
536 nOldAlignM = GetAlignM(),
537 nOldAlignB = GetAlignB(),
538 nOldBaseline = nBaseline; //! depends not on 'HasBaseline'
539 bool bOldHasAlignInfo = HasAlignInfo();
541 ExtendBy(rRect, eCopyMode);
543 if (bKeepVerAlignParams)
544 { nAlignT = nOldAlignT;
545 nAlignM = nOldAlignM;
546 nAlignB = nOldAlignB;
547 nBaseline = nOldBaseline;
548 bHasAlignInfo = bOldHasAlignInfo;
551 return *this;
555 tools::Long SmRect::OrientedDist(const Point &rPoint) const
556 // return oriented distance of rPoint to the current rectangle,
557 // especially the return value is <= 0 iff the point is inside the
558 // rectangle.
559 // For simplicity the maximum-norm is used.
561 bool bIsInside = IsInsideItalicRect(rPoint);
563 // build reference point to define the distance
564 Point aRef;
565 if (bIsInside)
566 { Point aIC (GetItalicCenterX(), GetCenterY());
568 aRef.setX( rPoint.X() >= aIC.X() ? GetItalicRight() : GetItalicLeft() );
569 aRef.setY( rPoint.Y() >= aIC.Y() ? GetBottom() : GetTop() );
571 else
573 // x-coordinate
574 if (rPoint.X() > GetItalicRight())
575 aRef.setX( GetItalicRight() );
576 else if (rPoint.X() < GetItalicLeft())
577 aRef.setX( GetItalicLeft() );
578 else
579 aRef.setX( rPoint.X() );
580 // y-coordinate
581 if (rPoint.Y() > GetBottom())
582 aRef.setY( GetBottom() );
583 else if (rPoint.Y() < GetTop())
584 aRef.setY( GetTop() );
585 else
586 aRef.setY( rPoint.Y() );
589 // build distance vector
590 Point aDist (aRef - rPoint);
592 tools::Long nAbsX = std::abs(aDist.X()),
593 nAbsY = std::abs(aDist.Y());
595 return bIsInside ? - std::min(nAbsX, nAbsY) : std::max (nAbsX, nAbsY);
599 bool SmRect::IsInsideRect(const Point &rPoint) const
601 return rPoint.Y() >= GetTop()
602 && rPoint.Y() <= GetBottom()
603 && rPoint.X() >= GetLeft()
604 && rPoint.X() <= GetRight();
608 bool SmRect::IsInsideItalicRect(const Point &rPoint) const
610 return rPoint.Y() >= GetTop()
611 && rPoint.Y() <= GetBottom()
612 && rPoint.X() >= GetItalicLeft()
613 && rPoint.X() <= GetItalicRight();
616 SmRect SmRect::AsGlyphRect() const
618 SmRect aRect (*this);
619 aRect.SetTop(nGlyphTop);
620 aRect.SetBottom(nGlyphBottom);
621 return aRect;
624 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */