diff options
Diffstat (limited to 'drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx')
-rw-r--r-- | drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx new file mode 100644 index 000000000000..6c60b3f22281 --- /dev/null +++ b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx @@ -0,0 +1,610 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_drawinglayer.hxx" + +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/attribute/strokeattribute.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/i18n/WordType.hpp> +#include <drawinglayer/primitive2d/texteffectprimitive2d.hxx> +#include <drawinglayer/primitive2d/shadowprimitive2d.hxx> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/textstrikeoutprimitive2d.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace drawinglayer +{ + namespace primitive2d + { + void TextDecoratedPortionPrimitive2D::impCreateGeometryContent( + std::vector< Primitive2DReference >& rTarget, + basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans, + const String& rText, + xub_StrLen aTextPosition, + xub_StrLen aTextLength, + const ::std::vector< double >& rDXArray, + const attribute::FontAttribute& rFontAttribute) const + { + // create the SimpleTextPrimitive needed in any case + rTarget.push_back(Primitive2DReference( + new TextSimplePortionPrimitive2D( + rDecTrans.getB2DHomMatrix(), + rText, + aTextPosition, + aTextLength, + rDXArray, + rFontAttribute, + getLocale(), + getFontColor()))); + + // 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()); + + if(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed) + { + // common preparations + TextLayouterDevice aTextLayouter; + + // TextLayouterDevice is needed to get metrics for text decorations like + // underline/strikeout/emphasis marks from it. For setup, the font size is needed + aTextLayouter.setFontAttribute( + getFontAttribute(), + rDecTrans.getScale().getX(), + rDecTrans.getScale().getY(), + getLocale()); + + // get text width + double fTextWidth(0.0); + + if(rDXArray.empty()) + { + fTextWidth = aTextLayouter.getTextWidth(rText, aTextPosition, aTextLength); + } + 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) + { + // create primitive geometry for overline + rTarget.push_back(Primitive2DReference( + new TextLinePrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + aTextLayouter.getOverlineOffset(), + aTextLayouter.getOverlineHeight(), + getFontOverline(), + getOverlineColor()))); + } + + if(bUnderlineUsed) + { + // create primitive geometry for underline + rTarget.push_back(Primitive2DReference( + new TextLinePrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + aTextLayouter.getUnderlineOffset(), + aTextLayouter.getUnderlineHeight(), + getFontUnderline(), + getTextlineColor()))); + } + + if(bStrikeoutUsed) + { + // 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'); + + rTarget.push_back(Primitive2DReference( + new TextCharacterStrikeoutPrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + getFontColor(), + aStrikeoutChar, + getFontAttribute(), + getLocale()))); + } + else + { + // strikeout with geometry + rTarget.push_back(Primitive2DReference( + new TextGeometryStrikeoutPrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + getFontColor(), + aTextLayouter.getUnderlineHeight(), + aTextLayouter.getStrikeoutOffset(), + getTextStrikeout()))); + } + } + } + + // TODO: Handle Font Emphasis Above/Below + } + + void TextDecoratedPortionPrimitive2D::impCorrectTextBoundary(::com::sun::star::i18n::Boundary& rNextWordBoundary) const + { + // truncate aNextWordBoundary to min/max possible values. This is necessary since the word start may be + // before/after getTextPosition() when a long string is the content and getTextPosition() + // is right inside a word. Same for end. + const sal_Int32 aMinPos(static_cast< sal_Int32 >(getTextPosition())); + const sal_Int32 aMaxPos(aMinPos + static_cast< sal_Int32 >(getTextLength())); + + if(rNextWordBoundary.startPos < aMinPos) + { + rNextWordBoundary.startPos = aMinPos; + } + else if(rNextWordBoundary.startPos > aMaxPos) + { + rNextWordBoundary.startPos = aMaxPos; + } + + if(rNextWordBoundary.endPos < aMinPos) + { + rNextWordBoundary.endPos = aMinPos; + } + else if(rNextWordBoundary.endPos > aMaxPos) + { + rNextWordBoundary.endPos = aMaxPos; + } + } + + void TextDecoratedPortionPrimitive2D::impSplitSingleWords( + std::vector< Primitive2DReference >& rTarget, + basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans) const + { + // break iterator support + // made static so it only needs to be fetched once, even with many single + // constructed VclMetafileProcessor2D. It's still incarnated on demand, + // but exists for OOo runtime now by purpose. + static ::com::sun::star::uno::Reference< ::com::sun::star::i18n::XBreakIterator > xLocalBreakIterator; + + if(!xLocalBreakIterator.is()) + { + ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory > xMSF(::comphelper::getProcessServiceFactory()); + xLocalBreakIterator.set(xMSF->createInstance(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("com.sun.star.i18n.BreakIterator"))), ::com::sun::star::uno::UNO_QUERY); + } + + if(xLocalBreakIterator.is() && getTextLength()) + { + // init word iterator, get first word and truncate to possibilities + ::com::sun::star::i18n::Boundary aNextWordBoundary(xLocalBreakIterator->getWordBoundary( + getText(), getTextPosition(), getLocale(), ::com::sun::star::i18n::WordType::ANYWORD_IGNOREWHITESPACES, sal_True)); + + if(aNextWordBoundary.endPos == getTextPosition()) + { + // backward hit, force next word + aNextWordBoundary = xLocalBreakIterator->getWordBoundary( + getText(), getTextPosition() + 1, getLocale(), ::com::sun::star::i18n::WordType::ANYWORD_IGNOREWHITESPACES, sal_True); + } + + impCorrectTextBoundary(aNextWordBoundary); + + // 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()); + + if(aNextWordBoundary.startPos == getTextPosition() && aNextWordBoundary.endPos == getTextLength()) + { + // it IS only a single word, handle as one word + impCreateGeometryContent(rTarget, rDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute); + } + else + { + // prepare TextLayouter + const bool bNoDXArray(getDXArray().empty()); + TextLayouterDevice aTextLayouter; + + if(bNoDXArray) + { + // ..but only completely when no DXArray + aTextLayouter.setFontAttribute( + getFontAttribute(), + rDecTrans.getScale().getX(), + rDecTrans.getScale().getY(), + getLocale()); + } + + // do iterate over single words + while(aNextWordBoundary.startPos != aNextWordBoundary.endPos) + { + // prepare values for new portion + const xub_StrLen nNewTextStart(static_cast< xub_StrLen >(aNextWordBoundary.startPos)); + const xub_StrLen nNewTextEnd(static_cast< xub_StrLen >(aNextWordBoundary.endPos)); + + // prepare transform for the single word + basegfx::B2DHomMatrix aNewTransform; + ::std::vector< double > aNewDXArray; + const bool bNewStartIsNotOldStart(nNewTextStart > getTextPosition()); + + if(!bNoDXArray) + { + // prepare new DXArray for the single word + aNewDXArray = ::std::vector< double >( + getDXArray().begin() + static_cast< sal_uInt32 >(nNewTextStart - getTextPosition()), + getDXArray().begin() + static_cast< sal_uInt32 >(nNewTextEnd - getTextPosition())); + } + + if(bNewStartIsNotOldStart) + { + // needs to be moved to a new start position + double fOffset(0.0); + + if(bNoDXArray) + { + // evaluate using TextLayouter + fOffset = aTextLayouter.getTextWidth(getText(), getTextPosition(), nNewTextStart); + } + else + { + // get from DXArray + const sal_uInt32 nIndex(static_cast< sal_uInt32 >(nNewTextStart - getTextPosition())); + fOffset = getDXArray()[nIndex - 1]; + } + + // need offset without FontScale for building the new transformation. The + // new transformation will be multiplied with the current text transformation + // so FontScale would be double + double fOffsetNoScale(fOffset); + const double fFontScaleX(rDecTrans.getScale().getX()); + + if(!basegfx::fTools::equal(fFontScaleX, 1.0) + && !basegfx::fTools::equalZero(fFontScaleX)) + { + fOffsetNoScale /= fFontScaleX; + } + + // apply needed offset to transformation + aNewTransform.translate(fOffsetNoScale, 0.0); + + if(!bNoDXArray) + { + // DXArray values need to be corrected with the offset, too. Here, + // take the scaled offset since the DXArray is scaled + const sal_uInt32 nArraySize(aNewDXArray.size()); + + for(sal_uInt32 a(0); a < nArraySize; a++) + { + aNewDXArray[a] -= fOffset; + } + } + } + + // add text transformation to new transformation + aNewTransform *= rDecTrans.getB2DHomMatrix(); + + // create geometry content for the single word. Do not forget + // to use the new transformation + basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(aNewTransform); + + impCreateGeometryContent(rTarget, aDecTrans, getText(), nNewTextStart, + nNewTextEnd - nNewTextStart, aNewDXArray, aNewFontAttribute); + + if(aNextWordBoundary.endPos >= getTextPosition() + getTextLength()) + { + // end reached + aNextWordBoundary.startPos = aNextWordBoundary.endPos; + } + else + { + // get new word portion + const sal_Int32 nLastEndPos(aNextWordBoundary.endPos); + + aNextWordBoundary = xLocalBreakIterator->getWordBoundary( + getText(), aNextWordBoundary.endPos, getLocale(), + ::com::sun::star::i18n::WordType::ANYWORD_IGNOREWHITESPACES, sal_True); + + if(nLastEndPos == aNextWordBoundary.endPos) + { + // backward hit, force next word + aNextWordBoundary = xLocalBreakIterator->getWordBoundary( + getText(), nLastEndPos + 1, getLocale(), + ::com::sun::star::i18n::WordType::ANYWORD_IGNOREWHITESPACES, sal_True); + } + + impCorrectTextBoundary(aNextWordBoundary); + } + } + } + } + } + + Primitive2DSequence TextDecoratedPortionPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + std::vector< Primitive2DReference > aNewPrimitives; + basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(getTextTransform()); + Primitive2DSequence aRetval; + + // create basic geometry such as SimpleTextPrimitive, Overline, Underline, + // Strikeout, etc... + if(getWordLineMode()) + { + // support for single word mode + impSplitSingleWords(aNewPrimitives, aDecTrans); + } + else + { + // prepare new font attributes WITHOUT outline + const attribute::FontAttribute aNewFontAttribute( + getFontAttribute().getFamilyName(), + getFontAttribute().getStyleName(), + getFontAttribute().getWeight(), + getFontAttribute().getSymbol(), + getFontAttribute().getVertical(), + getFontAttribute().getItalic(), + false, // no outline anymore, handled locally + getFontAttribute().getRTL(), + getFontAttribute().getBiDiStrong()); + + // handle as one word + impCreateGeometryContent(aNewPrimitives, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute); + } + + // convert to Primitive2DSequence + const sal_uInt32 nMemberCount(aNewPrimitives.size()); + + if(nMemberCount) + { + aRetval.realloc(nMemberCount); + + for(sal_uInt32 a(0); a < nMemberCount; a++) + { + aRetval[a] = aNewPrimitives[a]; + } + } + + // Handle Shadow, Outline and TextRelief + if(aRetval.hasElements()) + { + // outline AND shadow depend on NO TextRelief (see dialog) + const bool bHasTextRelief(TEXT_RELIEF_NONE != getTextRelief()); + const bool bHasShadow(!bHasTextRelief && getShadow()); + const bool bHasOutline(!bHasTextRelief && getFontAttribute().getOutline()); + + if(bHasShadow || bHasTextRelief || bHasOutline) + { + Primitive2DReference aShadow; + + if(bHasShadow) + { + // 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 double fFactor(1.0 / 24.0); + const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor); + static basegfx::BColor aShadowColor(0.3, 0.3, 0.3); + + // preapare shadow transform matrix + const basegfx::B2DHomMatrix aShadowTransform(basegfx::tools::createTranslateB2DHomMatrix( + fTextShadowOffset, fTextShadowOffset)); + + // create shadow primitive + aShadow = Primitive2DReference(new ShadowPrimitive2D( + aShadowTransform, + aShadowColor, + aRetval)); + } + + if(bHasTextRelief) + { + // 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_RELIEF_EMBOSSED); + + if(bDefaultTextColor) + { + if(TEXT_RELIEF_ENGRAVED == getTextRelief()) + { + aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_ENGRAVED_DEFAULT; + } + else + { + aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_EMBOSSED_DEFAULT; + } + } + else + { + if(TEXT_RELIEF_ENGRAVED == getTextRelief()) + { + aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_ENGRAVED; + } + else + { + aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_EMBOSSED; + } + } + + Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( + aRetval, + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + aTextEffectStyle2D)); + aRetval = Primitive2DSequence(&aNewTextEffect, 1); + } + else if(bHasOutline) + { + // create outline using an own helper primitive since this will + // be view-dependent + Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( + aRetval, + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + TEXTEFFECTSTYLE2D_OUTLINE)); + aRetval = Primitive2DSequence(&aNewTextEffect, 1); + } + + if(aShadow.is()) + { + // put shadow in front if there is one to paint timely before + // but placed behind content + const Primitive2DSequence aContent(aRetval); + aRetval = Primitive2DSequence(&aShadow, 1); + appendPrimitive2DSequenceToPrimitive2DSequence(aRetval, aContent); + } + } + } + + return aRetval; + } + + TextDecoratedPortionPrimitive2D::TextDecoratedPortionPrimitive2D( + + // TextSimplePortionPrimitive2D parameters + const basegfx::B2DHomMatrix& rNewTransform, + const String& rText, + xub_StrLen aTextPosition, + xub_StrLen aTextLength, + const ::std::vector< double >& rDXArray, + const attribute::FontAttribute& rFontAttribute, + const ::com::sun::star::lang::Locale& rLocale, + const basegfx::BColor& rFontColor, + + // 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, aTextPosition, aTextLength, rDXArray, rFontAttribute, rLocale, rFontColor), + 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::operator==(const BasePrimitive2D& rPrimitive) const + { + if(TextSimplePortionPrimitive2D::operator==(rPrimitive)) + { + const TextDecoratedPortionPrimitive2D& rCompare = (TextDecoratedPortionPrimitive2D&)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 + { + const bool bDecoratedIsNeeded( + TEXT_LINE_NONE != getFontOverline() + || TEXT_LINE_NONE != getFontUnderline() + || TEXT_STRIKEOUT_NONE != getTextStrikeout() + || TEXT_EMPHASISMARK_NONE != getTextEmphasisMark() + || TEXT_RELIEF_NONE != getTextRelief() + || getShadow()); + + if(bDecoratedIsNeeded) + { + // 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 + ImplPrimitrive2DIDBlock(TextDecoratedPortionPrimitive2D, PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D) + + } // end of namespace primitive2d +} // end of namespace drawinglayer + +////////////////////////////////////////////////////////////////////////////// +// eof + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |