pyuno: call target only with internal python
[LibreOffice.git] / svgio / source / svgreader / svgcharacternode.cxx
blobca1f91ebb99a030878fcce1a60fb876a6df28e31
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 <svgcharacternode.hxx>
21 #include <svgstyleattributes.hxx>
22 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
23 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
24 #include <drawinglayer/primitive2d/textbreakuphelper.hxx>
25 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
26 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
27 #include <utility>
28 #include <o3tl/string_view.hxx>
29 #include <osl/diagnose.h>
31 using namespace drawinglayer::primitive2d;
33 namespace svgio::svgreader
35 namespace {
37 class localTextBreakupHelper : public TextBreakupHelper
39 private:
40 SvgTextPosition& mrSvgTextPosition;
42 protected:
43 /// allow user callback to allow changes to the new TextTransformation. Default
44 /// does nothing.
45 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
47 public:
48 localTextBreakupHelper(
49 const TextSimplePortionPrimitive2D& rSource,
50 SvgTextPosition& rSvgTextPosition)
51 : TextBreakupHelper(rSource),
52 mrSvgTextPosition(rSvgTextPosition)
59 bool localTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/)
61 const double fRotation(mrSvgTextPosition.consumeRotation());
63 if(0.0 != fRotation)
65 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
67 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
68 rNewTransform.rotate(fRotation);
69 rNewTransform.translate(aBasePoint.getX(), aBasePoint.getY());
72 return true;
75 SvgCharacterNode::SvgCharacterNode(
76 SvgDocument& rDocument,
77 SvgNode* pParent,
78 OUString aText)
79 : SvgNode(SVGToken::Character, rDocument, pParent),
80 maText(std::move(aText)),
81 mpParentLine(nullptr)
85 SvgCharacterNode::~SvgCharacterNode()
89 const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
91 // no own style, use parent's
92 if(getParent())
94 return getParent()->getSvgStyleAttributes();
96 else
98 return nullptr;
102 drawinglayer::attribute::FontAttribute SvgCharacterNode::getFontAttribute(
103 const SvgStyleAttributes& rSvgStyleAttributes)
105 const SvgStringVector& rFontFamilyVector = rSvgStyleAttributes.getFontFamily();
106 OUString aFontFamily(u"Times New Roman"_ustr);
107 if(!rFontFamilyVector.empty())
108 aFontFamily=rFontFamilyVector[0];
110 // #i122324# if the FontFamily name ends on ' embedded' it is probably a re-import
111 // of a SVG export with font embedding. Remove this to make font matching work. This
112 // is pretty safe since there should be no font family names ending on ' embedded'.
113 // Remove again when FontEmbedding is implemented in SVG import
114 if(aFontFamily.endsWith(" embedded"))
116 aFontFamily = aFontFamily.copy(0, aFontFamily.getLength() - 9);
119 const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight()));
120 bool bItalic(FontStyle::italic == rSvgStyleAttributes.getFontStyle() || FontStyle::oblique == rSvgStyleAttributes.getFontStyle());
122 return drawinglayer::attribute::FontAttribute(
123 aFontFamily,
124 OUString(),
125 nFontWeight,
126 false/*bSymbol*/,
127 false/*bVertical*/,
128 bItalic,
129 false/*bMonospaced*/,
130 false/*bOutline*/,
131 false/*bRTL*/,
132 false/*bBiDiStrong*/);
135 rtl::Reference<BasePrimitive2D> SvgCharacterNode::createSimpleTextPrimitive(
136 SvgTextPosition& rSvgTextPosition,
137 const SvgStyleAttributes& rSvgStyleAttributes) const
139 // prepare retval, index and length
140 rtl::Reference<BasePrimitive2D> pRetval;
141 const sal_uInt32 nLength(getText().getLength());
143 if(nLength)
145 const sal_uInt32 nIndex(0);
147 // prepare FontAttribute
148 const drawinglayer::attribute::FontAttribute aFontAttribute(getFontAttribute(rSvgStyleAttributes));
150 // prepare FontSizeNumber
151 double fFontWidth(rSvgStyleAttributes.getFontSizeNumber().solve(*this));
152 double fFontHeight(fFontWidth);
154 // prepare locale
155 css::lang::Locale aLocale;
157 // prepare TextLayouterDevice; use a larger font size for more linear size
158 // calculations. Similar to nTextSizeFactor in sd/source/ui/view/sdview.cxx
159 // (ViewRedirector::createRedirectedPrimitive2DSequence).
160 const double sizeFactor = fFontHeight < 50000 ? 50000 / fFontHeight : 1.0;
161 TextLayouterDevice aTextLayouterDevice;
162 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth * sizeFactor, fFontHeight * sizeFactor, aLocale);
164 // prepare TextArray
165 ::std::vector< double > aTextArray(rSvgTextPosition.getX());
166 ::std::vector< double > aDxArray(rSvgTextPosition.getDx());
168 // Do nothing when X and Dx arrays are empty
169 if((!aTextArray.empty() || !aDxArray.empty()) && aTextArray.size() < nLength)
171 const sal_uInt32 nArray(aTextArray.size());
173 double fStartX(0.0);
174 if (!aTextArray.empty())
176 if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
178 fStartX = rSvgTextPosition.getParent()->getPosition().getX();
180 else
182 fStartX = aTextArray[nArray - 1];
186 ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray));
187 double fComulativeDx(0.0);
189 aTextArray.reserve(nLength);
190 for(size_t a = 0; a < aExtendArray.size(); ++a)
192 if (a < aDxArray.size())
194 fComulativeDx += aDxArray[a];
196 aTextArray.push_back(aExtendArray[a] / sizeFactor + fStartX + fComulativeDx);
200 // get current TextPosition and TextWidth in units
201 basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition());
202 double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength) / sizeFactor);
204 // check for user-given TextLength
205 if(0.0 != rSvgTextPosition.getTextLength()
206 && !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength()))
208 const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth);
210 if(rSvgTextPosition.getLengthAdjust())
212 // spacing, need to create and expand TextArray
213 if(aTextArray.empty())
215 auto aExtendArray(aTextLayouterDevice.getTextArray(getText(), nIndex, nLength));
216 aTextArray.reserve(aExtendArray.size());
217 for (auto n : aExtendArray)
218 aTextArray.push_back(n / sizeFactor);
221 for(auto &a : aTextArray)
223 a *= fFactor;
226 else
228 // spacing and glyphs, just apply to FontWidth
229 fFontWidth *= fFactor;
232 fTextWidth = rSvgTextPosition.getTextLength();
235 // get TextAlign
236 TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign());
238 // map TextAnchor to TextAlign, there seems not to be a difference
239 if(TextAnchor::notset != rSvgStyleAttributes.getTextAnchor())
241 switch(rSvgStyleAttributes.getTextAnchor())
243 case TextAnchor::start:
245 aTextAlign = TextAlign::left;
246 break;
248 case TextAnchor::middle:
250 aTextAlign = TextAlign::center;
251 break;
253 case TextAnchor::end:
255 aTextAlign = TextAlign::right;
256 break;
258 default:
260 break;
265 // apply TextAlign
266 switch(aTextAlign)
268 case TextAlign::right:
270 aPosition.setX(aPosition.getX() - mpParentLine->getTextLineWidth());
271 break;
273 case TextAlign::center:
275 aPosition.setX(aPosition.getX() - (mpParentLine->getTextLineWidth() * 0.5));
276 break;
278 case TextAlign::notset:
279 case TextAlign::left:
280 case TextAlign::justify:
282 // TextAlign::notset, TextAlign::left: nothing to do
283 // TextAlign::justify is not clear currently; handle as TextAlign::left
284 break;
288 // get DominantBaseline
289 const DominantBaseline aDominantBaseline(rSvgStyleAttributes.getDominantBaseline());
291 basegfx::B2DRange aRange(aTextLayouterDevice.getTextBoundRect(getText(), nIndex, nLength));
292 // apply DominantBaseline
293 switch(aDominantBaseline)
295 case DominantBaseline::Middle:
296 case DominantBaseline::Central:
298 aPosition.setY(aPosition.getY() - aRange.getCenterY() / sizeFactor);
299 break;
301 case DominantBaseline::Hanging:
303 aPosition.setY(aPosition.getY() - aRange.getMinY() / sizeFactor);
304 break;
306 default: // DominantBaseline::Auto
308 // nothing to do
309 break;
313 // get BaselineShift
314 const BaselineShift aBaselineShift(rSvgStyleAttributes.getBaselineShift());
316 // apply BaselineShift
317 switch(aBaselineShift)
319 case BaselineShift::Sub:
321 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset() / sizeFactor);
322 break;
324 case BaselineShift::Super:
326 aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset() / sizeFactor);
327 break;
329 case BaselineShift::Percentage:
330 case BaselineShift::Length:
332 const SvgNumber aNumber(rSvgStyleAttributes.getBaselineShiftNumber());
333 const double mfBaselineShift(aNumber.solve(*this));
335 aPosition.setY(aPosition.getY() - mfBaselineShift);
336 break;
338 default: // BaselineShift::Baseline
340 // nothing to do
341 break;
345 // get fill color
346 basegfx::BColor aFill(0, 0, 0);
347 if(rSvgStyleAttributes.getFill())
348 aFill = *rSvgStyleAttributes.getFill();
350 // get fill opacity
351 double fFillOpacity = 1.0;
352 if (rSvgStyleAttributes.getFillOpacity().isSet())
354 fFillOpacity = rSvgStyleAttributes.getFillOpacity().getNumber();
357 // prepare TextTransformation
358 basegfx::B2DHomMatrix aTextTransform;
360 aTextTransform.scale(fFontWidth, fFontHeight);
361 aTextTransform.translate(aPosition.getX(), aPosition.getY());
363 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
364 const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration());
366 if(TextDecoration::underline == aDeco
367 || TextDecoration::overline == aDeco
368 || TextDecoration::line_through == aDeco)
370 // get the fill for decoration as described by SVG. We cannot
371 // have different stroke colors/definitions for those, though
372 const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes();
374 basegfx::BColor aDecoColor(aFill);
375 if(pDecoDef && pDecoDef->getFill())
376 aDecoColor = *pDecoDef->getFill();
378 TextLine eFontOverline = TEXT_LINE_NONE;
379 if(TextDecoration::overline == aDeco)
380 eFontOverline = TEXT_LINE_SINGLE;
382 TextLine eFontUnderline = TEXT_LINE_NONE;
383 if(TextDecoration::underline == aDeco)
384 eFontUnderline = TEXT_LINE_SINGLE;
386 TextStrikeout eTextStrikeout = TEXT_STRIKEOUT_NONE;
387 if(TextDecoration::line_through == aDeco)
388 eTextStrikeout = TEXT_STRIKEOUT_SINGLE;
390 // create decorated text primitive
391 pRetval = new TextDecoratedPortionPrimitive2D(
392 aTextTransform,
393 getText(),
394 nIndex,
395 nLength,
396 std::move(aTextArray),
398 aFontAttribute,
399 std::move(aLocale),
400 aFill,
401 COL_TRANSPARENT,
403 // extra props for decorated
404 aDecoColor,
405 aDecoColor,
406 eFontOverline,
407 eFontUnderline,
408 false,
409 eTextStrikeout,
410 false,
411 TEXT_FONT_EMPHASIS_MARK_NONE,
412 true,
413 false,
414 TEXT_RELIEF_NONE,
415 false);
417 else
419 // create text primitive
420 pRetval = new TextSimplePortionPrimitive2D(
421 aTextTransform,
422 getText(),
423 nIndex,
424 nLength,
425 std::move(aTextArray),
427 aFontAttribute,
428 std::move(aLocale),
429 aFill);
432 if (fFillOpacity != 1.0)
434 pRetval = new UnifiedTransparencePrimitive2D(
435 drawinglayer::primitive2d::Primitive2DContainer{ pRetval },
436 1.0 - fFillOpacity);
439 // advance current TextPosition
440 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
443 return pRetval;
446 void SvgCharacterNode::decomposeTextWithStyle(
447 Primitive2DContainer& rTarget,
448 SvgTextPosition& rSvgTextPosition,
449 const SvgStyleAttributes& rSvgStyleAttributes) const
451 const Primitive2DReference xRef(
452 createSimpleTextPrimitive(
453 rSvgTextPosition,
454 rSvgStyleAttributes));
456 if(!(xRef.is() && (Visibility::visible == rSvgStyleAttributes.getVisibility())))
457 return;
459 if(!rSvgTextPosition.isRotated())
461 rTarget.push_back(xRef);
463 else
465 // need to apply rotations to each character as given
466 const TextSimplePortionPrimitive2D* pCandidate =
467 dynamic_cast< const TextSimplePortionPrimitive2D* >(xRef.get());
469 if(pCandidate)
471 localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
472 Primitive2DContainer aResult = alocalTextBreakupHelper.extractResult();
474 if(!aResult.empty())
476 rTarget.append(std::move(aResult));
479 // also consume for the implied single space
480 rSvgTextPosition.consumeRotation();
482 else
484 OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
489 SvgCharacterNode*
490 SvgCharacterNode::whiteSpaceHandling(SvgCharacterNode* pPreviousCharacterNode)
492 bool bIsDefault(XmlSpace::Default == getXmlSpace());
493 // if xml:space="default" then remove all newline characters, otherwise convert them to space
494 // convert tab to space too
495 maText = maText.replaceAll(u"\n", bIsDefault ? u"" : u" ").replaceAll(u"\t", u" ");
497 if (!bIsDefault)
499 if (maText.isEmpty())
501 // Ignore this empty node for the purpose of whitespace handling
502 return pPreviousCharacterNode;
505 if (pPreviousCharacterNode && pPreviousCharacterNode->mbHadTrailingSpace)
507 // pPreviousCharacterNode->mbHadTrailingSpace implies its xml:space="default".
508 // Even if this xml:space="preserve" node is whitespace-only, the trailing space
509 // of the previous node is significant - restore it
510 pPreviousCharacterNode->maText += " ";
513 return this;
516 bool bHadLeadingSpace = maText.startsWith(" ");
517 mbHadTrailingSpace = maText.endsWith(" "); // Only set for xml:space="default"
519 // strip of all leading and trailing spaces
520 // and consolidate contiguous space
521 maText = consolidateContiguousSpace(maText.trim());
523 if (pPreviousCharacterNode)
525 if (pPreviousCharacterNode->mbHadTrailingSpace)
527 // pPreviousCharacterNode->mbHadTrailingSpace implies its xml:space="default".
528 // The previous node already has a pending trailing space.
529 if (maText.isEmpty())
531 // Leading spaces in this empty node are insignificant.
532 // Ignore this empty node for the purpose of whitespace handling
533 return pPreviousCharacterNode;
535 // The previous node's trailing space is significant - restore it. Note that
536 // it is incorrect to insert a space in this node instead: the spaces in
537 // different nodes may have different size
538 pPreviousCharacterNode->maText += " ";
539 return this;
542 if (bHadLeadingSpace)
544 // This possibly whitespace-only xml:space="default" node goes after another
545 // node either having xml:space="default", but without a trailing space; or
546 // having xml:space="preserve" (in that case, it's irrelevant if that node had
547 // any trailing spaces).
548 if (!maText.isEmpty())
550 // The leading whitespace in this node is significant - restore it
551 maText = " " + maText;
553 // The trailing whitespace in this node may or may not be
554 // significant (it will be significant, if there will be more nodes). Keep it as
555 // it is (even empty), but return this, to participate in whitespace handling
556 return this;
560 // No previous node, or no leading/trailing space on the previous node's boundary: if
561 // this is whitespace-only, its whitespace is never significant
562 return maText.isEmpty() ? pPreviousCharacterNode : this;
565 void SvgCharacterNode::concatenate(std::u16string_view rText)
567 maText += rText;
570 void SvgCharacterNode::decomposeText(Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const
572 if(!getText().isEmpty())
574 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
576 if(pSvgStyleAttributes)
578 decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
583 } // end of namespace svgio
585 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */