/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include namespace drawinglayer::primitive2d { void TextDecoratedPortionPrimitive2D::impCreateGeometryContent( Primitive2DContainer& rTarget, basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose const & rDecTrans, const OUString& rText, sal_Int32 nTextPosition, sal_Int32 nTextLength, const std::vector< double >& rDXArray, const std::vector< sal_Bool >& rKashidaArray, const attribute::FontAttribute& rFontAttribute) const { // create the SimpleTextPrimitive needed in any case rTarget.push_back( new TextSimplePortionPrimitive2D( rDecTrans.getB2DHomMatrix(), rText, nTextPosition, nTextLength, std::vector(rDXArray), std::vector(rKashidaArray), rFontAttribute, getLocale(), getFontColor())); // create and add decoration const Primitive2DContainer& rDecorationGeometryContent( getOrCreateDecorationGeometryContent( rDecTrans, rText, nTextPosition, nTextLength, rDXArray)); rTarget.insert(rTarget.end(), rDecorationGeometryContent.begin(), rDecorationGeometryContent.end()); } const Primitive2DContainer& TextDecoratedPortionPrimitive2D::getOrCreateDecorationGeometryContent( basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose const & rDecTrans, const OUString& rText, sal_Int32 nTextPosition, sal_Int32 nTextLength, const std::vector< double >& rDXArray) const { // see if something else needs to be done const bool bOverlineUsed(TEXT_LINE_NONE != getFontOverline()); const bool bUnderlineUsed(TEXT_LINE_NONE != getFontUnderline()); const bool bStrikeoutUsed(TEXT_STRIKEOUT_NONE != getTextStrikeout()); const bool bEmphasisMarkUsed(TEXT_FONT_EMPHASIS_MARK_NONE != getTextEmphasisMark() && (getEmphasisMarkAbove() || getEmphasisMarkBelow())); if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed || bEmphasisMarkUsed)) { // not used, return empty Primitive2DContainer return maBufferedDecorationGeometry; } if (!maBufferedDecorationGeometry.empty()) { // if not empty it is used -> append and return Primitive2DContainer return maBufferedDecorationGeometry; } // common preparations - create TextLayouterDevice primitive2d::TextLayouterDevice aTextLayouter; createTextLayouter(aTextLayouter); // get text width double fTextWidth(0.0); if(rDXArray.empty()) { fTextWidth = aTextLayouter.getTextWidth(rText, nTextPosition, nTextLength); } else { fTextWidth = rDXArray.back() * rDecTrans.getScale().getX(); const double fFontScaleX(rDecTrans.getScale().getX()); if(!basegfx::fTools::equal(fFontScaleX, 1.0) && !basegfx::fTools::equalZero(fFontScaleX)) { // need to take FontScaling out of the DXArray fTextWidth /= fFontScaleX; } } if(bOverlineUsed) { // for Relief we have to manipulate the OverlineColor basegfx::BColor aOverlineColor(getOverlineColor()); if (hasTextRelief() && COL_BLACK.getBColor() == aOverlineColor) aOverlineColor = COL_WHITE.getBColor(); // create primitive geometry for overline maBufferedDecorationGeometry.push_back( new TextLinePrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, aTextLayouter.getOverlineOffset(), aTextLayouter.getOverlineHeight(), getFontOverline(), aOverlineColor)); } if(bUnderlineUsed) { // for Relief we have to manipulate the TextlineColor basegfx::BColor aTextlineColor(getTextlineColor()); if (hasTextRelief() && COL_BLACK.getBColor() == aTextlineColor) aTextlineColor = COL_WHITE.getBColor(); // create primitive geometry for underline maBufferedDecorationGeometry.push_back( new TextLinePrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, aTextLayouter.getUnderlineOffset(), aTextLayouter.getUnderlineHeight(), getFontUnderline(), aTextlineColor)); } if(bStrikeoutUsed) { // for Relief we have to manipulate the FontColor basegfx::BColor aFontColor(getFontColor()); if (hasTextRelief() && COL_BLACK.getBColor() == aFontColor) aFontColor = COL_WHITE.getBColor(); // create primitive geometry for strikeout if(TEXT_STRIKEOUT_SLASH == getTextStrikeout() || TEXT_STRIKEOUT_X == getTextStrikeout()) { // strikeout with character const sal_Unicode aStrikeoutChar(TEXT_STRIKEOUT_SLASH == getTextStrikeout() ? '/' : 'X'); maBufferedDecorationGeometry.push_back( new TextCharacterStrikeoutPrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, aFontColor, aStrikeoutChar, getFontAttribute(), getLocale())); } else { // strikeout with geometry maBufferedDecorationGeometry.push_back( new TextGeometryStrikeoutPrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, aFontColor, aTextLayouter.getUnderlineHeight(), aTextLayouter.getStrikeoutOffset(), getTextStrikeout())); } } if (bEmphasisMarkUsed) { // create primitives for EmphasisMark visualization - we need a SalLayout std::unique_ptr pSalLayout(createSalLayout(aTextLayouter)); if (pSalLayout) { // for Relief we have to manipulate the FontColor basegfx::BColor aFontColor(getFontColor()); if (hasTextRelief() && COL_BLACK.getBColor() == aFontColor) aFontColor = COL_WHITE.getBColor(); // placeholders for repeated content, only created once Primitive2DReference aShape; Primitive2DReference aRect1; Primitive2DReference aRect2; // space to collect primitives for EmphasisMark Primitive2DContainer aEmphasisContent; // callback collector will produce geometry already scaled, so // prepare local transform without FontScale const basegfx::B2DHomMatrix aObjTransformWithoutScale( basegfx::utils::createShearXRotateTranslateB2DHomMatrix( rDecTrans.getShearX(), rDecTrans.getRotate(), rDecTrans.getTranslate())); // the callback from OutputDevice::createEmphasisMarks providing the data // for each EmphasisMark auto aEmphasisCallback([&aShape, &aRect1, &aRect2, &aEmphasisContent, &aObjTransformWithoutScale, &aFontColor]( const basegfx::B2DPoint& rOutPoint, const basegfx::B2DPolyPolygon& rShape, bool isPolyLine, const tools::Rectangle& rRect1, const tools::Rectangle& rRect2) { // prepare complete ObjectTransform const basegfx::B2DHomMatrix aTransform( aObjTransformWithoutScale * basegfx::utils::createTranslateB2DHomMatrix(rOutPoint)); if (rShape.count()) { // create PolyPolygon if provided if (!aShape) { if (isPolyLine) aShape = new PolyPolygonHairlinePrimitive2D(rShape, aFontColor); else aShape = new PolyPolygonColorPrimitive2D(rShape, aFontColor); } aEmphasisContent.push_back( new TransformPrimitive2D( aTransform, Primitive2DContainer { aShape } )); } if (!rRect1.IsEmpty()) { // create Rectangle1 if provided if (!aRect1) aRect1 = new FilledRectanglePrimitive2D( basegfx::B2DRange(rRect1.Left(), rRect1.Top(), rRect1.Right(), rRect1.Bottom()), aFontColor); aEmphasisContent.push_back( new TransformPrimitive2D( aTransform, Primitive2DContainer { aRect1 } )); } if (!rRect2.IsEmpty()) { // create Rectangle2 if provided if (!aRect2) aRect2 = new FilledRectanglePrimitive2D( basegfx::B2DRange(rRect2.Left(), rRect2.Top(), rRect2.Right(), rRect2.Bottom()), aFontColor); aEmphasisContent.push_back( new TransformPrimitive2D( aTransform, Primitive2DContainer { aRect2 } )); } }); // call tooling method in vcl to generate the graphic representations aTextLayouter.createEmphasisMarks( *pSalLayout, getTextEmphasisMark(), getEmphasisMarkAbove(), aEmphasisCallback); if (!aEmphasisContent.empty()) { // if we got graphic representations of EmphasisMark, add // them to BufferedDecorationGeometry. Also embed them to // a TextHierarchyEmphasisMarkPrimitive2D GroupPrimitive // to be able to evtl. handle these in a special way maBufferedDecorationGeometry.push_back( new TextHierarchyEmphasisMarkPrimitive2D(std::move(aEmphasisContent))); } } } // append local result and return return maBufferedDecorationGeometry; } const Primitive2DContainer& TextDecoratedPortionPrimitive2D::getOrCreateBrokenUpText() const { if(!getWordLineMode()) { // return empty Primitive2DContainer return maBufferedBrokenUpText; } if (!maBufferedBrokenUpText.empty()) { // if not empty it is used -> return Primitive2DContainer return maBufferedBrokenUpText; } // support for single word mode; split to single word primitives // using TextBreakupHelper TextBreakupHelper aTextBreakupHelper(*this); maBufferedBrokenUpText = aTextBreakupHelper.extractResult(BreakupUnit::Word); return maBufferedBrokenUpText; } Primitive2DReference TextDecoratedPortionPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { if (!getOrCreateBrokenUpText().empty()) { // if BrokenUpText/WordLineMode is used, go into recursion Primitive2DContainer aContent(getOrCreateBrokenUpText()); return new GroupPrimitive2D(std::move(aContent)); } basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(getTextTransform()); Primitive2DContainer aRetval; // create basic geometry such as SimpleTextPrimitive, Overline, Underline, // Strikeout, etc... // prepare new font attributes WITHOUT outline const attribute::FontAttribute aNewFontAttribute( getFontAttribute().getFamilyName(), getFontAttribute().getStyleName(), getFontAttribute().getWeight(), getFontAttribute().getSymbol(), getFontAttribute().getVertical(), getFontAttribute().getItalic(), getFontAttribute().getMonospaced(), false, // no outline anymore, handled locally getFontAttribute().getRTL(), getFontAttribute().getBiDiStrong()); // handle as one word impCreateGeometryContent(aRetval, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), getKashidaArray(), aNewFontAttribute); // Handle Shadow, Outline and TextRelief if(aRetval.empty()) return nullptr; if(hasShadow() || hasTextRelief() || hasOutline()) { Primitive2DReference aShadow; if(hasShadow()) { // create shadow with current content (in aRetval). Text shadow // is constant, relative to font size, rotated with the text and has a // constant color. // shadow parameter values static const double fFactor(1.0 / 24.0); const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor); // see OutputDevice::ImplDrawSpecialText -> no longer simple fixed color const basegfx::BColor aBlack(0.0, 0.0, 0.0); basegfx::BColor aShadowColor(aBlack); if (aBlack == getFontColor() || getFontColor().luminance() < (8.0 / 255.0)) aShadowColor = COL_LIGHTGRAY.getBColor(); // prepare shadow transform matrix const basegfx::B2DHomMatrix aShadowTransform(basegfx::utils::createTranslateB2DHomMatrix( fTextShadowOffset, fTextShadowOffset)); // create shadow primitive aShadow = new ShadowPrimitive2D( aShadowTransform, aShadowColor, 0, // fShadowBlur = 0, there's no blur for text shadow yet. Primitive2DContainer(aRetval)); } if(hasTextRelief()) { // create emboss using an own helper primitive since this will // be view-dependent const basegfx::BColor aBBlack(0.0, 0.0, 0.0); const bool bDefaultTextColor(aBBlack == getFontColor()); TextEffectStyle2D aTextEffectStyle2D(TextEffectStyle2D::ReliefEmbossed); if(bDefaultTextColor) { if(TEXT_RELIEF_ENGRAVED == getTextRelief()) { aTextEffectStyle2D = TextEffectStyle2D::ReliefEngravedDefault; } else { aTextEffectStyle2D = TextEffectStyle2D::ReliefEmbossedDefault; } aRetval = Primitive2DContainer { new TextEffectPrimitive2D( std::move(aRetval), aDecTrans.getTranslate(), aDecTrans.getRotate(), aTextEffectStyle2D) }; } else { // create outline using an own helper primitive since this will // be view-dependent aRetval = Primitive2DContainer { new TextEffectPrimitive2D( std::move(aRetval), aDecTrans.getTranslate(), aDecTrans.getRotate(), TextEffectStyle2D::Outline) }; } aRetval = Primitive2DContainer { Primitive2DReference(new TextEffectPrimitive2D( std::move(aRetval), aDecTrans.getTranslate(), aDecTrans.getRotate(), aTextEffectStyle2D)) }; } else if(hasOutline()) { // create outline using an own helper primitive since this will // be view-dependent aRetval = Primitive2DContainer { Primitive2DReference(new TextEffectPrimitive2D( std::move(aRetval), aDecTrans.getTranslate(), aDecTrans.getRotate(), TextEffectStyle2D::Outline)) }; } if(aShadow.is()) { // put shadow in front if there is one to paint timely before // but placed behind content aRetval.insert(aRetval.begin(), aShadow); } } return new GroupPrimitive2D(std::move(aRetval)); } TextDecoratedPortionPrimitive2D::TextDecoratedPortionPrimitive2D( // TextSimplePortionPrimitive2D parameters const basegfx::B2DHomMatrix& rNewTransform, const OUString& rText, sal_Int32 nTextPosition, sal_Int32 nTextLength, std::vector< double >&& rDXArray, std::vector< sal_Bool >&& rKashidaArray, const attribute::FontAttribute& rFontAttribute, const css::lang::Locale& rLocale, const basegfx::BColor& rFontColor, const Color& rFillColor, // local parameters const basegfx::BColor& rOverlineColor, const basegfx::BColor& rTextlineColor, TextLine eFontOverline, TextLine eFontUnderline, bool bUnderlineAbove, TextStrikeout eTextStrikeout, bool bWordLineMode, TextEmphasisMark eTextEmphasisMark, bool bEmphasisMarkAbove, bool bEmphasisMarkBelow, TextRelief eTextRelief, bool bShadow) : TextSimplePortionPrimitive2D( rNewTransform, rText, nTextPosition, nTextLength, std::move(rDXArray), std::move(rKashidaArray), rFontAttribute, rLocale, rFontColor, rFillColor), maBufferedBrokenUpText(), maBufferedDecorationGeometry(), maOverlineColor(rOverlineColor), maTextlineColor(rTextlineColor), meFontOverline(eFontOverline), meFontUnderline(eFontUnderline), meTextStrikeout(eTextStrikeout), meTextEmphasisMark(eTextEmphasisMark), meTextRelief(eTextRelief), mbUnderlineAbove(bUnderlineAbove), mbWordLineMode(bWordLineMode), mbEmphasisMarkAbove(bEmphasisMarkAbove), mbEmphasisMarkBelow(bEmphasisMarkBelow), mbShadow(bShadow) { } bool TextDecoratedPortionPrimitive2D::hasTextRelief() const { return TEXT_RELIEF_NONE != getTextRelief(); } bool TextDecoratedPortionPrimitive2D::hasShadow() const { // not allowed with TextRelief, else defined in FontAttributes return !hasTextRelief() && getShadow(); } bool TextDecoratedPortionPrimitive2D::hasTextDecoration() const { return TEXT_LINE_NONE != getFontOverline() || TEXT_LINE_NONE != getFontUnderline() || TEXT_STRIKEOUT_NONE != getTextStrikeout() || TEXT_FONT_EMPHASIS_MARK_NONE != getTextEmphasisMark(); } bool TextDecoratedPortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { if(TextSimplePortionPrimitive2D::operator==(rPrimitive)) { const TextDecoratedPortionPrimitive2D& rCompare = static_cast(rPrimitive); return (getOverlineColor() == rCompare.getOverlineColor() && getTextlineColor() == rCompare.getTextlineColor() && getFontOverline() == rCompare.getFontOverline() && getFontUnderline() == rCompare.getFontUnderline() && getTextStrikeout() == rCompare.getTextStrikeout() && getTextEmphasisMark() == rCompare.getTextEmphasisMark() && getTextRelief() == rCompare.getTextRelief() && getUnderlineAbove() == rCompare.getUnderlineAbove() && getWordLineMode() == rCompare.getWordLineMode() && getEmphasisMarkAbove() == rCompare.getEmphasisMarkAbove() && getEmphasisMarkBelow() == rCompare.getEmphasisMarkBelow() && getShadow() == rCompare.getShadow()); } return false; } // #i96475# // Added missing implementation. Decorations may (will) stick out of the text's // inking area, so add them if needed basegfx::B2DRange TextDecoratedPortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const { // check if this needs to be a TextDecoratedPortionPrimitive2D or // if a TextSimplePortionPrimitive2D would be sufficient if (TEXT_LINE_NONE != getFontOverline() || TEXT_LINE_NONE != getFontUnderline() || TEXT_STRIKEOUT_NONE != getTextStrikeout() || TEXT_FONT_EMPHASIS_MARK_NONE != getTextEmphasisMark() || TEXT_RELIEF_NONE != getTextRelief() || getShadow()) { // decoration is used, fallback to BufferedDecompositionPrimitive2D::getB2DRange which uses // the own local decomposition for computation and thus creates all necessary // geometric objects return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); } else { // no relevant decoration used, fallback to TextSimplePortionPrimitive2D::getB2DRange return TextSimplePortionPrimitive2D::getB2DRange(rViewInformation); } } // provide unique ID sal_uInt32 TextDecoratedPortionPrimitive2D::getPrimitive2DID() const { return PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D; } } // end of namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */