1*b1cdbd2cSJim Jagielski /************************************************************** 2*b1cdbd2cSJim Jagielski * 3*b1cdbd2cSJim Jagielski * Licensed to the Apache Software Foundation (ASF) under one 4*b1cdbd2cSJim Jagielski * or more contributor license agreements. See the NOTICE file 5*b1cdbd2cSJim Jagielski * distributed with this work for additional information 6*b1cdbd2cSJim Jagielski * regarding copyright ownership. The ASF licenses this file 7*b1cdbd2cSJim Jagielski * to you under the Apache License, Version 2.0 (the 8*b1cdbd2cSJim Jagielski * "License"); you may not use this file except in compliance 9*b1cdbd2cSJim Jagielski * with the License. You may obtain a copy of the License at 10*b1cdbd2cSJim Jagielski * 11*b1cdbd2cSJim Jagielski * http://www.apache.org/licenses/LICENSE-2.0 12*b1cdbd2cSJim Jagielski * 13*b1cdbd2cSJim Jagielski * Unless required by applicable law or agreed to in writing, 14*b1cdbd2cSJim Jagielski * software distributed under the License is distributed on an 15*b1cdbd2cSJim Jagielski * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*b1cdbd2cSJim Jagielski * KIND, either express or implied. See the License for the 17*b1cdbd2cSJim Jagielski * specific language governing permissions and limitations 18*b1cdbd2cSJim Jagielski * under the License. 19*b1cdbd2cSJim Jagielski * 20*b1cdbd2cSJim Jagielski *************************************************************/ 21*b1cdbd2cSJim Jagielski 22*b1cdbd2cSJim Jagielski // MARKER(update_precomp.py): autogen include statement, do not remove 23*b1cdbd2cSJim Jagielski #include "precompiled_drawinglayer.hxx" 24*b1cdbd2cSJim Jagielski 25*b1cdbd2cSJim Jagielski #include <drawinglayer/primitive2d/textbreakuphelper.hxx> 26*b1cdbd2cSJim Jagielski #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> 27*b1cdbd2cSJim Jagielski #include <com/sun/star/i18n/XBreakIterator.hpp> 28*b1cdbd2cSJim Jagielski #include <comphelper/processfactory.hxx> 29*b1cdbd2cSJim Jagielski #include <com/sun/star/i18n/CharacterIteratorMode.hdl> 30*b1cdbd2cSJim Jagielski #include <com/sun/star/i18n/WordType.hpp> 31*b1cdbd2cSJim Jagielski #include <com/sun/star/i18n/CharType.hpp> 32*b1cdbd2cSJim Jagielski 33*b1cdbd2cSJim Jagielski ////////////////////////////////////////////////////////////////////////////// 34*b1cdbd2cSJim Jagielski 35*b1cdbd2cSJim Jagielski namespace drawinglayer 36*b1cdbd2cSJim Jagielski { 37*b1cdbd2cSJim Jagielski namespace primitive2d 38*b1cdbd2cSJim Jagielski { TextBreakupHelper(const TextSimplePortionPrimitive2D & rSource)39*b1cdbd2cSJim Jagielski TextBreakupHelper::TextBreakupHelper(const TextSimplePortionPrimitive2D& rSource) 40*b1cdbd2cSJim Jagielski : mrSource(rSource), 41*b1cdbd2cSJim Jagielski mxResult(), 42*b1cdbd2cSJim Jagielski maTextLayouter(), 43*b1cdbd2cSJim Jagielski maDecTrans(), 44*b1cdbd2cSJim Jagielski mbNoDXArray(false) 45*b1cdbd2cSJim Jagielski { 46*b1cdbd2cSJim Jagielski OSL_ENSURE(dynamic_cast< const TextSimplePortionPrimitive2D* >(&mrSource), "TextBreakupHelper with illegal primitive created (!)"); 47*b1cdbd2cSJim Jagielski maDecTrans = mrSource.getTextTransform(); 48*b1cdbd2cSJim Jagielski mbNoDXArray = mrSource.getDXArray().empty(); 49*b1cdbd2cSJim Jagielski 50*b1cdbd2cSJim Jagielski if(mbNoDXArray) 51*b1cdbd2cSJim Jagielski { 52*b1cdbd2cSJim Jagielski // init TextLayouter when no dxarray 53*b1cdbd2cSJim Jagielski maTextLayouter.setFontAttribute( 54*b1cdbd2cSJim Jagielski mrSource.getFontAttribute(), 55*b1cdbd2cSJim Jagielski maDecTrans.getScale().getX(), 56*b1cdbd2cSJim Jagielski maDecTrans.getScale().getY(), 57*b1cdbd2cSJim Jagielski mrSource.getLocale()); 58*b1cdbd2cSJim Jagielski } 59*b1cdbd2cSJim Jagielski } 60*b1cdbd2cSJim Jagielski ~TextBreakupHelper()61*b1cdbd2cSJim Jagielski TextBreakupHelper::~TextBreakupHelper() 62*b1cdbd2cSJim Jagielski { 63*b1cdbd2cSJim Jagielski } 64*b1cdbd2cSJim Jagielski breakupPortion(Primitive2DVector & rTempResult,sal_uInt32 nIndex,sal_uInt32 nLength,bool bWordLineMode)65*b1cdbd2cSJim Jagielski void TextBreakupHelper::breakupPortion(Primitive2DVector& rTempResult, sal_uInt32 nIndex, sal_uInt32 nLength, bool bWordLineMode) 66*b1cdbd2cSJim Jagielski { 67*b1cdbd2cSJim Jagielski if(nLength && !(nIndex == mrSource.getTextPosition() && nLength == mrSource.getTextLength())) 68*b1cdbd2cSJim Jagielski { 69*b1cdbd2cSJim Jagielski // prepare values for new portion 70*b1cdbd2cSJim Jagielski basegfx::B2DHomMatrix aNewTransform; 71*b1cdbd2cSJim Jagielski ::std::vector< double > aNewDXArray; 72*b1cdbd2cSJim Jagielski const bool bNewStartIsNotOldStart(nIndex > mrSource.getTextPosition()); 73*b1cdbd2cSJim Jagielski 74*b1cdbd2cSJim Jagielski if(!mbNoDXArray) 75*b1cdbd2cSJim Jagielski { 76*b1cdbd2cSJim Jagielski // prepare new DXArray for the single word 77*b1cdbd2cSJim Jagielski aNewDXArray = ::std::vector< double >( 78*b1cdbd2cSJim Jagielski mrSource.getDXArray().begin() + (nIndex - mrSource.getTextPosition()), 79*b1cdbd2cSJim Jagielski mrSource.getDXArray().begin() + ((nIndex + nLength) - mrSource.getTextPosition())); 80*b1cdbd2cSJim Jagielski } 81*b1cdbd2cSJim Jagielski 82*b1cdbd2cSJim Jagielski if(bNewStartIsNotOldStart) 83*b1cdbd2cSJim Jagielski { 84*b1cdbd2cSJim Jagielski // needs to be moved to a new start position 85*b1cdbd2cSJim Jagielski double fOffset(0.0); 86*b1cdbd2cSJim Jagielski 87*b1cdbd2cSJim Jagielski if(mbNoDXArray) 88*b1cdbd2cSJim Jagielski { 89*b1cdbd2cSJim Jagielski // evaluate using TextLayouter 90*b1cdbd2cSJim Jagielski fOffset = maTextLayouter.getTextWidth(mrSource.getText(), mrSource.getTextPosition(), nIndex); 91*b1cdbd2cSJim Jagielski } 92*b1cdbd2cSJim Jagielski else 93*b1cdbd2cSJim Jagielski { 94*b1cdbd2cSJim Jagielski // get from DXArray 95*b1cdbd2cSJim Jagielski const sal_uInt32 nIndex2(static_cast< sal_uInt32 >(nIndex - mrSource.getTextPosition())); 96*b1cdbd2cSJim Jagielski fOffset = mrSource.getDXArray()[nIndex2 - 1]; 97*b1cdbd2cSJim Jagielski } 98*b1cdbd2cSJim Jagielski 99*b1cdbd2cSJim Jagielski // need offset without FontScale for building the new transformation. The 100*b1cdbd2cSJim Jagielski // new transformation will be multiplied with the current text transformation 101*b1cdbd2cSJim Jagielski // so FontScale would be double 102*b1cdbd2cSJim Jagielski double fOffsetNoScale(fOffset); 103*b1cdbd2cSJim Jagielski const double fFontScaleX(maDecTrans.getScale().getX()); 104*b1cdbd2cSJim Jagielski 105*b1cdbd2cSJim Jagielski if(!basegfx::fTools::equal(fFontScaleX, 1.0) 106*b1cdbd2cSJim Jagielski && !basegfx::fTools::equalZero(fFontScaleX)) 107*b1cdbd2cSJim Jagielski { 108*b1cdbd2cSJim Jagielski fOffsetNoScale /= fFontScaleX; 109*b1cdbd2cSJim Jagielski } 110*b1cdbd2cSJim Jagielski 111*b1cdbd2cSJim Jagielski // apply needed offset to transformation 112*b1cdbd2cSJim Jagielski aNewTransform.translate(fOffsetNoScale, 0.0); 113*b1cdbd2cSJim Jagielski 114*b1cdbd2cSJim Jagielski if(!mbNoDXArray) 115*b1cdbd2cSJim Jagielski { 116*b1cdbd2cSJim Jagielski // DXArray values need to be corrected with the offset, too. Here, 117*b1cdbd2cSJim Jagielski // take the scaled offset since the DXArray is scaled 118*b1cdbd2cSJim Jagielski const sal_uInt32 nArraySize(aNewDXArray.size()); 119*b1cdbd2cSJim Jagielski 120*b1cdbd2cSJim Jagielski for(sal_uInt32 a(0); a < nArraySize; a++) 121*b1cdbd2cSJim Jagielski { 122*b1cdbd2cSJim Jagielski aNewDXArray[a] -= fOffset; 123*b1cdbd2cSJim Jagielski } 124*b1cdbd2cSJim Jagielski } 125*b1cdbd2cSJim Jagielski } 126*b1cdbd2cSJim Jagielski 127*b1cdbd2cSJim Jagielski // add text transformation to new transformation 128*b1cdbd2cSJim Jagielski aNewTransform = maDecTrans.getB2DHomMatrix() * aNewTransform; 129*b1cdbd2cSJim Jagielski 130*b1cdbd2cSJim Jagielski // callback to allow evtl. changes 131*b1cdbd2cSJim Jagielski const bool bCreate(allowChange(rTempResult.size(), aNewTransform, nIndex, nLength)); 132*b1cdbd2cSJim Jagielski 133*b1cdbd2cSJim Jagielski if(bCreate) 134*b1cdbd2cSJim Jagielski { 135*b1cdbd2cSJim Jagielski // check if we have a decorated primitive as source 136*b1cdbd2cSJim Jagielski const TextDecoratedPortionPrimitive2D* pTextDecoratedPortionPrimitive2D = 137*b1cdbd2cSJim Jagielski dynamic_cast< const TextDecoratedPortionPrimitive2D* >(&mrSource); 138*b1cdbd2cSJim Jagielski 139*b1cdbd2cSJim Jagielski if(pTextDecoratedPortionPrimitive2D) 140*b1cdbd2cSJim Jagielski { 141*b1cdbd2cSJim Jagielski // create a TextDecoratedPortionPrimitive2D 142*b1cdbd2cSJim Jagielski rTempResult.push_back( 143*b1cdbd2cSJim Jagielski new TextDecoratedPortionPrimitive2D( 144*b1cdbd2cSJim Jagielski aNewTransform, 145*b1cdbd2cSJim Jagielski mrSource.getText(), 146*b1cdbd2cSJim Jagielski nIndex, 147*b1cdbd2cSJim Jagielski nLength, 148*b1cdbd2cSJim Jagielski aNewDXArray, 149*b1cdbd2cSJim Jagielski mrSource.getFontAttribute(), 150*b1cdbd2cSJim Jagielski mrSource.getLocale(), 151*b1cdbd2cSJim Jagielski mrSource.getFontColor(), 152*b1cdbd2cSJim Jagielski 153*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getOverlineColor(), 154*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getTextlineColor(), 155*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getFontOverline(), 156*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getFontUnderline(), 157*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getUnderlineAbove(), 158*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getTextStrikeout(), 159*b1cdbd2cSJim Jagielski 160*b1cdbd2cSJim Jagielski // reset WordLineMode when BreakupUnit_word is executed; else copy original 161*b1cdbd2cSJim Jagielski bWordLineMode ? false : pTextDecoratedPortionPrimitive2D->getWordLineMode(), 162*b1cdbd2cSJim Jagielski 163*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getTextEmphasisMark(), 164*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getEmphasisMarkAbove(), 165*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getEmphasisMarkBelow(), 166*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getTextRelief(), 167*b1cdbd2cSJim Jagielski pTextDecoratedPortionPrimitive2D->getShadow())); 168*b1cdbd2cSJim Jagielski } 169*b1cdbd2cSJim Jagielski else 170*b1cdbd2cSJim Jagielski { 171*b1cdbd2cSJim Jagielski // create a SimpleTextPrimitive 172*b1cdbd2cSJim Jagielski rTempResult.push_back( 173*b1cdbd2cSJim Jagielski new TextSimplePortionPrimitive2D( 174*b1cdbd2cSJim Jagielski aNewTransform, 175*b1cdbd2cSJim Jagielski mrSource.getText(), 176*b1cdbd2cSJim Jagielski nIndex, 177*b1cdbd2cSJim Jagielski nLength, 178*b1cdbd2cSJim Jagielski aNewDXArray, 179*b1cdbd2cSJim Jagielski mrSource.getFontAttribute(), 180*b1cdbd2cSJim Jagielski mrSource.getLocale(), 181*b1cdbd2cSJim Jagielski mrSource.getFontColor())); 182*b1cdbd2cSJim Jagielski } 183*b1cdbd2cSJim Jagielski } 184*b1cdbd2cSJim Jagielski } 185*b1cdbd2cSJim Jagielski } 186*b1cdbd2cSJim Jagielski allowChange(sal_uInt32,basegfx::B2DHomMatrix &,sal_uInt32,sal_uInt32)187*b1cdbd2cSJim Jagielski bool TextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& /*rNewTransform*/, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/) 188*b1cdbd2cSJim Jagielski { 189*b1cdbd2cSJim Jagielski return true; 190*b1cdbd2cSJim Jagielski } 191*b1cdbd2cSJim Jagielski breakup(BreakupUnit aBreakupUnit)192*b1cdbd2cSJim Jagielski void TextBreakupHelper::breakup(BreakupUnit aBreakupUnit) 193*b1cdbd2cSJim Jagielski { 194*b1cdbd2cSJim Jagielski if(mrSource.getTextLength()) 195*b1cdbd2cSJim Jagielski { 196*b1cdbd2cSJim Jagielski Primitive2DVector aTempResult; 197*b1cdbd2cSJim Jagielski static ::com::sun::star::uno::Reference< ::com::sun::star::i18n::XBreakIterator > xBreakIterator; 198*b1cdbd2cSJim Jagielski 199*b1cdbd2cSJim Jagielski if(!xBreakIterator.is()) 200*b1cdbd2cSJim Jagielski { 201*b1cdbd2cSJim Jagielski ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory > xMSF(::comphelper::getProcessServiceFactory()); 202*b1cdbd2cSJim Jagielski xBreakIterator.set(xMSF->createInstance(rtl::OUString::createFromAscii("com.sun.star.i18n.BreakIterator")), ::com::sun::star::uno::UNO_QUERY); 203*b1cdbd2cSJim Jagielski } 204*b1cdbd2cSJim Jagielski 205*b1cdbd2cSJim Jagielski if(xBreakIterator.is()) 206*b1cdbd2cSJim Jagielski { 207*b1cdbd2cSJim Jagielski const rtl::OUString& rTxt = mrSource.getText(); 208*b1cdbd2cSJim Jagielski const sal_Int32 nTextLength(mrSource.getTextLength()); 209*b1cdbd2cSJim Jagielski const ::com::sun::star::lang::Locale& rLocale = mrSource.getLocale(); 210*b1cdbd2cSJim Jagielski const sal_Int32 nTextPosition(mrSource.getTextPosition()); 211*b1cdbd2cSJim Jagielski sal_Int32 nCurrent(nTextPosition); 212*b1cdbd2cSJim Jagielski 213*b1cdbd2cSJim Jagielski switch(aBreakupUnit) 214*b1cdbd2cSJim Jagielski { 215*b1cdbd2cSJim Jagielski case BreakupUnit_character: 216*b1cdbd2cSJim Jagielski { 217*b1cdbd2cSJim Jagielski sal_Int32 nDone; 218*b1cdbd2cSJim Jagielski sal_Int32 nNextCellBreak(xBreakIterator->nextCharacters(rTxt, nTextPosition, rLocale, ::com::sun::star::i18n::CharacterIteratorMode::SKIPCELL, 0, nDone)); 219*b1cdbd2cSJim Jagielski sal_Int32 a(nTextPosition); 220*b1cdbd2cSJim Jagielski 221*b1cdbd2cSJim Jagielski for(; a < nTextPosition + nTextLength; a++) 222*b1cdbd2cSJim Jagielski { 223*b1cdbd2cSJim Jagielski if(a == nNextCellBreak) 224*b1cdbd2cSJim Jagielski { 225*b1cdbd2cSJim Jagielski breakupPortion(aTempResult, nCurrent, a - nCurrent, false); 226*b1cdbd2cSJim Jagielski nCurrent = a; 227*b1cdbd2cSJim Jagielski nNextCellBreak = xBreakIterator->nextCharacters(rTxt, a, rLocale, ::com::sun::star::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); 228*b1cdbd2cSJim Jagielski } 229*b1cdbd2cSJim Jagielski } 230*b1cdbd2cSJim Jagielski 231*b1cdbd2cSJim Jagielski breakupPortion(aTempResult, nCurrent, a - nCurrent, false); 232*b1cdbd2cSJim Jagielski break; 233*b1cdbd2cSJim Jagielski } 234*b1cdbd2cSJim Jagielski case BreakupUnit_word: 235*b1cdbd2cSJim Jagielski { 236*b1cdbd2cSJim Jagielski ::com::sun::star::i18n::Boundary nNextWordBoundary(xBreakIterator->getWordBoundary(rTxt, nTextPosition, rLocale, ::com::sun::star::i18n::WordType::ANY_WORD, sal_True)); 237*b1cdbd2cSJim Jagielski sal_Int32 a(nTextPosition); 238*b1cdbd2cSJim Jagielski 239*b1cdbd2cSJim Jagielski for(; a < nTextPosition + nTextLength; a++) 240*b1cdbd2cSJim Jagielski { 241*b1cdbd2cSJim Jagielski if(a == nNextWordBoundary.endPos) 242*b1cdbd2cSJim Jagielski { 243*b1cdbd2cSJim Jagielski if(a > nCurrent) 244*b1cdbd2cSJim Jagielski { 245*b1cdbd2cSJim Jagielski breakupPortion(aTempResult, nCurrent, a - nCurrent, true); 246*b1cdbd2cSJim Jagielski } 247*b1cdbd2cSJim Jagielski 248*b1cdbd2cSJim Jagielski nCurrent = a; 249*b1cdbd2cSJim Jagielski 250*b1cdbd2cSJim Jagielski // skip spaces (maybe enhanced with a bool later if needed) 251*b1cdbd2cSJim Jagielski { 252*b1cdbd2cSJim Jagielski const sal_Int32 nEndOfSpaces(xBreakIterator->endOfCharBlock(rTxt, a, rLocale, ::com::sun::star::i18n::CharType::SPACE_SEPARATOR)); 253*b1cdbd2cSJim Jagielski 254*b1cdbd2cSJim Jagielski if(nEndOfSpaces > a) 255*b1cdbd2cSJim Jagielski { 256*b1cdbd2cSJim Jagielski nCurrent = nEndOfSpaces; 257*b1cdbd2cSJim Jagielski } 258*b1cdbd2cSJim Jagielski } 259*b1cdbd2cSJim Jagielski 260*b1cdbd2cSJim Jagielski nNextWordBoundary = xBreakIterator->getWordBoundary(rTxt, a + 1, rLocale, ::com::sun::star::i18n::WordType::ANY_WORD, sal_True); 261*b1cdbd2cSJim Jagielski } 262*b1cdbd2cSJim Jagielski } 263*b1cdbd2cSJim Jagielski 264*b1cdbd2cSJim Jagielski if(a > nCurrent) 265*b1cdbd2cSJim Jagielski { 266*b1cdbd2cSJim Jagielski breakupPortion(aTempResult, nCurrent, a - nCurrent, true); 267*b1cdbd2cSJim Jagielski } 268*b1cdbd2cSJim Jagielski break; 269*b1cdbd2cSJim Jagielski } 270*b1cdbd2cSJim Jagielski case BreakupUnit_sentence: 271*b1cdbd2cSJim Jagielski { 272*b1cdbd2cSJim Jagielski sal_Int32 nNextSentenceBreak(xBreakIterator->endOfSentence(rTxt, nTextPosition, rLocale)); 273*b1cdbd2cSJim Jagielski sal_Int32 a(nTextPosition); 274*b1cdbd2cSJim Jagielski 275*b1cdbd2cSJim Jagielski for(; a < nTextPosition + nTextLength; a++) 276*b1cdbd2cSJim Jagielski { 277*b1cdbd2cSJim Jagielski if(a == nNextSentenceBreak) 278*b1cdbd2cSJim Jagielski { 279*b1cdbd2cSJim Jagielski breakupPortion(aTempResult, nCurrent, a - nCurrent, false); 280*b1cdbd2cSJim Jagielski nCurrent = a; 281*b1cdbd2cSJim Jagielski nNextSentenceBreak = xBreakIterator->endOfSentence(rTxt, a + 1, rLocale); 282*b1cdbd2cSJim Jagielski } 283*b1cdbd2cSJim Jagielski } 284*b1cdbd2cSJim Jagielski 285*b1cdbd2cSJim Jagielski breakupPortion(aTempResult, nCurrent, a - nCurrent, false); 286*b1cdbd2cSJim Jagielski break; 287*b1cdbd2cSJim Jagielski } 288*b1cdbd2cSJim Jagielski } 289*b1cdbd2cSJim Jagielski } 290*b1cdbd2cSJim Jagielski 291*b1cdbd2cSJim Jagielski mxResult = Primitive2DVectorToPrimitive2DSequence(aTempResult); 292*b1cdbd2cSJim Jagielski } 293*b1cdbd2cSJim Jagielski } 294*b1cdbd2cSJim Jagielski getResult(BreakupUnit aBreakupUnit) const295*b1cdbd2cSJim Jagielski const Primitive2DSequence& TextBreakupHelper::getResult(BreakupUnit aBreakupUnit) const 296*b1cdbd2cSJim Jagielski { 297*b1cdbd2cSJim Jagielski if(!mxResult.hasElements()) 298*b1cdbd2cSJim Jagielski { 299*b1cdbd2cSJim Jagielski const_cast< TextBreakupHelper* >(this)->breakup(aBreakupUnit); 300*b1cdbd2cSJim Jagielski } 301*b1cdbd2cSJim Jagielski 302*b1cdbd2cSJim Jagielski return mxResult; 303*b1cdbd2cSJim Jagielski } 304*b1cdbd2cSJim Jagielski 305*b1cdbd2cSJim Jagielski } // end of namespace primitive2d 306*b1cdbd2cSJim Jagielski } // end of namespace drawinglayer 307*b1cdbd2cSJim Jagielski 308*b1cdbd2cSJim Jagielski ////////////////////////////////////////////////////////////////////////////// 309*b1cdbd2cSJim Jagielski // eof 310