cURL: follow redirects
[LibreOffice.git] / starmath / source / rect.cxx
blob6252e5551f5cbd3f01b10e3066695d439d733130
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/svapp.hxx>
22 #include <vcl/wrkwin.hxx>
23 #include <vcl/virdev.hxx>
26 #include "rect.hxx"
27 #include "types.hxx"
28 #include "utility.hxx"
29 #include "smmod.hxx"
31 #include <cassert>
34 // '\0' terminated Array with symbol, which should be treat as letters in
35 // StarMath Font, (to get a normal (non-clipped) SmRect in contrast to the
36 // other operators and symbols).
37 static sal_Unicode const aMathAlpha[] =
39 MS_ALEPH, MS_IM, MS_RE,
40 MS_WP, sal_Unicode(0xE070), MS_EMPTYSET,
41 sal_Unicode(0x2113), sal_Unicode(0xE0D6), sal_Unicode(0x2107),
42 sal_Unicode(0x2127), sal_Unicode(0x210A), MS_HBAR,
43 MS_LAMBDABAR, MS_SETN, MS_SETZ,
44 MS_SETQ, MS_SETR, MS_SETC,
45 sal_Unicode(0x2373), sal_Unicode(0xE0A5), sal_Unicode(0x2112),
46 sal_Unicode(0x2130), sal_Unicode(0x2131),
47 sal_Unicode('\0')
50 bool SmIsMathAlpha(const OUString &rText)
51 // true iff symbol (from StarMath Font) should be treated as letter
53 if (rText.isEmpty())
54 return false;
56 OSL_ENSURE(rText.getLength() == 1, "Sm : string must be exactly one character long");
57 sal_Unicode cChar = rText[0];
59 // is it a greek symbol?
60 if (sal_Unicode(0xE0AC) <= cChar && cChar <= sal_Unicode(0xE0D4))
61 return true;
62 else
64 // appears it in 'aMathAlpha'?
65 const sal_Unicode *pChar = aMathAlpha;
66 while (*pChar && *pChar != cChar)
67 pChar++;
68 return *pChar != '\0';
73 // SmRect members
76 SmRect::SmRect()
77 // constructs empty rectangle at (0, 0) with width and height 0.
78 : aTopLeft(0, 0)
79 , aSize(0, 0)
80 , nBaseline(0)
81 , nAlignT(0)
82 , nAlignM(0)
83 , nAlignB(0)
84 , nGlyphTop(0)
85 , nGlyphBottom(0)
86 , nItalicLeftSpace(0)
87 , nItalicRightSpace(0)
88 , nLoAttrFence(0)
89 , nHiAttrFence(0)
90 , nBorderWidth(0)
91 , bHasBaseline(false)
92 , bHasAlignInfo(false)
97 SmRect::SmRect(const SmRect &rRect)
98 : aTopLeft(rRect.aTopLeft)
99 , aSize(rRect.aSize)
100 , nBaseline(rRect.nBaseline)
101 , nAlignT(rRect.nAlignT)
102 , nAlignM(rRect.nAlignM)
103 , nAlignB(rRect.nAlignB)
104 , nGlyphTop(rRect.nGlyphTop)
105 , nGlyphBottom(rRect.nGlyphBottom)
106 , nItalicLeftSpace(rRect.nItalicLeftSpace)
107 , nItalicRightSpace(rRect.nItalicRightSpace)
108 , nLoAttrFence(rRect.nLoAttrFence)
109 , nHiAttrFence(rRect.nHiAttrFence)
110 , nBorderWidth(rRect.nBorderWidth)
111 , bHasBaseline(rRect.bHasBaseline)
112 , bHasAlignInfo(rRect.bHasAlignInfo)
117 void SmRect::CopyAlignInfo(const SmRect &rRect)
119 nBaseline = rRect.nBaseline;
120 bHasBaseline = rRect.bHasBaseline;
121 nAlignT = rRect.nAlignT;
122 nAlignM = rRect.nAlignM;
123 nAlignB = rRect.nAlignB;
124 bHasAlignInfo = rRect.bHasAlignInfo;
125 nLoAttrFence = rRect.nLoAttrFence;
126 nHiAttrFence = rRect.nHiAttrFence;
130 void SmRect::BuildRect(const OutputDevice &rDev, const SmFormat *pFormat,
131 const OUString &rText, sal_uInt16 nBorder)
133 OSL_ENSURE(aTopLeft == Point(0, 0), "Sm: Ooops...");
135 aSize = Size(rDev.GetTextWidth(rText), rDev.GetTextHeight());
137 const FontMetric aFM (rDev.GetFontMetric());
138 bool bIsMath = aFM.GetFamilyName().equalsIgnoreAsciiCase( FONTNAME_MATH );
139 bool bAllowSmaller = bIsMath && !SmIsMathAlpha(rText);
140 const long nFontHeight = rDev.GetFont().GetFontSize().Height();
142 nBorderWidth = nBorder;
143 bHasAlignInfo = true;
144 bHasBaseline = true;
145 nBaseline = aFM.GetAscent();
146 nAlignT = nBaseline - nFontHeight * 750L / 1000L;
147 nAlignM = nBaseline - nFontHeight * 121L / 422L;
148 // that's where the horizontal bars of '+', '-', ... are
149 // (1/3 of ascent over baseline)
150 // (121 = 1/3 of 12pt ascent, 422 = 12pt fontheight)
151 nAlignB = nBaseline;
153 // workaround for printer fonts with very small (possible 0 or even
154 // negative(!)) leading
155 if (aFM.GetInternalLeading() < 5 && rDev.GetOutDevType() == OUTDEV_PRINTER)
157 OutputDevice *pWindow = Application::GetDefaultDevice();
159 pWindow->Push(PushFlags::MAPMODE | PushFlags::FONT);
161 pWindow->SetMapMode(rDev.GetMapMode());
162 pWindow->SetFont(rDev.GetFontMetric());
164 long nDelta = pWindow->GetFontMetric().GetInternalLeading();
165 if (nDelta == 0)
166 { // this value approx. fits a Leading of 80 at a
167 // Fontheight of 422 (12pt)
168 nDelta = nFontHeight * 8L / 43;
170 SetTop(GetTop() - nDelta);
172 pWindow->Pop();
175 // get GlyphBoundRect
176 Rectangle aGlyphRect;
177 bool bSuccess = SmGetGlyphBoundRect(rDev, rText, aGlyphRect);
178 if (!bSuccess)
179 SAL_WARN("starmath", "Ooops... (Font missing?)");
181 nItalicLeftSpace = GetLeft() - aGlyphRect.Left() + nBorderWidth;
182 nItalicRightSpace = aGlyphRect.Right() - GetRight() + nBorderWidth;
183 if (nItalicLeftSpace < 0 && !bAllowSmaller)
184 nItalicLeftSpace = 0;
185 if (nItalicRightSpace < 0 && !bAllowSmaller)
186 nItalicRightSpace = 0;
188 long nDist = 0;
189 if (pFormat)
190 nDist = (rDev.GetFont().GetFontSize().Height()
191 * pFormat->GetDistance(DIS_ORNAMENTSIZE)) / 100L;
193 nHiAttrFence = aGlyphRect.TopLeft().Y() - 1 - nBorderWidth - nDist;
194 nLoAttrFence = SmFromTo(GetAlignB(), GetBottom(), 0.0);
196 nGlyphTop = aGlyphRect.Top() - nBorderWidth;
197 nGlyphBottom = aGlyphRect.Bottom() + nBorderWidth;
199 if (bAllowSmaller)
201 // for symbols and operators from the StarMath Font
202 // we adjust upper and lower margin of the symbol
203 SetTop(nGlyphTop);
204 SetBottom(nGlyphBottom);
207 if (nHiAttrFence < GetTop())
208 nHiAttrFence = GetTop();
210 if (nLoAttrFence > GetBottom())
211 nLoAttrFence = GetBottom();
213 OSL_ENSURE(rText.isEmpty() || !IsEmpty(),
214 "Sm: empty rectangle created");
219 SmRect::SmRect(const OutputDevice &rDev, const SmFormat *pFormat,
220 const OUString &rText, long nEBorderWidth)
222 OSL_ENSURE( nEBorderWidth >= 0, "BorderWidth is negative" );
223 if (nEBorderWidth < 0)
224 nEBorderWidth = 0;
225 // get rectangle fitting for drawing 'rText' on OutputDevice 'rDev'
226 BuildRect(rDev, pFormat, rText, sal::static_int_cast<sal_uInt16>(nEBorderWidth));
230 SmRect::SmRect(long nWidth, long nHeight)
231 // this constructor should never be used for anything textlike because
232 // it will not provide useful values for baseline, AlignT and AlignB!
233 // It's purpose is to get a 'SmRect' for the horizontal line in fractions
234 // as used in 'SmBinVerNode'.
235 : aTopLeft(0, 0)
236 , aSize(nWidth, nHeight)
237 , nBaseline(0)
238 , nItalicLeftSpace(0)
239 , nItalicRightSpace(0)
240 , nBorderWidth(0)
241 , bHasBaseline(false)
242 , bHasAlignInfo(true)
244 nAlignT = nGlyphTop = nHiAttrFence = GetTop();
245 nAlignB = nGlyphBottom = nLoAttrFence = GetBottom();
246 nAlignM = (nAlignT + nAlignB) / 2; // this is the default
250 void SmRect::SetLeft(long nLeft)
252 if (nLeft <= GetRight())
253 { aSize.Width() = GetRight() - nLeft + 1;
254 aTopLeft.X() = nLeft;
259 void SmRect::SetRight(long nRight)
261 if (nRight >= GetLeft())
262 aSize.Width() = nRight - GetLeft() + 1;
266 void SmRect::SetBottom(long nBottom)
268 if (nBottom >= GetTop())
269 aSize.Height() = nBottom - GetTop() + 1;
273 void SmRect::SetTop(long nTop)
275 if (nTop <= GetBottom())
276 { aSize.Height() = GetBottom() - nTop + 1;
277 aTopLeft.Y() = nTop;
282 void SmRect::Move(const Point &rPosition)
283 // move rectangle by position 'rPosition'.
285 aTopLeft += rPosition;
287 long nDelta = rPosition.Y();
288 nBaseline += nDelta;
289 nAlignT += nDelta;
290 nAlignM += nDelta;
291 nAlignB += nDelta;
292 nGlyphTop += nDelta;
293 nGlyphBottom += nDelta;
294 nHiAttrFence += nDelta;
295 nLoAttrFence += nDelta;
299 const Point SmRect::AlignTo(const SmRect &rRect, RectPos ePos,
300 RectHorAlign eHor, RectVerAlign eVer) const
301 { Point aPos (GetTopLeft());
302 // will become the topleft point of the new rectangle position
304 // set horizontal or vertical new rectangle position depending on ePos
305 switch (ePos)
306 { case RectPos::Left:
307 aPos.X() = rRect.GetItalicLeft() - GetItalicRightSpace()
308 - GetWidth();
309 break;
310 case RectPos::Right:
311 aPos.X() = rRect.GetItalicRight() + 1 + GetItalicLeftSpace();
312 break;
313 case RectPos::Top:
314 aPos.Y() = rRect.GetTop() - GetHeight();
315 break;
316 case RectPos::Bottom:
317 aPos.Y() = rRect.GetBottom() + 1;
318 break;
319 case RectPos::Attribute:
320 aPos.X() = rRect.GetItalicCenterX() - GetItalicWidth() / 2
321 + GetItalicLeftSpace();
322 break;
323 default:
324 assert(false);
327 // check if horizontal position is already set
328 if (ePos == RectPos::Left || ePos == RectPos::Right || ePos == RectPos::Attribute)
329 // correct error in current vertical position
330 switch (eVer)
331 { case RectVerAlign::Top :
332 aPos.Y() += rRect.GetAlignT() - GetAlignT();
333 break;
334 case RectVerAlign::Mid :
335 aPos.Y() += rRect.GetAlignM() - GetAlignM();
336 break;
337 case RectVerAlign::Baseline :
338 // align baselines if possible else align mid's
339 if (HasBaseline() && rRect.HasBaseline())
340 aPos.Y() += rRect.GetBaseline() - GetBaseline();
341 else
342 aPos.Y() += rRect.GetAlignM() - GetAlignM();
343 break;
344 case RectVerAlign::Bottom :
345 aPos.Y() += rRect.GetAlignB() - GetAlignB();
346 break;
347 case RectVerAlign::CenterY :
348 aPos.Y() += rRect.GetCenterY() - GetCenterY();
349 break;
350 case RectVerAlign::AttributeHi:
351 aPos.Y() += rRect.GetHiAttrFence() - GetBottom();
352 break;
353 case RectVerAlign::AttributeMid :
354 aPos.Y() += SmFromTo(rRect.GetAlignB(), rRect.GetAlignT(), 0.4)
355 - GetCenterY();
356 break;
357 case RectVerAlign::AttributeLo :
358 aPos.Y() += rRect.GetLoAttrFence() - GetTop();
359 break;
360 default :
361 assert(false);
364 // check if vertical position is already set
365 if (ePos == RectPos::Top || ePos == RectPos::Bottom)
366 // correct error in current horizontal position
367 switch (eHor)
368 { case RectHorAlign::Left:
369 aPos.X() += rRect.GetItalicLeft() - GetItalicLeft();
370 break;
371 case RectHorAlign::Center:
372 aPos.X() += rRect.GetItalicCenterX() - GetItalicCenterX();
373 break;
374 case RectHorAlign::Right:
375 aPos.X() += rRect.GetItalicRight() - GetItalicRight();
376 break;
377 default:
378 assert(false);
381 return aPos;
385 void SmRect::Union(const SmRect &rRect)
386 // rectangle union of current one with 'rRect'. The result is to be the
387 // smallest rectangles that covers the space of both rectangles.
388 // (empty rectangles cover no space)
389 //! Italic correction is NOT taken into account here!
391 if (rRect.IsEmpty())
392 return;
394 long nL = rRect.GetLeft(),
395 nR = rRect.GetRight(),
396 nT = rRect.GetTop(),
397 nB = rRect.GetBottom(),
398 nGT = rRect.nGlyphTop,
399 nGB = rRect.nGlyphBottom;
400 if (!IsEmpty())
401 { long nTmp;
403 if ((nTmp = GetLeft()) < nL)
404 nL = nTmp;
405 if ((nTmp = GetRight()) > nR)
406 nR = nTmp;
407 if ((nTmp = GetTop()) < nT)
408 nT = nTmp;
409 if ((nTmp = GetBottom()) > nB)
410 nB = nTmp;
411 if ((nTmp = nGlyphTop) < nGT)
412 nGT = nTmp;
413 if ((nTmp = nGlyphBottom) > nGB)
414 nGB = nTmp;
417 SetLeft(nL);
418 SetRight(nR);
419 SetTop(nT);
420 SetBottom(nB);
421 nGlyphTop = nGT;
422 nGlyphBottom = nGB;
426 SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode)
427 // let current rectangle be the union of itself and 'rRect'
428 // (the smallest rectangle surrounding both). Also adapt values for
429 // 'AlignT', 'AlignM', 'AlignB', baseline and italic-spaces.
430 // The baseline is set according to 'eCopyMode'.
431 // If one of the rectangles has no relevant info the other one is copied.
433 // get some values used for (italic) spaces adaption
434 // ! (need to be done before changing current SmRect) !
435 long nL = std::min(GetItalicLeft(), rRect.GetItalicLeft()),
436 nR = std::max(GetItalicRight(), rRect.GetItalicRight());
438 Union(rRect);
440 SetItalicSpaces(GetLeft() - nL, nR - GetRight());
442 if (!HasAlignInfo())
443 CopyAlignInfo(rRect);
444 else if (rRect.HasAlignInfo())
445 { nAlignT = std::min(GetAlignT(), rRect.GetAlignT());
446 nAlignB = std::max(GetAlignB(), rRect.GetAlignB());
447 nHiAttrFence = std::min(GetHiAttrFence(), rRect.GetHiAttrFence());
448 nLoAttrFence = std::max(GetLoAttrFence(), rRect.GetLoAttrFence());
449 OSL_ENSURE(HasAlignInfo(), "Sm: ooops...");
451 switch (eCopyMode)
452 { case RectCopyMBL::This:
453 // already done
454 break;
455 case RectCopyMBL::Arg:
456 CopyMBL(rRect);
457 break;
458 case RectCopyMBL::None:
459 bHasBaseline = false;
460 nAlignM = (nAlignT + nAlignB) / 2;
461 break;
462 case RectCopyMBL::Xor:
463 if (!HasBaseline())
464 CopyMBL(rRect);
465 break;
466 default :
467 assert(false);
471 return *this;
475 void SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode,
476 long nNewAlignM)
477 // as 'ExtendBy' but sets AlignM value to 'nNewAlignM'.
478 // (this version will be used in 'SmBinVerNode' to provide means to
479 // align eg "{a over b} over c" correctly where AlignM should not
480 // be (AlignT + AlignB) / 2)
482 OSL_ENSURE(HasAlignInfo(), "Sm: no align info");
484 ExtendBy(rRect, eCopyMode);
485 nAlignM = nNewAlignM;
489 SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode,
490 bool bKeepVerAlignParams)
491 // as 'ExtendBy' but keeps original values for AlignT, -M and -B and
492 // baseline.
493 // (this is used in 'SmSupSubNode' where the sub-/supscripts shouldn't
494 // be allowed to modify these values.)
496 long nOldAlignT = GetAlignT(),
497 nOldAlignM = GetAlignM(),
498 nOldAlignB = GetAlignB(),
499 nOldBaseline = nBaseline; //! depends not on 'HasBaseline'
500 bool bOldHasAlignInfo = HasAlignInfo();
502 ExtendBy(rRect, eCopyMode);
504 if (bKeepVerAlignParams)
505 { nAlignT = nOldAlignT;
506 nAlignM = nOldAlignM;
507 nAlignB = nOldAlignB;
508 nBaseline = nOldBaseline;
509 bHasAlignInfo = bOldHasAlignInfo;
512 return *this;
516 long SmRect::OrientedDist(const Point &rPoint) const
517 // return oriented distance of rPoint to the current rectangle,
518 // especially the return value is <= 0 iff the point is inside the
519 // rectangle.
520 // For simplicity the maximum-norm is used.
522 bool bIsInside = IsInsideItalicRect(rPoint);
524 // build reference point to define the distance
525 Point aRef;
526 if (bIsInside)
527 { Point aIC (GetItalicCenterX(), GetCenterY());
529 aRef.X() = rPoint.X() >= aIC.X() ? GetItalicRight() : GetItalicLeft();
530 aRef.Y() = rPoint.Y() >= aIC.Y() ? GetBottom() : GetTop();
532 else
534 // x-coordinate
535 if (rPoint.X() > GetItalicRight())
536 aRef.X() = GetItalicRight();
537 else if (rPoint.X() < GetItalicLeft())
538 aRef.X() = GetItalicLeft();
539 else
540 aRef.X() = rPoint.X();
541 // y-coordinate
542 if (rPoint.Y() > GetBottom())
543 aRef.Y() = GetBottom();
544 else if (rPoint.Y() < GetTop())
545 aRef.Y() = GetTop();
546 else
547 aRef.Y() = rPoint.Y();
550 // build distance vector
551 Point aDist (aRef - rPoint);
553 long nAbsX = labs(aDist.X()),
554 nAbsY = labs(aDist.Y());
556 return bIsInside ? - std::min(nAbsX, nAbsY) : std::max (nAbsX, nAbsY);
560 bool SmRect::IsInsideRect(const Point &rPoint) const
562 return rPoint.Y() >= GetTop()
563 && rPoint.Y() <= GetBottom()
564 && rPoint.X() >= GetLeft()
565 && rPoint.X() <= GetRight();
569 bool SmRect::IsInsideItalicRect(const Point &rPoint) const
571 return rPoint.Y() >= GetTop()
572 && rPoint.Y() <= GetBottom()
573 && rPoint.X() >= GetItalicLeft()
574 && rPoint.X() <= GetItalicRight();
577 SmRect SmRect::AsGlyphRect() const
579 SmRect aRect (*this);
580 aRect.SetTop(nGlyphTop);
581 aRect.SetBottom(nGlyphBottom);
582 return aRect;
585 bool SmGetGlyphBoundRect(const vcl::RenderContext &rDev,
586 const OUString &rText, Rectangle &rRect)
587 // basically the same as 'GetTextBoundRect' (in class 'OutputDevice')
588 // but with a string as argument.
590 // handle special case first
591 if (rText.isEmpty())
593 rRect.SetEmpty();
594 return true;
597 // get a device where 'OutputDevice::GetTextBoundRect' will be successful
598 OutputDevice *pGlyphDev;
599 if (rDev.GetOutDevType() != OUTDEV_PRINTER)
600 pGlyphDev = const_cast<OutputDevice *>(&rDev);
601 else
603 // since we format for the printer (where GetTextBoundRect will fail)
604 // we need a virtual device here.
605 pGlyphDev = &SM_MOD()->GetDefaultVirtualDev();
608 const FontMetric aDevFM (rDev.GetFontMetric());
610 pGlyphDev->Push(PushFlags::FONT | PushFlags::MAPMODE);
611 vcl::Font aFnt(rDev.GetFont());
612 aFnt.SetAlignment(ALIGN_TOP);
614 // use scale factor when calling GetTextBoundRect to counter
615 // negative effects from antialiasing which may otherwise result
616 // in significant incorrect bounding rectangles for some characters.
617 Size aFntSize = aFnt.GetFontSize();
619 // Workaround to avoid HUGE font sizes and resulting problems
620 long nScaleFactor = 1;
621 while( aFntSize.Height() > 2000 * nScaleFactor )
622 nScaleFactor *= 2;
624 aFnt.SetFontSize( Size( aFntSize.Width() / nScaleFactor, aFntSize.Height() / nScaleFactor ) );
625 pGlyphDev->SetFont(aFnt);
627 long nTextWidth = rDev.GetTextWidth(rText);
628 Point aPoint;
629 Rectangle aResult (aPoint, Size(nTextWidth, rDev.GetTextHeight())),
630 aTmp;
632 bool bSuccess = pGlyphDev->GetTextBoundRect(aTmp, rText);
633 OSL_ENSURE( bSuccess, "GetTextBoundRect failed" );
636 if (!aTmp.IsEmpty())
638 aResult = Rectangle(aTmp.Left() * nScaleFactor, aTmp.Top() * nScaleFactor,
639 aTmp.Right() * nScaleFactor, aTmp.Bottom() * nScaleFactor);
640 if (&rDev != pGlyphDev) /* only when rDev is a printer... */
642 long nGDTextWidth = pGlyphDev->GetTextWidth(rText);
643 if (nGDTextWidth != 0 &&
644 nTextWidth != nGDTextWidth)
646 aResult.Right() *= nTextWidth;
647 aResult.Right() /= nGDTextWidth * nScaleFactor;
652 // move rectangle to match possibly different baselines
653 // (because of different devices)
654 long nDelta = aDevFM.GetAscent() - pGlyphDev->GetFontMetric().GetAscent() * nScaleFactor;
655 aResult.Move(0, nDelta);
657 pGlyphDev->Pop();
659 rRect = aResult;
660 return bSuccess;
664 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */