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 <vcl/metric.hxx>
22 #include <vcl/svapp.hxx>
23 #include <vcl/virdev.hxx>
24 #include <sal/log.hxx>
32 #include <unordered_set>
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
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
);
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
)
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())),
82 bool bSuccess
= pGlyphDev
->GetTextBoundRect(aTmp
, rText
);
83 OSL_ENSURE( bSuccess
, "GetTextBoundRect failed" );
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
);
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
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',
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')
139 // or, does it appear in 'aMathAlpha'?
140 return aMathAlpha
.find(cChar
) != aMathAlpha
.end();
147 // constructs empty rectangle at (0, 0) with width and height 0.
156 , nItalicLeftSpace(0)
157 , nItalicRightSpace(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'
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;
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)
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();
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
);
224 // get GlyphBoundRect
225 tools::Rectangle aGlyphRect
;
226 bool bSuccess
= SmGetGlyphBoundRect(rDev
, rText
, aGlyphRect
);
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;
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
;
250 // for symbols and operators from the StarMath Font
251 // we adjust upper and lower margin of the symbol
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'.
273 , aSize(nWidth
, nHeight
)
275 , nItalicLeftSpace(0)
276 , nItalicRightSpace(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();
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
343 { case RectPos::Left
:
344 aPos
.setX( rRect
.GetItalicLeft() - GetItalicRightSpace()
348 aPos
.setX( rRect
.GetItalicRight() + 1 + GetItalicLeftSpace() );
351 aPos
.setY( rRect
.GetTop() - GetHeight() );
353 case RectPos::Bottom
:
354 aPos
.setY( rRect
.GetBottom() + 1 );
356 case RectPos::Attribute
:
357 aPos
.setX( rRect
.GetItalicCenterX() - GetItalicWidth() / 2
358 + GetItalicLeftSpace() );
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
368 { case RectVerAlign::Top
:
369 aPos
.AdjustY(rRect
.GetAlignT() - GetAlignT() );
371 case RectVerAlign::Mid
:
372 aPos
.AdjustY(rRect
.GetAlignM() - GetAlignM() );
374 case RectVerAlign::Baseline
:
375 // align baselines if possible else align mid's
376 if (HasBaseline() && rRect
.HasBaseline())
377 aPos
.AdjustY(rRect
.GetBaseline() - GetBaseline() );
379 aPos
.AdjustY(rRect
.GetAlignM() - GetAlignM() );
381 case RectVerAlign::Bottom
:
382 aPos
.AdjustY(rRect
.GetAlignB() - GetAlignB() );
384 case RectVerAlign::CenterY
:
385 aPos
.AdjustY(rRect
.GetCenterY() - GetCenterY() );
387 case RectVerAlign::AttributeHi
:
388 aPos
.AdjustY(rRect
.GetHiAttrFence() - GetBottom() );
390 case RectVerAlign::AttributeMid
:
391 aPos
.AdjustY(SmFromTo(rRect
.GetAlignB(), rRect
.GetAlignT(), 0.4)
394 case RectVerAlign::AttributeLo
:
395 aPos
.AdjustY(rRect
.GetLoAttrFence() - GetTop() );
401 // check if vertical position is already set
402 if (ePos
== RectPos::Top
|| ePos
== RectPos::Bottom
)
403 // correct error in current horizontal position
405 { case RectHorAlign::Left
:
406 aPos
.AdjustX(rRect
.GetItalicLeft() - GetItalicLeft() );
408 case RectHorAlign::Center
:
409 aPos
.AdjustX(rRect
.GetItalicCenterX() - GetItalicCenterX() );
411 case RectHorAlign::Right
:
412 aPos
.AdjustX(rRect
.GetItalicRight() - GetItalicRight() );
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!
431 long nL
= rRect
.GetLeft(),
432 nR
= rRect
.GetRight(),
434 nB
= rRect
.GetBottom(),
435 nGT
= rRect
.nGlyphTop
,
436 nGB
= rRect
.nGlyphBottom
;
440 if ((nTmp
= GetLeft()) < nL
)
442 if ((nTmp
= GetRight()) > nR
)
444 if ((nTmp
= GetTop()) < nT
)
446 if ((nTmp
= GetBottom()) > nB
)
448 if ((nTmp
= nGlyphTop
) < nGT
)
450 if ((nTmp
= 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());
477 SetItalicSpaces(GetLeft() - nL
, nR
- GetRight());
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());
490 { case RectCopyMBL::This
:
493 case RectCopyMBL::Arg
:
496 case RectCopyMBL::None
:
497 bHasBaseline
= false;
498 nAlignM
= (nAlignT
+ nAlignB
) / 2;
500 case RectCopyMBL::Xor
:
513 void SmRect::ExtendBy(const SmRect
&rRect
, RectCopyMBL eCopyMode
,
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
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
;
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
558 // For simplicity the maximum-norm is used.
560 bool bIsInside
= IsInsideItalicRect(rPoint
);
562 // build reference point to define the distance
565 { Point
aIC (GetItalicCenterX(), GetCenterY());
567 aRef
.setX( rPoint
.X() >= aIC
.X() ? GetItalicRight() : GetItalicLeft() );
568 aRef
.setY( rPoint
.Y() >= aIC
.Y() ? GetBottom() : GetTop() );
573 if (rPoint
.X() > GetItalicRight())
574 aRef
.setX( GetItalicRight() );
575 else if (rPoint
.X() < GetItalicLeft())
576 aRef
.setX( GetItalicLeft() );
578 aRef
.setX( rPoint
.X() );
580 if (rPoint
.Y() > GetBottom())
581 aRef
.setY( GetBottom() );
582 else if (rPoint
.Y() < GetTop())
583 aRef
.setY( GetTop() );
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
);
623 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */