summaryrefslogtreecommitdiffstats
path: root/svgio/source/svgreader
diff options
context:
space:
mode:
Diffstat (limited to 'svgio/source/svgreader')
-rw-r--r--svgio/source/svgreader/SvgNumber.cxx5
-rw-r--r--svgio/source/svgreader/svgcharacternode.cxx115
-rw-r--r--svgio/source/svgreader/svgdocumenthandler.cxx83
-rw-r--r--svgio/source/svgreader/svgmarkernode.cxx2
-rw-r--r--svgio/source/svgreader/svgnode.cxx36
-rw-r--r--svgio/source/svgreader/svgstyleattributes.cxx80
-rw-r--r--svgio/source/svgreader/svgtspannode.cxx5
7 files changed, 208 insertions, 118 deletions
diff --git a/svgio/source/svgreader/SvgNumber.cxx b/svgio/source/svgreader/SvgNumber.cxx
index c1558f3e64..72a44dbdd0 100644
--- a/svgio/source/svgreader/SvgNumber.cxx
+++ b/svgio/source/svgreader/SvgNumber.cxx
@@ -35,10 +35,11 @@ double SvgNumber::solveNonPercentage(const InfoProvider& rInfoProvider) const
switch (meUnit)
{
+ // See https://drafts.csswg.org/css-values-4/#font-relative-length
case SvgUnit::em:
- return mfNumber * rInfoProvider.getCurrentFontSizeInherited();
+ return mfNumber * rInfoProvider.getCurrentFontSize();
case SvgUnit::ex:
- return mfNumber * rInfoProvider.getCurrentXHeightInherited() * 0.5;
+ return mfNumber * rInfoProvider.getCurrentXHeight();
case SvgUnit::px:
return mfNumber;
case SvgUnit::pt:
diff --git a/svgio/source/svgreader/svgcharacternode.cxx b/svgio/source/svgreader/svgcharacternode.cxx
index 02bc3911a3..0aea1976c1 100644
--- a/svgio/source/svgreader/svgcharacternode.cxx
+++ b/svgio/source/svgreader/svgcharacternode.cxx
@@ -138,11 +138,11 @@ namespace svgio::svgreader
{
// prepare retval, index and length
rtl::Reference<BasePrimitive2D> pRetval;
- sal_uInt32 nLength(getText().getLength());
+ const sal_uInt32 nLength(getText().getLength());
if(nLength)
{
- sal_uInt32 nIndex(0);
+ const sal_uInt32 nIndex(0);
// prepare FontAttribute
const drawinglayer::attribute::FontAttribute aFontAttribute(getFontAttribute(rSvgStyleAttributes));
@@ -154,9 +154,12 @@ namespace svgio::svgreader
// prepare locale
css::lang::Locale aLocale;
- // prepare TextLayouterDevice
+ // prepare TextLayouterDevice; use a larger font size for more linear size
+ // calculations. Similar to nTextSizeFactor in sd/source/ui/view/sdview.cxx
+ // (ViewRedirector::createRedirectedPrimitive2DSequence).
+ const double sizeFactor = fFontHeight < 50000 ? 50000 / fFontHeight : 1.0;
TextLayouterDevice aTextLayouterDevice;
- aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
+ aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth * sizeFactor, fFontHeight * sizeFactor, aLocale);
// prepare TextArray
::std::vector< double > aTextArray(rSvgTextPosition.getX());
@@ -190,13 +193,13 @@ namespace svgio::svgreader
{
fComulativeDx += aDxArray[a];
}
- aTextArray.push_back(aExtendArray[a] + fStartX + fComulativeDx);
+ aTextArray.push_back(aExtendArray[a] / sizeFactor + fStartX + fComulativeDx);
}
}
// get current TextPosition and TextWidth in units
basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition());
- double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength));
+ double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength) / sizeFactor);
// check for user-given TextLength
if(0.0 != rSvgTextPosition.getTextLength()
@@ -209,7 +212,10 @@ namespace svgio::svgreader
// spacing, need to create and expand TextArray
if(aTextArray.empty())
{
- aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength);
+ auto aExtendArray(aTextLayouterDevice.getTextArray(getText(), nIndex, nLength));
+ aTextArray.reserve(aExtendArray.size());
+ for (auto n : aExtendArray)
+ aTextArray.push_back(n / sizeFactor);
}
for(auto &a : aTextArray)
@@ -289,12 +295,12 @@ namespace svgio::svgreader
case DominantBaseline::Middle:
case DominantBaseline::Central:
{
- aPosition.setY(aPosition.getY() - aRange.getCenterY());
+ aPosition.setY(aPosition.getY() - aRange.getCenterY() / sizeFactor);
break;
}
case DominantBaseline::Hanging:
{
- aPosition.setY(aPosition.getY() - aRange.getMinY());
+ aPosition.setY(aPosition.getY() - aRange.getMinY() / sizeFactor);
break;
}
default: // DominantBaseline::Auto
@@ -312,12 +318,12 @@ namespace svgio::svgreader
{
case BaselineShift::Sub:
{
- aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset());
+ aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset() / sizeFactor);
break;
}
case BaselineShift::Super:
{
- aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset());
+ aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset() / sizeFactor);
break;
}
case BaselineShift::Percentage:
@@ -480,53 +486,80 @@ namespace svgio::svgreader
}
}
- void SvgCharacterNode::whiteSpaceHandling()
+ SvgCharacterNode*
+ SvgCharacterNode::whiteSpaceHandling(SvgCharacterNode* pPreviousCharacterNode)
{
bool bIsDefault(XmlSpace::Default == getXmlSpace());
// if xml:space="default" then remove all newline characters, otherwise convert them to space
// convert tab to space too
- maText = maTextBeforeSpaceHandling = maText.replaceAll(u"\n", bIsDefault ? u"" : u" ").replaceAll(u"\t", u" ");
+ maText = maText.replaceAll(u"\n", bIsDefault ? u"" : u" ").replaceAll(u"\t", u" ");
- if(bIsDefault)
+ if (!bIsDefault)
{
- // strip of all leading and trailing spaces
- // and consolidate contiguous space
- maText = consolidateContiguousSpace(maText.trim());
- }
- }
+ if (maText.isEmpty())
+ {
+ // Ignore this empty node for the purpose of whitespace handling
+ return pPreviousCharacterNode;
+ }
- SvgCharacterNode* SvgCharacterNode::addGap(SvgCharacterNode* pPreviousCharacterNode)
- {
- // maText may have lost all text. If that's the case, ignore as invalid character node
- // Also ignore if maTextBeforeSpaceHandling just have spaces
- if(!maText.isEmpty() && !o3tl::trim(maTextBeforeSpaceHandling).empty())
- {
- if(pPreviousCharacterNode)
+ if (pPreviousCharacterNode && pPreviousCharacterNode->mbHadTrailingSpace)
{
- bool bAddGap(true);
+ // pPreviousCharacterNode->mbHadTrailingSpace implies its xml:space="default".
+ // Even if this xml:space="preserve" node is whitespace-only, the trailing space
+ // of the previous node is significant - restore it
+ pPreviousCharacterNode->maText += " ";
+ }
+
+ return this;
+ }
- // Do not add a gap if last node doesn't end with a space and
- // current note doesn't start with a space
- const sal_uInt32 nLastLength(pPreviousCharacterNode->maTextBeforeSpaceHandling.getLength());
- if(pPreviousCharacterNode->maTextBeforeSpaceHandling[nLastLength - 1] != ' ' && maTextBeforeSpaceHandling[0] != ' ')
- bAddGap = false;
+ bool bHadLeadingSpace = maText.startsWith(" ");
+ mbHadTrailingSpace = maText.endsWith(" "); // Only set for xml:space="default"
- // Do not add a gap if this node and last node are in different lines
- if(pPreviousCharacterNode->mpParentLine != mpParentLine)
- bAddGap = false;
+ // strip of all leading and trailing spaces
+ // and consolidate contiguous space
+ maText = consolidateContiguousSpace(maText.trim());
- // add in-between whitespace (single space) to the beginning of the current character node
- if(bAddGap)
+ if (pPreviousCharacterNode)
+ {
+ if (pPreviousCharacterNode->mbHadTrailingSpace)
+ {
+ // pPreviousCharacterNode->mbHadTrailingSpace implies its xml:space="default".
+ // The previous node already has a pending trailing space.
+ if (maText.isEmpty())
{
- maText = " " + maText;
+ // Leading spaces in this empty node are insignificant.
+ // Ignore this empty node for the purpose of whitespace handling
+ return pPreviousCharacterNode;
}
+ // The previous node's trailing space is significant - restore it. Note that
+ // it is incorrect to insert a space in this node instead: the spaces in
+ // different nodes may have different size
+ pPreviousCharacterNode->maText += " ";
+ return this;
}
- // this becomes the previous character node
- return this;
+ if (bHadLeadingSpace)
+ {
+ // This possibly whitespace-only xml:space="default" node goes after another
+ // node either having xml:space="default", but without a trailing space; or
+ // having xml:space="preserve" (in that case, it's irrelevant if that node had
+ // any trailing spaces).
+ if (!maText.isEmpty())
+ {
+ // The leading whitespace in this node is significant - restore it
+ maText = " " + maText;
+ }
+ // The trailing whitespace in this node may or may not be
+ // significant (it will be significant, if there will be more nodes). Keep it as
+ // it is (even empty), but return this, to participate in whitespace handling
+ return this;
+ }
}
- return pPreviousCharacterNode;
+ // No previous node, or no leading/trailing space on the previous node's boundary: if
+ // this is whitespace-only, its whitespace is never significant
+ return maText.isEmpty() ? pPreviousCharacterNode : this;
}
void SvgCharacterNode::concatenate(std::u16string_view rText)
diff --git a/svgio/source/svgreader/svgdocumenthandler.cxx b/svgio/source/svgreader/svgdocumenthandler.cxx
index 8d2cc8849c..5e6e0d4048 100644
--- a/svgio/source/svgreader/svgdocumenthandler.cxx
+++ b/svgio/source/svgreader/svgdocumenthandler.cxx
@@ -66,7 +66,46 @@ namespace svgio::svgreader
namespace
{
- svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode const * pNode, svgio::svgreader::SvgTspanNode* pParentLine, svgio::svgreader::SvgCharacterNode* pLast)
+using CharacterNodeHandlerFunc
+ = svgio::svgreader::SvgCharacterNode*(svgio::svgreader::SvgCharacterNode* pCharNode,
+ svgio::svgreader::SvgTspanNode* pParentLine,
+ svgio::svgreader::SvgCharacterNode* pLast);
+ // clean whitespace in text span
+ svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgCharacterNode* pCharNode,
+ svgio::svgreader::SvgTspanNode* pParentLine,
+ svgio::svgreader::SvgCharacterNode* pLast)
+ {
+ pCharNode->setParentLine(pParentLine);
+ return pCharNode->whiteSpaceHandling(pLast);
+ }
+
+ // set correct widths of text lines
+ svgio::svgreader::SvgCharacterNode* calcTextLineWidths(svgio::svgreader::SvgCharacterNode* pCharNode,
+ svgio::svgreader::SvgTspanNode* pParentLine,
+ svgio::svgreader::SvgCharacterNode* /*pLast*/)
+ {
+ if (const SvgStyleAttributes* pSvgStyleAttributes = pCharNode->getSvgStyleAttributes())
+ {
+ const drawinglayer::attribute::FontAttribute aFontAttribute(
+ svgio::svgreader::SvgCharacterNode::getFontAttribute(*pSvgStyleAttributes));
+
+ double fFontWidth(pSvgStyleAttributes->getFontSizeNumber().solve(*pCharNode));
+ double fFontHeight(fFontWidth);
+
+ css::lang::Locale aLocale;
+ drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
+ aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
+ double fTextWidth = aTextLayouterDevice.getTextWidth(pCharNode->getText(), 0.0,
+ pCharNode->getText().getLength());
+ pParentLine->concatenateTextLineWidth(fTextWidth);
+ }
+ return nullptr; // no pLast handling
+ }
+
+ svgio::svgreader::SvgCharacterNode* walkRecursive(svgio::svgreader::SvgNode const* pNode,
+ svgio::svgreader::SvgTspanNode* pParentLine,
+ svgio::svgreader::SvgCharacterNode* pLast,
+ CharacterNodeHandlerFunc* pHandlerFunc)
{
if(pNode)
{
@@ -83,34 +122,9 @@ namespace
{
case SVGToken::Character:
{
- // clean whitespace in text span
svgio::svgreader::SvgCharacterNode* pCharNode = static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate);
- pCharNode->setParentLine(pParentLine);
-
- pCharNode->whiteSpaceHandling();
- pLast = pCharNode->addGap(pLast);
-
- double fTextWidth(0.0);
-
- const SvgStyleAttributes* pSvgStyleAttributes = pCharNode->getSvgStyleAttributes();
-
- if(pSvgStyleAttributes)
- {
- const drawinglayer::attribute::FontAttribute aFontAttribute(
- svgio::svgreader::SvgCharacterNode::getFontAttribute(*pSvgStyleAttributes));
-
- double fFontWidth(pSvgStyleAttributes->getFontSizeNumber().solve(*pCharNode));
- double fFontHeight(fFontWidth);
-
- css::lang::Locale aLocale;
-
- drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
- aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
- fTextWidth = aTextLayouterDevice.getTextWidth(pCharNode->getText(), 0.0, pCharNode->getText().getLength());
- }
-
- pParentLine->concatenateTextLineWidth(fTextWidth);
+ pLast = pHandlerFunc(pCharNode, pParentLine, pLast);
break;
}
case SVGToken::Tspan:
@@ -121,15 +135,15 @@ namespace
if(!pTspanNode->getX().empty() || !pTspanNode->getY().empty())
pParentLine = pTspanNode;
- // recursively clean whitespaces in subhierarchy
- pLast = whiteSpaceHandling(pCandidate, pParentLine, pLast);
+ // recursively handle subhierarchy
+ pLast = walkRecursive(pCandidate, pParentLine, pLast, pHandlerFunc);
break;
}
case SVGToken::TextPath:
case SVGToken::Tref:
{
- // recursively clean whitespaces in subhierarchy
- pLast = whiteSpaceHandling(pCandidate, pParentLine, pLast);
+ // recursively handle subhierarchy
+ pLast = walkRecursive(pCandidate, pParentLine, pLast, pHandlerFunc);
break;
}
default:
@@ -516,7 +530,12 @@ namespace
if(pTextNode)
{
// cleanup read strings
- whiteSpaceHandling(pTextNode, static_cast< SvgTspanNode*>(pTextNode), nullptr);
+ // First pass: handle whitespace. This works in a way that handling a following
+ // node may append a space to a previous node; so correct line width calculation
+ // may only happen after this pass finishes
+ walkRecursive(pTextNode, static_cast<SvgTspanNode*>(pTextNode), nullptr, whiteSpaceHandling);
+ // Second pass: calculate line widths
+ walkRecursive(pTextNode, static_cast<SvgTspanNode*>(pTextNode), nullptr, calcTextLineWidths);
}
}
diff --git a/svgio/source/svgreader/svgmarkernode.cxx b/svgio/source/svgreader/svgmarkernode.cxx
index 083471b49c..2279920634 100644
--- a/svgio/source/svgreader/svgmarkernode.cxx
+++ b/svgio/source/svgreader/svgmarkernode.cxx
@@ -174,7 +174,7 @@ namespace svgio::svgreader
const drawinglayer::primitive2d::Primitive2DContainer& SvgMarkerNode::getMarkerPrimitives() const
{
- if(aPrimitives.empty() && Display::None != getDisplay())
+ if(Display::None != getDisplay())
{
decomposeSvgNode(const_cast< SvgMarkerNode* >(this)->aPrimitives, true);
}
diff --git a/svgio/source/svgreader/svgnode.cxx b/svgio/source/svgreader/svgnode.cxx
index 7a45c681f1..8fdc29eab7 100644
--- a/svgio/source/svgreader/svgnode.cxx
+++ b/svgio/source/svgreader/svgnode.cxx
@@ -691,45 +691,23 @@ namespace {
}
}
- double SvgNode::getCurrentFontSizeInherited() const
- {
- if(getParent())
- {
- return getParent()->getCurrentFontSize();
- }
- else
- {
- return 0.0;
- }
- }
-
double SvgNode::getCurrentFontSize() const
{
if(getSvgStyleAttributes())
return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::xcoordinate);
- return getCurrentFontSizeInherited();
- }
-
- double SvgNode::getCurrentXHeightInherited() const
- {
if(getParent())
- {
- return getParent()->getCurrentXHeight();
- }
- else
- {
- return 0.0;
- }
+ return getParent()->getCurrentFontSize();
+
+ return 0.0;
}
double SvgNode::getCurrentXHeight() const
{
- if(getSvgStyleAttributes())
- // for XHeight, use FontSize currently
- return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::ycoordinate);
-
- return getCurrentXHeightInherited();
+ // https://drafts.csswg.org/css-values-4/#ex
+ // for XHeight, use 0.5em fallback currently
+ // FIXME: use "x-height of the first available font"
+ return getCurrentFontSize() * 0.5;
}
void SvgNode::setId(OUString const & rId)
diff --git a/svgio/source/svgreader/svgstyleattributes.cxx b/svgio/source/svgreader/svgstyleattributes.cxx
index 58bdb9add8..42e19c51fc 100644
--- a/svgio/source/svgreader/svgstyleattributes.cxx
+++ b/svgio/source/svgreader/svgstyleattributes.cxx
@@ -817,6 +817,16 @@ namespace svgio::svgreader
rMarkerTransform.identity();
rClipRange.reset();
+ // Set the current fill to the marker before calling getMarkerPrimitives,
+ // which calls decomposeSvgNode to decompose the children of the marker.
+ // If any of the children uses 'fill="context-fill"', then it will use it
+ const_cast<SvgStyleAttributes*>(rMarker.getSvgStyleAttributes())->maContextFill = getFill();
+
+ // Set the current stroke to the marker before calling getMarkerPrimitives,
+ // which calls decomposeSvgNode to decompose the children of the marker.
+ // If any of the children uses 'stroke="context-stroke"', then it will use it
+ const_cast<SvgStyleAttributes*>(rMarker.getSvgStyleAttributes())->maContextStroke = getStroke();
+
// get marker primitive representation
rMarkerPrimitives = rMarker.getMarkerPrimitives();
@@ -1287,8 +1297,12 @@ namespace svgio::svgreader
maBaselineShift(BaselineShift::Baseline),
maBaselineShiftNumber(0),
maDominantBaseline(DominantBaseline::Auto),
- maResolvingParent(32, 0),
- mbStrokeDasharraySet(false)
+ maResolvingParent(34, 0),
+ mbStrokeDasharraySet(false),
+ mbContextFill(false),
+ mbContextStroke(false),
+ maContextFill(nullptr),
+ maContextStroke(nullptr)
{
}
@@ -1308,7 +1322,11 @@ namespace svgio::svgreader
OUString aURL;
SvgNumber aOpacity;
- if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"context-fill"))
+ {
+ mbContextFill = true;
+ }
+ else if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
{
setFill(aSvgPaint);
if(aOpacity.isSet())
@@ -1353,7 +1371,11 @@ namespace svgio::svgreader
OUString aURL;
SvgNumber aOpacity;
- if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"context-stroke"))
+ {
+ mbContextStroke = true;
+ }
+ else if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
{
maStroke = aSvgPaint;
if(aOpacity.isSet())
@@ -1996,6 +2018,40 @@ namespace svgio::svgreader
}
}
+ const basegfx::BColor* SvgStyleAttributes::getContextFill() const
+ {
+ if (SVGToken::Marker == mrOwner.getType())
+ return maContextFill;
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+ if (pSvgStyleAttributes && maResolvingParent[33] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[33];
+ auto ret = pSvgStyleAttributes->getContextFill();
+ --maResolvingParent[33];
+ return ret;
+ }
+
+ return nullptr;
+ }
+
+ const basegfx::BColor* SvgStyleAttributes::getContextStroke() const
+ {
+ if (SVGToken::Marker == mrOwner.getType())
+ return maContextStroke;
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+ if (pSvgStyleAttributes && maResolvingParent[32] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[32];
+ auto ret = pSvgStyleAttributes->getContextStroke();
+ --maResolvingParent[32];
+ return ret;
+ }
+
+ return nullptr;
+ }
+
bool SvgStyleAttributes::isClipPathContent() const
{
if (SVGToken::ClipPathNode == mrOwner.getType())
@@ -2064,6 +2120,10 @@ namespace svgio::svgreader
}
}
}
+ else if (mbContextFill)
+ {
+ return getContextFill();
+ }
else if (maNodeFillURL.isEmpty())
{
const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
@@ -2109,6 +2169,10 @@ namespace svgio::svgreader
return &maStroke.getBColor();
}
}
+ else if (mbContextStroke)
+ {
+ return getContextStroke();
+ }
else if (maNodeStrokeURL.isEmpty())
{
const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
@@ -2642,11 +2706,11 @@ namespace svgio::svgreader
if(pSvgStyleAttributes)
{
const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
+ double n = aParentNumber.getNumber() * maFontSizeNumber.getNumber();
+ if (SvgUnit::ex == maFontSizeNumber.getUnit())
+ n *= 0.5; // FIXME: use "x-height of the first available font"
- return SvgNumber(
- aParentNumber.getNumber() * maFontSizeNumber.getNumber(),
- aParentNumber.getUnit(),
- true);
+ return SvgNumber(n, aParentNumber.getUnit());
}
}
diff --git a/svgio/source/svgreader/svgtspannode.cxx b/svgio/source/svgreader/svgtspannode.cxx
index 27d714e66a..4e97c2bfc4 100644
--- a/svgio/source/svgreader/svgtspannode.cxx
+++ b/svgio/source/svgreader/svgtspannode.cxx
@@ -141,11 +141,6 @@ namespace svgio::svgreader
}
}
- double SvgTspanNode::getCurrentFontSize() const
- {
- return getCurrentFontSizeInherited();
- }
-
} // end of namespace svgio::svgreader
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */