/************************************************************************* * * $RCSfile: salatslayout.cxx,v $ * * $Revision: 1.5 $ * * last change: $Author: danw $ $Date: 2003/11/19 06:52:28 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses * * - GNU General Public License Version 2.1 * * * Sun Microsystems Inc., October, 2000 * * GNU General Public License Version 2.1 * ============================================= * Copyright 2000 by Sun Microsystems, Inc. * 901 San Antonio Road, Palo Alto, CA 94303, USA * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2.1, as published by the Free Software Foundation. * * This library 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * * * ================================================= * Modified September 2002 by Edward Peterlin. SISSL Removed. NeoOffice is distributed under GPL only under modification term 3 of the LGPL. * * Contributor(s): _______________________________________ * * ************************************************************************/ #ifndef _SV_SALGDI_HXX #include #endif #ifndef _SV_SALDATA_HXX #include #endif #ifndef _SV_SALGDIUTILS_HXX #include #endif #ifndef _SV_SALLAYOUT_HXX #include #endif #include #include #include #include inline int Float32ToInt( Float32 f ) { return (int)(f+0.5); } // ======================================================================= class ATSLayout : public SalLayout { public: ATSLayout( SalGraphicsData *pGraphicsData ); ~ATSLayout(); virtual bool LayoutText( ImplLayoutArgs& ); virtual void AdjustLayout( ImplLayoutArgs& ); virtual void DrawText( SalGraphics& ) const; virtual int GetNextGlyphs( int nLen, long* pGlyphs, Point& rPos, int&, long* pGlyphAdvances, int* pCharIndexes ) const; virtual long GetTextWidth() const; virtual long FillDXArray( long* pDXArray ) const; virtual int GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const; virtual void GetCaretPositions( int nArraySize, long* pCaretXArray ) const; virtual bool GetGlyphOutlines( SalGraphics&, PolyPolyVector& ) const; virtual bool GetBoundRect( SalGraphics&, Rectangle& ) const; // for glyph+font+script fallback virtual void InitFont(); virtual void MoveGlyph( int nStart, long nNewXPos ); virtual void DropGlyph( int nStart ); virtual void Simplify( bool bIsBase ); private: SalGraphicsData * mpGraphicsData; // ATSUStyle maATSUIStyle; ATSUTextLayout maATSULayout; UniCharArrayOffset mnTextOfs; UniCharCount mnTextLen; int mnBaseAdv; private: bool InitGIA() const; mutable ATSUGlyphInfoArray * mpGIA; }; // ======================================================================= ATSLayout::ATSLayout( SalGraphicsData *pGraphicsData ) : mpGraphicsData( pGraphicsData ), maATSULayout( NULL ), mnTextOfs( 0 ), mnTextLen( 0 ), mnBaseAdv( 0 ), mpGIA( NULL ) {} // ----------------------------------------------------------------------- ATSLayout::~ATSLayout() { if ( maATSULayout ) ATSUDisposeTextLayout( maATSULayout ); } // ----------------------------------------------------------------------- bool ATSLayout::LayoutText( ImplLayoutArgs& rArgs ) { if( maATSULayout ) ATSUDisposeTextLayout( maATSULayout ); // At some point we actually need to do layout... #if 0 // why doesn't ATSUCreateTextLayoutWithTextPtr always fails with mnTextOfs!=0 ??? mnTextOfs = rArgs.mnMinCharPos; mnTextLen = rArgs.mnEndCharPos - rArgs.mnMinCharPos; UniCharCount nRunLength = kATSUToTextEnd; OSStatus theErr = ATSUCreateTextLayoutWithTextPtr( rArgs.mpStr, mnTextOfs, mnTextLen, rArgs.mnLength, 1, &nRunLength, &(mpGraphicsData->maATSUIStyle), &maATSULayout ); #else // workaround for problem above => expect small problems in complex text mnTextOfs = 0; mnTextLen = rArgs.mnEndCharPos - rArgs.mnMinCharPos; UniCharCount nRunLength = kATSUToTextEnd; OSStatus theErr = ATSUCreateTextLayoutWithTextPtr( rArgs.mpStr + rArgs.mnMinCharPos, mnTextOfs, mnTextLen, rArgs.mnLength-rArgs.mnMinCharPos, 1, &nRunLength, &(mpGraphicsData->maATSUIStyle), &maATSULayout ); #endif if( theErr != noErr ) { fprintf( stderr, "ATSLayout::LayoutText() : " "Unable to create ATSUI text layout! " "nTextOfs=%d, nTextLen=%d, nParaLen=%d => err = %d\n", mnTextOfs, mnTextLen, rArgs.mnLength, theErr ); return false; } return true; } // ----------------------------------------------------------------------- void ATSLayout::AdjustLayout( ImplLayoutArgs& rArgs ) { int nPixelWidth = rArgs.mnLayoutWidth; if( !nPixelWidth && rArgs.mpDXArray ) { // for now we are only interested in the layout width // TODO: account for individual logical widths nPixelWidth = rArgs.mpDXArray[ mnTextLen - 1 ]; } // return early if there is nothing to do if( !nPixelWidth ) return; ATSUAttributeTag nTags[2]; ATSUAttributeValuePtr nVals[2]; ByteCount nBytes[2]; Fixed nFixedWidth = Long2Fix( nPixelWidth ); Fixed nFixedOne = Long2Fix( 1 ); nTags[0] = kATSULineWidthTag; nBytes[0] = sizeof( Fixed ); nVals[0] = &nFixedWidth; nTags[1] = kATSULineJustificationFactorTag; nBytes[1] = sizeof( Fixed ); nVals[1] = &nFixedOne; ATSUSetLayoutControls( maATSULayout, 2, nTags, nBytes, nVals ); } // ----------------------------------------------------------------------- void ATSLayout::DrawText( SalGraphics& rGraphics ) const { if( mnTextLen <= 0 ) return; Point aPos = GetDrawPosition( Point(mnBaseAdv, 0) ); if ( BeginGraphics(mpGraphicsData) ) { ATSUAttributeTag cgTag = kATSUCGContextTag; ByteCount cgSize = sizeof( CGContextRef ); ATSUAttributeValuePtr cgValPtr = &(mpGraphicsData->mpCGContext); CGRect viewRect; // [ed] 9/21 If we're not in a standard rotation, draw the rotated text if( mpGraphicsData->mnATSUIRotation != 0 ) { Fixed theAngle = Long2Fix( mpGraphicsData->mnATSUIRotation ); ATSUAttributeTag theTag = kATSULineRotationTag; ByteCount valSize = sizeof( Fixed ); ATSUAttributeValuePtr valPtr = &theAngle; OSStatus theErr; theErr = ATSUSetLayoutControls( maATSULayout, 1, &theTag, &valSize, &valPtr ); if( theErr != noErr ) fprintf( stderr, "ATSLayout::DrawText(0x%X) : Unable to set layout font rotation\n", this ); } // Tell ATSUI to use CoreGraphics ATSUSetLayoutControls( maATSULayout, 1, &cgTag, &cgSize, &cgValPtr ); // Save the current CTM and state so we can munge it for ATSUI drawing CGContextSaveGState( mpGraphicsData->mpCGContext ); GetGraphicsBoundsCGRect( mpGraphicsData, &viewRect ); // Undo the coordinate translations we did to flip the view. // Because translations are CUMULATIVE, we can't simply cancel just the scale CGContextTranslateCTM( mpGraphicsData->mpCGContext, 0, viewRect.size.height ); CGContextScaleCTM( mpGraphicsData->mpCGContext, 1.0, -1.0 ); // Draw the text if( ATSUDrawText( maATSULayout, mnTextOfs, mnTextLen, Long2Fix(aPos.X()), Long2Fix((long)(viewRect.size.height)-(aPos.Y()))) != noErr ) { fprintf( stderr, "ATSLayout::DrawText(0x%X) : ATSUDrawText failed!\n", this ); } // Restore OOo-correct CTM and state CGContextRestoreGState( mpGraphicsData->mpCGContext ); EndGraphics( mpGraphicsData ); } } // ----------------------------------------------------------------------- int ATSLayout::GetNextGlyphs( int nLen, long* pGlyphs, Point& rPos, int& nStart, long* pGlyphAdvances, int* pCharIndexes ) const { if( !InitGIA() ) return 0; if( nStart < 0 ) // first glyph requested? nStart = 0; if( nStart >= mpGIA->numGlyphs ) // no glyph left? return 0; const ATSUGlyphInfo* pG = mpGIA->glyphs + nStart; rPos.X() = pG->screenX; rPos.Y() = Float32ToInt( pG->deltaY ); int nCount = 0; for(; ++nCount <= nLen; ++pG ) { *(pGlyphs++) = pG->glyphID; if( pCharIndexes ) *(pCharIndexes++) = pG->charIndex; if( pGlyphAdvances ) *(pGlyphAdvances++) = 0; // TODO // TODO: break early if unexpected position break; // nCount=1 because we don't know the glyph width } nStart += nCount; return nCount; } // ----------------------------------------------------------------------- // get typographic bounds of the text long ATSLayout::GetTextWidth() const { ItemCount nBoundsCount = 0; ATSTrapezoid aTrapez; ATSUGetGlyphBounds( maATSULayout, 0, 0, mnTextOfs, mnTextLen, kATSUseDeviceOrigins, 1, &aTrapez, &nBoundsCount ); if( nBoundsCount != 1 ) return 0; long nWidth = Fix2Long( aTrapez.lowerRight.x - aTrapez.lowerLeft.x ); return ( nWidth ); } // ----------------------------------------------------------------------- long ATSLayout::FillDXArray( long* pDXArray ) const { long nWidth = GetTextWidth(); if ( (NULL != pDXArray) && (mnTextLen > 0) ) { // for now we split the whole width to fake the virtual charwidths // TODO: calculate the virtual character widths exactly for( int i = 0; i < mnTextLen; i++ ) pDXArray[ i ] = ((i+1) * nWidth) / mnTextLen; } return( nWidth ); } // ----------------------------------------------------------------------- int ATSLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const { // TODO: accout for nCharExtra ATSUTextMeasurement nATSUMaxWidth = Long2Fix( nMaxWidth / nFactor ); // TODO: massage ATSUBreakLine to like inword breaks: // we prefer BreakInWord instead of ATSUBreakLine trying to be smart // and moving the soft break inbetween words, as the ATSUI API says UniCharArrayOffset nBreakPos = mnTextOfs; OSStatus nStatus = ATSUBreakLine( maATSULayout, mnTextOfs, nATSUMaxWidth, false, &nBreakPos ); if( (nStatus != noErr) && (nStatus != kATSULineBreakInWord) ) { fprintf(stderr,"ATSUBreakLine => %d\n", nStatus); return( STRING_LEN ); } return( nBreakPos ); } // ----------------------------------------------------------------------- void ATSLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const { // prepare caret array int n; for( n = 0; n < nMaxIndex; ++n ) pCaretXArray[n] = -1; if( !InitGIA() ) return; // use glyph index array to fill the caret positions const ATSUGlyphInfo* pG = mpGIA->glyphs; for( int i = 0; i < mpGIA->numGlyphs; ++i ) { n = 2 * pG->charIndex; if( n > nMaxIndex + 1 ) continue; // TODO: recognize and deal with RTL glyphs // is it one the pG->layputFlags? bool bRTL = false; // TODO: pCaretXArray[ n+0 ] = ???; pCaretXArray[ n+1 ] = pG->caretX; } long nXPos = 0; for( n = 0; n < nMaxIndex; ++n ) { if( pCaretXArray[n] >= 0 ) nXPos = pCaretXArray[n]; else pCaretXArray[n] = nXPos; } } // ----------------------------------------------------------------------- // get ink bounds of the text bool ATSLayout::GetBoundRect( SalGraphics&, Rectangle& rVCLRect ) const { Rect aMacRect; ATSUMeasureTextImage( maATSULayout, mnTextOfs, mnTextLen, 0, 0, &aMacRect ); rVCLRect.Left() = aMacRect.left; rVCLRect.Top() = aMacRect.top; rVCLRect.Right() = aMacRect.right; rVCLRect.Bottom() = aMacRect.bottom; return true; } // ----------------------------------------------------------------------- // initialize glyph index array bool ATSLayout::InitGIA() const { if( mpGIA ) return true; // TODO: is there a good way to predict the maximum glyph count? ByteCount nBufSize = sizeof(ATSUGlyphInfoArray); nBufSize += 3*(mnTextLen+16) * sizeof(ATSUGlyphInfo); char* pBuffer = new char[ nBufSize ]; OSStatus theErr = ATSUGetGlyphInfo( maATSULayout, mnTextOfs, mnTextLen, &nBufSize, mpGIA ); if( theErr == noErr ) mpGIA = reinterpret_cast( pBuffer ); else { fprintf( stderr, "ATSUGetGlyphInfo => err=%d\n", theErr); delete[] pBuffer; } return (mpGIA != NULL); } // ======================================================================= // helper class to convert ATSUI outlines to VCL PolyPolygons class PolyArgs { public: PolyArgs(); ~PolyArgs(); void Init( PolyPolygon* pPolyPoly, long nXOffset, long nYOffset ); void AddPoint( const Float32Point&, PolyFlags ); void ClosePolygon(); private: PolyPolygon* mpPolyPoly; long mnXOffset, mnYOffset; Point* mpPointAry; BYTE* mpFlagAry; USHORT mnMaxPoints; USHORT mnPointCount; USHORT mnPolyCount; bool mbHasOffline; }; // ----------------------------------------------------------------------- PolyArgs::PolyArgs() : mpPolyPoly(NULL), mnPointCount(0), mnPolyCount(0), mbHasOffline(false) { mnMaxPoints = 256; mpPointAry = new Point[ mnMaxPoints ]; mpFlagAry = new BYTE [ mnMaxPoints ]; } // ----------------------------------------------------------------------- PolyArgs::~PolyArgs() { delete[] mpFlagAry; delete[] mpPointAry; } // ----------------------------------------------------------------------- void PolyArgs::Init( PolyPolygon* pPolyPoly, long nXOffset, long nYOffset ) { mnXOffset = nXOffset; mnYOffset = nYOffset; mpPolyPoly = pPolyPoly; mpPolyPoly->Clear(); mnPointCount = 0; mnPolyCount = 0; } // ----------------------------------------------------------------------- void PolyArgs::AddPoint( const Float32Point& rPoint, PolyFlags eFlags ) { if( mnPointCount >= mnMaxPoints ) { // resize if needed (TODO: use STL?) mnMaxPoints *= 4; Point* mpNewPoints = new Point[ mnMaxPoints ]; BYTE* mpNewFlags = new BYTE[ mnMaxPoints ]; for( int i = 0; i < mnPointCount; ++i ) { mpNewPoints[ i ] = mpPointAry[ i ]; mpNewFlags[ i ] = mpFlagAry[ i ]; } delete[] mpFlagAry; delete[] mpPointAry; mpPointAry = mpNewPoints; mpFlagAry = mpNewFlags; } // convert to pixels and add startpoint offset int nXPos = Float32ToInt( rPoint.x ); int nYPos = Float32ToInt( rPoint.y ); mpPointAry[ mnPointCount ] = Point( nXPos + mnXOffset, nYPos + mnYOffset ); // set point flags mpFlagAry[ mnPointCount++ ]= eFlags; mbHasOffline |= (eFlags != POLY_NORMAL); } // ----------------------------------------------------------------------- void PolyArgs::ClosePolygon() { if( !mnPolyCount++ ) return; // append finished polygon Polygon aPoly( mnPointCount, mpPointAry, (mbHasOffline ? mpFlagAry : NULL) ); mpPolyPoly->Insert( aPoly ); // prepare for new polygon mnPointCount = 0; mbHasOffline = false; } // ======================================================================= // helper functions for ATSLayout::GetGlyphOutlines() OSStatus MyATSCubicMoveToCallback( const Float32Point *pt1, void* pData ) { PolyArgs& rA = *reinterpret_cast(pData); // MoveTo implies a new polygon => finish old polygon first rA.ClosePolygon(); rA.AddPoint( *pt1, POLY_NORMAL ); } OSStatus MyATSCubicLineToCallback( const Float32Point* pt1, void* pData ) { PolyArgs& rA = *reinterpret_cast(pData); rA.AddPoint( *pt1, POLY_NORMAL ); } OSStatus MyATSCubicCurveToCallback( const Float32Point* pt1, const Float32Point* pt2, const Float32Point* pt3, void* pData ) { PolyArgs& rA = *reinterpret_cast(pData); rA.AddPoint( *pt1, POLY_CONTROL ); rA.AddPoint( *pt2, POLY_CONTROL ); rA.AddPoint( *pt3, POLY_NORMAL ); } OSStatus MyATSCubicClosePathCallback ( void *pData ) { PolyArgs& rA = *reinterpret_cast(pData); rA.ClosePolygon(); } // ----------------------------------------------------------------------- bool ATSLayout::GetGlyphOutlines( SalGraphics&, PolyPolyVector& rPPV ) const { rPPV.clear(); if( !InitGIA() ) return false; rPPV.resize( mpGIA->numGlyphs ); PolyArgs aPolyArgs; const ATSUGlyphInfo* pG = mpGIA->glyphs; for( int i = 0; i < mpGIA->numGlyphs; ++i, ++pG ) { // convert glyphid at glyphpos to outline GlyphID nGlyphId = pG->glyphID; long nDeltaY = Float32ToInt( pG->deltaY ); aPolyArgs.Init( &rPPV[i], pG->screenX, nDeltaY ); OSStatus nStatus, nCBStatus; nStatus = ATSUGlyphGetCubicPaths( mpGraphicsData->maATSUIStyle, nGlyphId, MyATSCubicMoveToCallback, MyATSCubicLineToCallback, MyATSCubicCurveToCallback, MyATSCubicClosePathCallback, &aPolyArgs, &nCBStatus ); if( (nStatus != noErr) && (nCBStatus != noErr) ) { fprintf( stderr,"ATSUCallback = %d,%d\n", nStatus, nCBStatus ); rPPV.resize( i ); break; } } return true; } // ----------------------------------------------------------------------- void ATSLayout::InitFont() { // TODO to allow glyph fallback } // ----------------------------------------------------------------------- void ATSLayout::MoveGlyph( int nStart, long nNewXPos ) { // TODO to allow glyph fallback } // ----------------------------------------------------------------------- void ATSLayout::DropGlyph( int nStart ) { // TODO to allow glyph fallback } // ----------------------------------------------------------------------- void ATSLayout::Simplify( bool bIsBase ) { // TODO to allow glyph fallback } // ======================================================================= SalLayout* SalGraphics::GetTextLayout( ImplLayoutArgs& rArgs, int nFallbackLevel ) { ATSLayout* pATSLayout = new ATSLayout( &maGraphicsData ); return( pATSLayout ); } // =======================================================================