1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
34 bool SmGetGlyphBoundRect(const vcl::RenderContext
&rDev
,
35 const OUString
&rText
, tools::Rectangle
&rRect
)
36 // basically the same as 'GetTextBoundRect' (in class 'OutputDevice')
37 // but with a string as argument.
39 // handle special case first
46 // get a device where 'OutputDevice::GetTextBoundRect' will be successful
47 OutputDevice
*pGlyphDev
;
48 if (rDev
.GetOutDevType() != OUTDEV_PRINTER
)
49 pGlyphDev
= const_cast<OutputDevice
*>(&rDev
);
52 // since we format for the printer (where GetTextBoundRect will fail)
53 // we need a virtual device here.
54 pGlyphDev
= &SmModule::get()->GetDefaultVirtualDev();
57 const FontMetric
aDevFM (rDev
.GetFontMetric());
59 pGlyphDev
->Push(vcl::PushFlags::FONT
| vcl::PushFlags::MAPMODE
);
60 vcl::Font
aFnt(rDev
.GetFont());
61 aFnt
.SetAlignment(ALIGN_TOP
);
63 // use scale factor when calling GetTextBoundRect to counter
64 // negative effects from antialiasing which may otherwise result
65 // in significant incorrect bounding rectangles for some characters.
66 Size aFntSize
= aFnt
.GetFontSize();
68 // Workaround to avoid HUGE font sizes and resulting problems
69 tools::Long nScaleFactor
= 1;
70 while( aFntSize
.Height() > 2000 * nScaleFactor
)
73 aFnt
.SetFontSize( Size( aFntSize
.Width() / nScaleFactor
, aFntSize
.Height() / nScaleFactor
) );
74 pGlyphDev
->SetFont(aFnt
);
76 tools::Long nTextWidth
= rDev
.GetTextWidth(rText
);
77 tools::Rectangle
aResult (Point(), Size(nTextWidth
, rDev
.GetTextHeight())),
80 bool bSuccess
= pGlyphDev
->GetTextBoundRect(aTmp
, rText
);
81 OSL_ENSURE( bSuccess
, "GetTextBoundRect failed" );
86 aResult
= tools::Rectangle(aTmp
.Left() * nScaleFactor
, aTmp
.Top() * nScaleFactor
,
87 aTmp
.Right() * nScaleFactor
, aTmp
.Bottom() * nScaleFactor
);
88 if (&rDev
!= pGlyphDev
) /* only when rDev is a printer... */
90 tools::Long nGDTextWidth
= pGlyphDev
->GetTextWidth(rText
);
91 if (nGDTextWidth
!= 0 &&
92 nTextWidth
!= nGDTextWidth
)
94 aResult
.SetRight( aResult
.Right() * nTextWidth
);
95 aResult
.SetRight( aResult
.Right() / ( nGDTextWidth
* nScaleFactor
) );
100 // move rectangle to match possibly different baselines
101 // (because of different devices)
102 tools::Long nDelta
= aDevFM
.GetAscent() - pGlyphDev
->GetFontMetric().GetAscent() * nScaleFactor
;
103 aResult
.Move(0, nDelta
);
111 bool SmIsMathAlpha(std::u16string_view aText
)
112 // true iff symbol (from StarMath Font) should be treated as letter
114 // Set of symbols, which should be treated as letters in StarMath Font
115 // (to get a normal (non-clipped) SmRect in contrast to the other operators
117 static o3tl::sorted_vector
<sal_Unicode
> const aMathAlpha({
118 MS_ALEPH
, MS_IM
, MS_RE
,
119 MS_WP
, u
'\xE070', MS_EMPTYSET
,
120 u
'\x2113', u
'\xE0D6', u
'\x2107',
121 u
'\x2127', u
'\x210A', MS_HBAR
,
122 MS_LAMBDABAR
, MS_SETN
, MS_SETZ
,
123 MS_SETQ
, MS_SETR
, MS_SETC
,
124 u
'\x2373', u
'\xE0A5', u
'\x2112',
131 OSL_ENSURE(aText
.size() == 1, "Sm : string must be exactly one character long");
132 sal_Unicode cChar
= aText
[0];
134 // is it a greek symbol?
135 if (u
'\xE0AC' <= cChar
&& cChar
<= u
'\xE0D4')
137 // or, does it appear in 'aMathAlpha'?
138 return aMathAlpha
.find(cChar
) != aMathAlpha
.end();
145 // constructs empty rectangle at (0, 0) with width and height 0.
154 , nItalicLeftSpace(0)
155 , nItalicRightSpace(0)
159 , bHasBaseline(false)
160 , bHasAlignInfo(false)
165 void SmRect::CopyAlignInfo(const SmRect
&rRect
)
167 nBaseline
= rRect
.nBaseline
;
168 bHasBaseline
= rRect
.bHasBaseline
;
169 nAlignT
= rRect
.nAlignT
;
170 nAlignM
= rRect
.nAlignM
;
171 nAlignB
= rRect
.nAlignB
;
172 bHasAlignInfo
= rRect
.bHasAlignInfo
;
173 nLoAttrFence
= rRect
.nLoAttrFence
;
174 nHiAttrFence
= rRect
.nHiAttrFence
;
178 SmRect::SmRect(const OutputDevice
&rDev
, const SmFormat
*pFormat
,
179 const OUString
&rText
, sal_uInt16 nBorder
)
180 // get rectangle fitting for drawing 'rText' on OutputDevice 'rDev'
182 , aSize(rDev
.GetTextWidth(rText
), rDev
.GetTextHeight())
184 const FontMetric
aFM (rDev
.GetFontMetric());
185 bool bIsMath
= aFM
.GetFamilyName().equalsIgnoreAsciiCase( FONTNAME_MATH
);
186 bool bAllowSmaller
= bIsMath
&& !SmIsMathAlpha(rText
);
187 const tools::Long nFontHeight
= rDev
.GetFont().GetFontSize().Height();
189 nBorderWidth
= nBorder
;
190 bHasAlignInfo
= true;
192 nBaseline
= aFM
.GetAscent();
193 nAlignT
= nBaseline
- nFontHeight
* 750 / 1000;
194 nAlignM
= nBaseline
- nFontHeight
* 121 / 422;
195 // that's where the horizontal bars of '+', '-', ... are
196 // (1/3 of ascent over baseline)
197 // (121 = 1/3 of 12pt ascent, 422 = 12pt fontheight)
200 // workaround for printer fonts with very small (possible 0 or even
201 // negative(!)) leading
202 if (aFM
.GetInternalLeading() < 5 && rDev
.GetOutDevType() == OUTDEV_PRINTER
)
204 OutputDevice
*pWindow
= Application::GetDefaultDevice();
206 pWindow
->Push(vcl::PushFlags::MAPMODE
| vcl::PushFlags::FONT
);
208 pWindow
->SetMapMode(rDev
.GetMapMode());
209 pWindow
->SetFont(rDev
.GetFontMetric());
211 tools::Long nDelta
= pWindow
->GetFontMetric().GetInternalLeading();
213 { // this value approx. fits a Leading of 80 at a
214 // Fontheight of 422 (12pt)
215 nDelta
= nFontHeight
* 8 / 43;
217 SetTop(GetTop() - nDelta
);
222 // get GlyphBoundRect
223 tools::Rectangle aGlyphRect
;
224 bool bSuccess
= SmGetGlyphBoundRect(rDev
, rText
, aGlyphRect
);
226 SAL_WARN("starmath", "Ooops... (Font missing?)");
228 nItalicLeftSpace
= GetLeft() - aGlyphRect
.Left() + nBorderWidth
;
229 nItalicRightSpace
= aGlyphRect
.Right() - GetRight() + nBorderWidth
;
230 if (nItalicLeftSpace
< 0 && !bAllowSmaller
)
231 nItalicLeftSpace
= 0;
232 if (nItalicRightSpace
< 0 && !bAllowSmaller
)
233 nItalicRightSpace
= 0;
235 tools::Long nDist
= 0;
237 nDist
= (rDev
.GetFont().GetFontSize().Height()
238 * pFormat
->GetDistance(DIS_ORNAMENTSIZE
)) / 100;
240 nHiAttrFence
= aGlyphRect
.Top() - 1 - nBorderWidth
- nDist
;
241 nLoAttrFence
= SmFromTo(GetAlignB(), GetBottom(), 0.0);
243 nGlyphTop
= aGlyphRect
.Top() - nBorderWidth
;
244 nGlyphBottom
= aGlyphRect
.Bottom() + nBorderWidth
;
248 // for symbols and operators from the StarMath Font
249 // we adjust upper and lower margin of the symbol
251 SetBottom(nGlyphBottom
);
254 if (nHiAttrFence
< GetTop())
255 nHiAttrFence
= GetTop();
257 if (nLoAttrFence
> GetBottom())
258 nLoAttrFence
= GetBottom();
260 OSL_ENSURE(rText
.isEmpty() || !IsEmpty(),
261 "Sm: empty rectangle created");
265 SmRect::SmRect(tools::Long nWidth
, tools::Long nHeight
)
266 // this constructor should never be used for anything textlike because
267 // it will not provide useful values for baseline, AlignT and AlignB!
268 // It's purpose is to get a 'SmRect' for the horizontal line in fractions
269 // as used in 'SmBinVerNode'.
271 , aSize(nWidth
, nHeight
)
273 , nItalicLeftSpace(0)
274 , nItalicRightSpace(0)
276 , bHasBaseline(false)
277 , bHasAlignInfo(true)
279 nAlignT
= nGlyphTop
= nHiAttrFence
= GetTop();
280 nAlignB
= nGlyphBottom
= nLoAttrFence
= GetBottom();
281 nAlignM
= (nAlignT
+ nAlignB
) / 2; // this is the default
285 void SmRect::SetLeft(tools::Long nLeft
)
287 if (nLeft
<= GetRight())
288 { aSize
.setWidth( GetRight() - nLeft
+ 1 );
289 aTopLeft
.setX( nLeft
);
294 void SmRect::SetRight(tools::Long nRight
)
296 if (nRight
>= GetLeft())
297 aSize
.setWidth( nRight
- GetLeft() + 1 );
301 void SmRect::SetBottom(tools::Long nBottom
)
303 if (nBottom
>= GetTop())
304 aSize
.setHeight( nBottom
- GetTop() + 1 );
308 void SmRect::SetTop(tools::Long nTop
)
310 if (nTop
<= GetBottom())
311 { aSize
.setHeight( GetBottom() - nTop
+ 1 );
312 aTopLeft
.setY( nTop
);
317 void SmRect::Move(const Point
&rPosition
)
318 // move rectangle by position 'rPosition'.
320 aTopLeft
+= rPosition
;
322 tools::Long nDelta
= rPosition
.Y();
328 nGlyphBottom
+= nDelta
;
329 nHiAttrFence
+= nDelta
;
330 nLoAttrFence
+= nDelta
;
334 Point
SmRect::AlignTo(const SmRect
&rRect
, RectPos ePos
,
335 RectHorAlign eHor
, RectVerAlign eVer
) const
336 { Point
aPos (GetTopLeft());
337 // will become the topleft point of the new rectangle position
339 // set horizontal or vertical new rectangle position depending on ePos
341 { case RectPos::Left
:
342 aPos
.setX( rRect
.GetItalicLeft() - GetItalicRightSpace()
346 aPos
.setX( rRect
.GetItalicRight() + 1 + GetItalicLeftSpace() );
349 aPos
.setY( rRect
.GetTop() - GetHeight() );
351 case RectPos::Bottom
:
352 aPos
.setY( rRect
.GetBottom() + 1 );
354 case RectPos::Attribute
:
355 aPos
.setX( rRect
.GetItalicCenterX() - GetItalicWidth() / 2
356 + GetItalicLeftSpace() );
362 // check if horizontal position is already set
363 if (ePos
== RectPos::Left
|| ePos
== RectPos::Right
|| ePos
== RectPos::Attribute
)
364 // correct error in current vertical position
366 { case RectVerAlign::Top
:
367 aPos
.AdjustY(rRect
.GetAlignT() - GetAlignT() );
369 case RectVerAlign::Mid
:
370 aPos
.AdjustY(rRect
.GetAlignM() - GetAlignM() );
372 case RectVerAlign::Baseline
:
373 // align baselines if possible else align mid's
374 if (HasBaseline() && rRect
.HasBaseline())
375 aPos
.AdjustY(rRect
.GetBaseline() - GetBaseline() );
377 aPos
.AdjustY(rRect
.GetAlignM() - GetAlignM() );
379 case RectVerAlign::Bottom
:
380 aPos
.AdjustY(rRect
.GetAlignB() - GetAlignB() );
382 case RectVerAlign::CenterY
:
383 aPos
.AdjustY(rRect
.GetCenterY() - GetCenterY() );
385 case RectVerAlign::AttributeHi
:
386 aPos
.AdjustY(rRect
.GetHiAttrFence() - GetBottom() );
388 case RectVerAlign::AttributeMid
:
389 aPos
.AdjustY(SmFromTo(rRect
.GetAlignB(), rRect
.GetAlignT(), 0.4)
392 case RectVerAlign::AttributeLo
:
393 aPos
.AdjustY(rRect
.GetLoAttrFence() - GetTop() );
399 // check if vertical position is already set
400 if (ePos
== RectPos::Top
|| ePos
== RectPos::Bottom
)
401 // correct error in current horizontal position
403 { case RectHorAlign::Left
:
404 aPos
.AdjustX(rRect
.GetItalicLeft() - GetItalicLeft() );
406 case RectHorAlign::Center
:
407 aPos
.AdjustX(rRect
.GetItalicCenterX() - GetItalicCenterX() );
409 case RectHorAlign::Right
:
410 aPos
.AdjustX(rRect
.GetItalicRight() - GetItalicRight() );
420 void SmRect::Union(const SmRect
&rRect
)
421 // rectangle union of current one with 'rRect'. The result is to be the
422 // smallest rectangles that covers the space of both rectangles.
423 // (empty rectangles cover no space)
424 //! Italic correction is NOT taken into account here!
429 tools::Long nL
= rRect
.GetLeft(),
430 nR
= rRect
.GetRight(),
432 nB
= rRect
.GetBottom(),
433 nGT
= rRect
.nGlyphTop
,
434 nGB
= rRect
.nGlyphBottom
;
438 if ((nTmp
= GetLeft()) < nL
)
440 if ((nTmp
= GetRight()) > nR
)
442 if ((nTmp
= GetTop()) < nT
)
444 if ((nTmp
= GetBottom()) > nB
)
446 if ((nTmp
= nGlyphTop
) < nGT
)
448 if ((nTmp
= nGlyphBottom
) > nGB
)
461 SmRect
& SmRect::ExtendBy(const SmRect
&rRect
, RectCopyMBL eCopyMode
)
462 // let current rectangle be the union of itself and 'rRect'
463 // (the smallest rectangle surrounding both). Also adapt values for
464 // 'AlignT', 'AlignM', 'AlignB', baseline and italic-spaces.
465 // The baseline is set according to 'eCopyMode'.
466 // If one of the rectangles has no relevant info the other one is copied.
468 // get some values used for (italic) spaces adaptation
469 // ! (need to be done before changing current SmRect) !
470 tools::Long nL
= std::min(GetItalicLeft(), rRect
.GetItalicLeft()),
471 nR
= std::max(GetItalicRight(), rRect
.GetItalicRight());
475 SetItalicSpaces(GetLeft() - nL
, nR
- GetRight());
478 CopyAlignInfo(rRect
);
479 else if (rRect
.HasAlignInfo())
481 assert(HasAlignInfo());
482 nAlignT
= std::min(GetAlignT(), rRect
.GetAlignT());
483 nAlignB
= std::max(GetAlignB(), rRect
.GetAlignB());
484 nHiAttrFence
= std::min(GetHiAttrFence(), rRect
.GetHiAttrFence());
485 nLoAttrFence
= std::max(GetLoAttrFence(), rRect
.GetLoAttrFence());
488 { case RectCopyMBL::This
:
491 case RectCopyMBL::Arg
:
494 case RectCopyMBL::None
:
495 bHasBaseline
= false;
496 nAlignM
= (nAlignT
+ nAlignB
) / 2;
498 case RectCopyMBL::Xor
:
511 void SmRect::ExtendBy(const SmRect
&rRect
, RectCopyMBL eCopyMode
,
512 tools::Long nNewAlignM
)
513 // as 'ExtendBy' but sets AlignM value to 'nNewAlignM'.
514 // (this version will be used in 'SmBinVerNode' to provide means to
515 // align eg "{a over b} over c" correctly where AlignM should not
516 // be (AlignT + AlignB) / 2)
518 OSL_ENSURE(HasAlignInfo(), "Sm: no align info");
520 ExtendBy(rRect
, eCopyMode
);
521 nAlignM
= nNewAlignM
;
525 SmRect
& SmRect::ExtendBy(const SmRect
&rRect
, RectCopyMBL eCopyMode
,
526 bool bKeepVerAlignParams
)
527 // as 'ExtendBy' but keeps original values for AlignT, -M and -B and
529 // (this is used in 'SmSupSubNode' where the sub-/supscripts shouldn't
530 // be allowed to modify these values.)
532 tools::Long nOldAlignT
= GetAlignT(),
533 nOldAlignM
= GetAlignM(),
534 nOldAlignB
= GetAlignB(),
535 nOldBaseline
= nBaseline
; //! depends not on 'HasBaseline'
536 bool bOldHasAlignInfo
= HasAlignInfo();
538 ExtendBy(rRect
, eCopyMode
);
540 if (bKeepVerAlignParams
)
541 { nAlignT
= nOldAlignT
;
542 nAlignM
= nOldAlignM
;
543 nAlignB
= nOldAlignB
;
544 nBaseline
= nOldBaseline
;
545 bHasAlignInfo
= bOldHasAlignInfo
;
552 tools::Long
SmRect::OrientedDist(const Point
&rPoint
) const
553 // return oriented distance of rPoint to the current rectangle,
554 // especially the return value is <= 0 iff the point is inside the
556 // For simplicity the maximum-norm is used.
558 bool bIsInside
= IsInsideItalicRect(rPoint
);
560 // build reference point to define the distance
563 { Point
aIC (GetItalicCenterX(), GetCenterY());
565 aRef
.setX( rPoint
.X() >= aIC
.X() ? GetItalicRight() : GetItalicLeft() );
566 aRef
.setY( rPoint
.Y() >= aIC
.Y() ? GetBottom() : GetTop() );
571 if (rPoint
.X() > GetItalicRight())
572 aRef
.setX( GetItalicRight() );
573 else if (rPoint
.X() < GetItalicLeft())
574 aRef
.setX( GetItalicLeft() );
576 aRef
.setX( rPoint
.X() );
578 if (rPoint
.Y() > GetBottom())
579 aRef
.setY( GetBottom() );
580 else if (rPoint
.Y() < GetTop())
581 aRef
.setY( GetTop() );
583 aRef
.setY( rPoint
.Y() );
586 // build distance vector
587 Point
aDist (aRef
- rPoint
);
589 tools::Long nAbsX
= std::abs(aDist
.X()),
590 nAbsY
= std::abs(aDist
.Y());
592 return bIsInside
? - std::min(nAbsX
, nAbsY
) : std::max (nAbsX
, nAbsY
);
596 bool SmRect::IsInsideRect(const Point
&rPoint
) const
598 return rPoint
.Y() >= GetTop()
599 && rPoint
.Y() <= GetBottom()
600 && rPoint
.X() >= GetLeft()
601 && rPoint
.X() <= GetRight();
605 bool SmRect::IsInsideItalicRect(const Point
&rPoint
) const
607 return rPoint
.Y() >= GetTop()
608 && rPoint
.Y() <= GetBottom()
609 && rPoint
.X() >= GetItalicLeft()
610 && rPoint
.X() <= GetItalicRight();
613 SmRect
SmRect::AsGlyphRect() const
615 SmRect
aRect (*this);
616 aRect
.SetTop(nGlyphTop
);
617 aRect
.SetBottom(nGlyphBottom
);
621 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */