Change-Id: I5efe4b12f753b33a425af7a4164adfe2214c2eb1 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210473 Tested-by: Jenkins Reviewed-by: Khaled Hosny <khaled@libreoffice.org>
374 lines
13 KiB
C++
374 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <node.hxx>
|
|
#include "tmpdevice.hxx"
|
|
|
|
void SmNode::ArrangeOpenType(OutputDevice& rDev, const SmFormat& rFormat)
|
|
{
|
|
// Fallback: Default to legacy arrange if OpenType is not implemented
|
|
Arrange(rDev, rFormat);
|
|
}
|
|
|
|
void SmTableNode::ArrangeOpenType(OutputDevice& rDev, const SmFormat& rFormat)
|
|
// arranges all subnodes in one column
|
|
{
|
|
SmNode* pNode;
|
|
size_t nSize = GetNumSubNodes();
|
|
|
|
// make distance depend on font size
|
|
tools::Long nDist
|
|
= +(rFormat.GetDistance(DIS_VERTICAL) * GetFont().GetFontSize().Height()) / 100;
|
|
|
|
if (nSize < 1)
|
|
return;
|
|
|
|
// arrange subnodes and get maximum width of them
|
|
tools::Long nMaxWidth = 0, nTmp;
|
|
for (size_t i = 0; i < nSize; ++i)
|
|
{
|
|
if (nullptr != (pNode = GetSubNode(i)))
|
|
{
|
|
pNode->ArrangeOpenType(rDev, rFormat);
|
|
if ((nTmp = pNode->GetItalicWidth()) > nMaxWidth)
|
|
nMaxWidth = nTmp;
|
|
}
|
|
}
|
|
|
|
bool bIsTextmode = rFormat.IsTextmode();
|
|
tools::Long nGapMin;
|
|
|
|
if (!bIsTextmode)
|
|
nGapMin = rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::StackDisplayStyleGapMin);
|
|
else
|
|
nGapMin = rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::StackGapMin);
|
|
|
|
if (nSize == 2 && GetSubNode(0) != nullptr && GetSubNode(1) != nullptr)
|
|
{
|
|
SmNode *pNum = GetSubNode(0), *pDenom = GetSubNode(1);
|
|
|
|
// Math Constants for Stack (it's like fractions without thickness) uses in binom.
|
|
tools::Long nNumShiftUp, nDenomShiftDown, nGap, nGapDiff, nGapDelta, nNumAscent,
|
|
nDenomAscent, nNumDescent, nNumBaseline, nDenomBaseline, nMathAscent;
|
|
|
|
if (!bIsTextmode)
|
|
{
|
|
nNumShiftUp = rDev.GetOpenTypeMathConstant(
|
|
vcl::OpenTypeMathConstant::StackTopDisplayStyleShiftUp);
|
|
nDenomShiftDown = rDev.GetOpenTypeMathConstant(
|
|
vcl::OpenTypeMathConstant::StackBottomDisplayStyleShiftDown);
|
|
}
|
|
else
|
|
{
|
|
nNumShiftUp = rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::StackTopShiftUp);
|
|
nDenomShiftDown
|
|
= rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::StackBottomShiftDown);
|
|
}
|
|
|
|
nNumBaseline = pNum->HasBaseline() ? pNum->GetBaseline() : pNum->GetCenterY();
|
|
nDenomBaseline = pDenom->HasBaseline() ? pDenom->GetBaseline() : pDenom->GetCenterY();
|
|
|
|
nNumAscent = nNumBaseline - pNum->GetTop();
|
|
nDenomAscent = nDenomBaseline - pDenom->GetTop();
|
|
nNumDescent = pNum->GetBottom() - nNumBaseline;
|
|
|
|
nGap = nDenomShiftDown - nDenomAscent + nNumShiftUp - nNumDescent;
|
|
if (nGap < nGapMin)
|
|
{
|
|
nGapDiff = nGapMin - nGap;
|
|
nGapDelta = nGapDiff / 2;
|
|
nNumShiftUp += nGapDelta;
|
|
nDenomShiftDown += nGapDiff - nGapDelta;
|
|
}
|
|
|
|
nMathAscent
|
|
= std::max<tools::Long>(nNumShiftUp + nNumAscent, -nDenomShiftDown + nDenomAscent);
|
|
|
|
SmRect aReferenceRect(nMaxWidth, 1);
|
|
|
|
const SmNode* pLM = pNum->GetLeftMost();
|
|
RectHorAlign eHorAlign = pLM->GetRectHorAlign();
|
|
|
|
// move numerator to its position
|
|
Point aPos = pNum->AlignTo(aReferenceRect, RectPos::Top, eHorAlign, RectVerAlign::Top);
|
|
aPos.setY(nMathAscent - nNumShiftUp - nNumAscent);
|
|
pNum->MoveTo(aPos);
|
|
|
|
// get horizontal alignment for denominator
|
|
pLM = pDenom->GetLeftMost();
|
|
eHorAlign = pLM->GetRectHorAlign();
|
|
aPos = pDenom->AlignTo(aReferenceRect, RectPos::Bottom, eHorAlign, RectVerAlign::Top);
|
|
|
|
// move denominator to its position
|
|
aPos.setY(nMathAscent + nDenomShiftDown - nDenomAscent);
|
|
pDenom->MoveTo(aPos);
|
|
|
|
SmRect::operator=(*pNum);
|
|
ExtendBy(*pDenom, RectCopyMBL::None);
|
|
|
|
mnFormulaBaseline = nMathAscent;
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nDist = nGapMin;
|
|
}
|
|
|
|
Point aPos;
|
|
SmRect::operator=(SmRect(nMaxWidth, 1));
|
|
for (size_t i = 0; i < nSize; ++i)
|
|
{
|
|
if (nullptr != (pNode = GetSubNode(i)))
|
|
{
|
|
const SmRect& rNodeRect = pNode->GetRect();
|
|
const SmNode* pCoNode = pNode->GetLeftMost();
|
|
RectHorAlign eHorAlign = pCoNode->GetRectHorAlign();
|
|
|
|
aPos = rNodeRect.AlignTo(*this, RectPos::Bottom, eHorAlign, RectVerAlign::Baseline);
|
|
if (i)
|
|
aPos.AdjustY(nDist);
|
|
pNode->MoveTo(aPos);
|
|
ExtendBy(rNodeRect, nSize > 1 ? RectCopyMBL::None : RectCopyMBL::Arg);
|
|
}
|
|
}
|
|
// #i972#
|
|
if (HasBaseline())
|
|
mnFormulaBaseline = GetBaseline();
|
|
else
|
|
{
|
|
SmTmpDevice aTmpDev(rDev, true);
|
|
aTmpDev.SetFont(GetFont());
|
|
|
|
SmRect aRect(aTmpDev, &rFormat, u"a"_ustr, GetFont().GetBorderWidth());
|
|
mnFormulaBaseline = GetAlignM();
|
|
// move from middle position by constant - distance
|
|
// between middle and baseline for single letter
|
|
mnFormulaBaseline += aRect.GetBaseline() - aRect.GetAlignM();
|
|
}
|
|
}
|
|
|
|
void SmLineNode::ArrangeOpenType(OutputDevice& rDev, const SmFormat& rFormat)
|
|
// arranges all subnodes in one row with some extra space between
|
|
{
|
|
SmNode* pNode;
|
|
size_t nSize = GetNumSubNodes();
|
|
for (size_t i = 0; i < nSize; ++i)
|
|
{
|
|
if (nullptr != (pNode = GetSubNode(i)))
|
|
pNode->ArrangeOpenType(rDev, rFormat);
|
|
}
|
|
|
|
SmTmpDevice aTmpDev(rDev, true);
|
|
aTmpDev.SetFont(GetFont());
|
|
|
|
if (nSize < 1)
|
|
{
|
|
// provide an empty rectangle with alignment parameters for the "current"
|
|
// font (in order to make "a^1 {}_2^3 a_4" work correctly, that is, have the
|
|
// same sub-/supscript positions.)
|
|
//! be sure to use a character that has explicitly defined HiAttribute
|
|
//! line in rect.cxx such as 'a' in order to make 'vec a' look the same as
|
|
//! 'vec {a}'.
|
|
SmRect::operator=(SmRect(aTmpDev, &rFormat, u"a"_ustr, GetFont().GetBorderWidth()));
|
|
// make sure that the rectangle occupies (almost) no space
|
|
SetWidth(1);
|
|
SetItalicSpaces(0, 0);
|
|
return;
|
|
}
|
|
|
|
// make distance depend on font size
|
|
tools::Long nDist
|
|
= (rFormat.GetDistance(DIS_HORIZONTAL) * GetFont().GetFontSize().Height()) / 100;
|
|
if (!IsUseExtraSpaces())
|
|
nDist = 0;
|
|
|
|
Point aPos;
|
|
// copy the first node into LineNode and extend by the others
|
|
if (nullptr != (pNode = GetSubNode(0)))
|
|
SmRect::operator=(pNode->GetRect());
|
|
|
|
for (size_t i = 1; i < nSize; ++i)
|
|
{
|
|
if (nullptr != (pNode = GetSubNode(i)))
|
|
{
|
|
aPos = pNode->AlignTo(*this, RectPos::Right, RectHorAlign::Center,
|
|
RectVerAlign::Baseline);
|
|
|
|
// add horizontal space to the left for each but the first sub node
|
|
aPos.AdjustX(nDist);
|
|
|
|
pNode->MoveTo(aPos);
|
|
ExtendBy(*pNode, RectCopyMBL::Xor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SmBinHorNode::ArrangeOpenType(OutputDevice& rDev, const SmFormat& rFormat)
|
|
{
|
|
SmNode *pLeft = LeftOperand(), *pOper = Symbol(), *pRight = RightOperand();
|
|
assert(pLeft);
|
|
assert(pOper);
|
|
assert(pRight);
|
|
|
|
pOper->SetSize(double(rFormat.GetRelSize(SIZ_OPERATOR)) / 100);
|
|
|
|
pLeft->ArrangeOpenType(rDev, rFormat);
|
|
pOper->ArrangeOpenType(rDev, rFormat);
|
|
pRight->ArrangeOpenType(rDev, rFormat);
|
|
|
|
const SmRect& rOpRect = pOper->GetRect();
|
|
|
|
tools::Long nMul;
|
|
if (o3tl::checked_multiply<tools::Long>(rOpRect.GetWidth(), rFormat.GetDistance(DIS_HORIZONTAL),
|
|
nMul))
|
|
{
|
|
SAL_WARN("starmath", "integer overflow");
|
|
return;
|
|
}
|
|
|
|
tools::Long nDist = nMul / 100;
|
|
|
|
SmRect::operator=(*pLeft);
|
|
|
|
Point aPos;
|
|
aPos = pOper->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline);
|
|
aPos.AdjustX(nDist);
|
|
pOper->MoveTo(aPos);
|
|
ExtendBy(*pOper, RectCopyMBL::Xor);
|
|
|
|
aPos = pRight->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline);
|
|
aPos.AdjustX(nDist);
|
|
|
|
pRight->MoveTo(aPos);
|
|
ExtendBy(*pRight, RectCopyMBL::Xor);
|
|
}
|
|
|
|
void SmBinVerNode::ArrangeOpenType(OutputDevice& rDev, const SmFormat& rFormat)
|
|
{
|
|
SmNode *pNum = GetSubNode(0), *pLine = GetSubNode(1), *pDenom = GetSubNode(2);
|
|
assert(pNum);
|
|
assert(pLine);
|
|
assert(pDenom);
|
|
|
|
bool bIsTextmode = rFormat.IsTextmode();
|
|
if (bIsTextmode)
|
|
{
|
|
double fFraction = double(rFormat.GetRelSize(SIZ_INDEX)) / 100;
|
|
pNum->SetSize(fFraction);
|
|
pLine->SetSize(fFraction);
|
|
pDenom->SetSize(fFraction);
|
|
}
|
|
|
|
pNum->ArrangeOpenType(rDev, rFormat);
|
|
pDenom->ArrangeOpenType(rDev, rFormat);
|
|
|
|
// Describes the amount to shift the numerator/denominator of the fraction when
|
|
// a fraction bar is present. Data is populated from the OpenType MATH table.
|
|
// If the OpenType MATH table is not present fallback values are used.
|
|
// https://w3c.github.io/mathml-core/#fraction-with-nonzero-line-thickness
|
|
|
|
tools::Long nFontHeight, nExtLen, nWidth, nAxisHeight, nNumBaseline, nDenomBaseline,
|
|
nNumDescent, nDenomAscent;
|
|
|
|
nFontHeight = GetFont().GetFontSize().Height();
|
|
nExtLen = nFontHeight * rFormat.GetDistance(DIS_FRACTION) / 100;
|
|
nWidth = std::max(pNum->GetItalicWidth(), pDenom->GetItalicWidth());
|
|
nAxisHeight = rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::AxisHeight);
|
|
nNumBaseline = pNum->HasBaseline() ? pNum->GetBaseline() : pNum->GetCenterY();
|
|
nDenomBaseline = pDenom->HasBaseline() ? pDenom->GetBaseline() : pDenom->GetCenterY();
|
|
|
|
nNumDescent = pNum->GetBottom() - nNumBaseline;
|
|
nDenomAscent = nDenomBaseline - pDenom->GetTop();
|
|
|
|
// We try and read constants to draw the fraction from the OpenType MATH and
|
|
// use fallback values suggested in the MathML Core specification otherwise.
|
|
|
|
tools::Long nThick, nNumShiftUp, nDenomShiftDown, nNumGapMin, nDenomGapMin, nNumDist,
|
|
nDenomDist;
|
|
|
|
nThick = std::round(
|
|
rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::FractionRuleThickness));
|
|
if (!bIsTextmode)
|
|
{
|
|
nNumShiftUp = rDev.GetOpenTypeMathConstant(
|
|
vcl::OpenTypeMathConstant::FractionNumeratorDisplayStyleShiftUp);
|
|
nDenomShiftDown = rDev.GetOpenTypeMathConstant(
|
|
vcl::OpenTypeMathConstant::FractionDenominatorDisplayStyleShiftDown);
|
|
nNumGapMin = rDev.GetOpenTypeMathConstant(
|
|
vcl::OpenTypeMathConstant::FractionNumDisplayStyleGapMin);
|
|
nDenomGapMin = rDev.GetOpenTypeMathConstant(
|
|
vcl::OpenTypeMathConstant::FractionDenomDisplayStyleGapMin);
|
|
}
|
|
else
|
|
{
|
|
nNumShiftUp
|
|
= rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::FractionNumeratorShiftUp);
|
|
nDenomShiftDown
|
|
= rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::FractionDenominatorShiftDown);
|
|
nNumGapMin
|
|
= rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::FractionNumeratorGapMin);
|
|
nDenomGapMin
|
|
= rDev.GetOpenTypeMathConstant(vcl::OpenTypeMathConstant::FractionDenominatorGapMin);
|
|
}
|
|
|
|
nNumDist
|
|
= std::max<tools::Long>(nNumShiftUp, nAxisHeight + nThick / 2 + nNumGapMin + nNumDescent);
|
|
nDenomDist = std::max<tools::Long>(nDenomShiftDown,
|
|
nThick / 2 + nDenomGapMin + nDenomAscent - nAxisHeight);
|
|
|
|
// font specialist advised to change the width first
|
|
pLine->AdaptToY(rDev, nThick);
|
|
pLine->AdaptToX(rDev, nWidth + 2 * nExtLen);
|
|
pLine->ArrangeOpenType(rDev, rFormat);
|
|
|
|
Point aLinePos(pLine->GetLeft(), pLine->GetTop());
|
|
aLinePos.AdjustY(nAxisHeight - nThick / 2);
|
|
pLine->MoveTo(aLinePos);
|
|
|
|
// get horizontal alignment for numerator
|
|
const SmNode* pLM = pNum->GetLeftMost();
|
|
RectHorAlign eHorAlign = pLM->GetRectHorAlign();
|
|
|
|
// move numerator to its position
|
|
Point aPos = pNum->AlignTo(*pLine, RectPos::Top, eHorAlign, RectVerAlign::Top);
|
|
tools::Long nMathAxisY = pLine->GetCenterY();
|
|
|
|
tools::Long nNumBaselineOffset = nNumBaseline - pNum->GetTop();
|
|
aPos.setY(nMathAxisY - (nNumDist - nAxisHeight) - nNumBaselineOffset);
|
|
pNum->MoveTo(aPos);
|
|
|
|
// get horizontal alignment for denominator
|
|
pLM = pDenom->GetLeftMost();
|
|
eHorAlign = pLM->GetRectHorAlign();
|
|
|
|
// move denominator to its position
|
|
aPos = pDenom->AlignTo(*pLine, RectPos::Bottom, eHorAlign, RectVerAlign::Top);
|
|
|
|
tools::Long nDenomBaselineOffset = nDenomBaseline - pDenom->GetTop();
|
|
aPos.setY(nMathAxisY + (nDenomDist + nAxisHeight) - nDenomBaselineOffset);
|
|
pDenom->MoveTo(aPos);
|
|
|
|
SmRect::operator=(*pLine);
|
|
ExtendBy(*pNum, RectCopyMBL::None);
|
|
ExtendBy(*pDenom, RectCopyMBL::None, pLine->GetCenterY());
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|