diff options
author | Herbert Dürr <hdu@apache.org> | 2014-03-13 21:53:27 -0500 |
---|---|---|
committer | Norbert Thiebaud <nthiebaud@gmail.com> | 2014-03-13 23:44:06 -0500 |
commit | eb4fb12fdbe14b929cf8fe5d5785fcfae8d96fa4 (patch) | |
tree | ecc618e622646870e1fc3bc5df39755edc334a50 | |
parent | 90af4f50e8673975e63c928aa47fbc18eb160046 (diff) |
fdo#55142 CoreText handling of trailing whitespace in justified mode
adapted from be899f92ba and 9581d8a5a9 by Herbert Dürr
Change-Id: I24a1e3773764e94bce74c50a83a2cf202b468dda
-rw-r--r-- | vcl/quartz/ctlayout.cxx | 200 |
1 files changed, 142 insertions, 58 deletions
diff --git a/vcl/quartz/ctlayout.cxx b/vcl/quartz/ctlayout.cxx index 1cb8c4825745..ec7516882e7a 100644 --- a/vcl/quartz/ctlayout.cxx +++ b/vcl/quartz/ctlayout.cxx @@ -33,8 +33,8 @@ public: virtual void DrawText( SalGraphics& ) const SAL_OVERRIDE; virtual int GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int&, - sal_Int32* pGlyphAdvances, int* pCharIndexes, - const PhysicalFontFace** pFallbackFonts ) const SAL_OVERRIDE; + sal_Int32* pGlyphAdvances, int* pCharIndexes, + const PhysicalFontFace** pFallbackFonts ) const SAL_OVERRIDE; virtual long GetTextWidth() const SAL_OVERRIDE; virtual long FillDXArray( sal_Int32* pDXArray ) const SAL_OVERRIDE; @@ -58,6 +58,8 @@ private: CTLineRef mpCTLine; int mnCharCount; // ==mnEndCharPos-mnMinCharPos + int mnTrailingSpaceCount; + double mfTrailingSpaceWidth; // cached details about the resulting layout // mutable members since these details are all lazy initialized @@ -69,12 +71,14 @@ private: }; CTLayout::CTLayout( const CoreTextStyle* pTextStyle ) -: mpTextStyle( pTextStyle ) -, mpAttrString( NULL ) -, mpCTLine( NULL ) -, mnCharCount( 0 ) -, mfCachedWidth( -1 ) -, mfBaseAdv( 0 ) + : mpTextStyle( pTextStyle ) + , mpAttrString( NULL ) + , mpCTLine( NULL ) + , mnCharCount( 0 ) + , mnTrailingSpaceCount( 0 ) + , mfTrailingSpaceWidth( 0.0 ) + , mfCachedWidth( -1 ) + , mfBaseAdv( 0 ) { } @@ -103,12 +107,32 @@ bool CTLayout::LayoutText( ImplLayoutArgs& rArgs ) return false; // create the CoreText line layout - CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, rArgs.mpStr + mnMinCharPos, mnCharCount, kCFAllocatorNull ); + CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, + rArgs.mpStr + mnMinCharPos, + mnCharCount, + kCFAllocatorNull ); // CFAttributedStringCreate copies the attribues parameter mpAttrString = CFAttributedStringCreate( NULL, aCFText, mpTextStyle->GetStyleDict() ); mpCTLine = CTLineCreateWithAttributedString( mpAttrString ); CFRelease( aCFText); + mnTrailingSpaceCount = 0; + // reverse search for first 'non-space'... + for( int i = mnEndCharPos - 1; i >= mnMinCharPos; i--) + { + sal_Unicode nChar = rArgs.mpStr[i]; + if ((nChar <= 0x0020) || // blank + (nChar == 0x00A0) || // non breaking space + (nChar >= 0x2000 && nChar <= 0x200F) || // whitespace + (nChar == 0x3000)) // ideographic space + { + mnTrailingSpaceCount += 1; + } + else + { + break; + } + } return true; } @@ -117,37 +141,66 @@ void CTLayout::AdjustLayout( ImplLayoutArgs& rArgs ) if( !mpCTLine) return; - int nOrigWidth = GetTextWidth(); - int nPixelWidth = rArgs.mnLayoutWidth; - if( nPixelWidth ) - { - if( nPixelWidth <= 0) - return; - } - else if( rArgs.mpDXArray ) - { - // for now we are only interested in the layout width - // TODO: use all mpDXArray elements for layouting - nPixelWidth = rArgs.mpDXArray[ mnCharCount - 1 ]; - } - - float fTrailingSpace = CTLineGetTrailingWhitespaceWidth( mpCTLine ); - // in RTL-layouts trailing spaces are leftmost - // TODO: use BiDi-algorithm to thoroughly check this assumption - if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL) - mfBaseAdv = fTrailingSpace; - - // return early if there is nothing to do - if( nPixelWidth <= 0 ) + int nPixelWidth = rArgs.mpDXArray ? rArgs.mpDXArray[ mnCharCount - 1 ] : rArgs.mnLayoutWidth; + if( nPixelWidth <= 0) return; // HACK: justification requests which change the width by just one pixel are probably // #i86038# introduced by lossy conversions between integer based coordinate system - if( (nOrigWidth >= nPixelWidth-1) && (nOrigWidth <= nPixelWidth+1) ) + int fuzz = (nPixelWidth - GetTextWidth()) / 2; + if (!fuzz) + { return; + } - CTLineRef pNewCTLine = CTLineCreateJustifiedLine( mpCTLine, 1.0, nPixelWidth - fTrailingSpace ); - if( !pNewCTLine ) { // CTLineCreateJustifiedLine can and does fail + // if the text to be justified has whitespace in it then + // - Writer goes crazy with its HalfSpace magic + // - CoreText handles spaces specially (in particular at the text end) + if( mnTrailingSpaceCount ) + { + if(rArgs.mpDXArray) + { + int nFullPixelWidth = nPixelWidth; + nPixelWidth = rArgs.mpDXArray[ mnCharCount - mnTrailingSpaceCount - 1]; + mfTrailingSpaceWidth = nFullPixelWidth - nPixelWidth; + } + else + { + if(mfTrailingSpaceWidth <= 0.0) + { + mfTrailingSpaceWidth = CTLineGetTrailingWhitespaceWidth( mpCTLine ); + nPixelWidth -= rint(mfTrailingSpaceWidth); + } + } + if(nPixelWidth <= 0) + { + return; + } + // recreate the CoreText line layout without trailing spaces + CFRelease( mpCTLine ); + CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, + rArgs.mpStr + mnMinCharPos, + mnCharCount - mnTrailingSpaceCount, + kCFAllocatorNull ); + CFAttributedStringRef pAttrStr = CFAttributedStringCreate( NULL, + aCFText, + mpTextStyle->GetStyleDict() ); + mpCTLine = CTLineCreateWithAttributedString( pAttrStr ); + CFRelease( aCFText); + CFRelease( pAttrStr ); + + // in RTL-layouts trailing spaces are leftmost + // TODO: use BiDi-algorithm to thoroughly check this assumption + if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL) + { + mfBaseAdv = mfTrailingSpaceWidth; + } + } + + CTLineRef pNewCTLine = CTLineCreateJustifiedLine( mpCTLine, 1.0, nPixelWidth); + if( !pNewCTLine ) + { + // CTLineCreateJustifiedLine can and does fail // handle failure by keeping the unjustified layout // TODO: a better solution such as // - forcing glyph overlap @@ -157,7 +210,7 @@ void CTLayout::AdjustLayout( ImplLayoutArgs& rArgs ) } CFRelease( mpCTLine ); mpCTLine = pNewCTLine; - mfCachedWidth = nPixelWidth; + mfCachedWidth = nPixelWidth + mfTrailingSpaceWidth; } // When drawing right aligned text, rounding errors in the position returned by @@ -196,8 +249,7 @@ void CTLayout::DrawText( SalGraphics& rGraphics ) const AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics); // short circuit if there is nothing to do - if( (mnCharCount <= 0) - || !rAquaGraphics.CheckContext() ) + if( (mnCharCount <= 0) || !rAquaGraphics.CheckContext() ) return; // the view is vertically flipped => flipped glyphs @@ -235,8 +287,8 @@ void CTLayout::DrawText( SalGraphics& rGraphics ) const } int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int& nStart, - sal_Int32* pGlyphAdvances, int* pCharIndexes, - const PhysicalFontFace** pFallbackFonts ) const + sal_Int32* pGlyphAdvances, int* pCharIndexes, + const PhysicalFontFace** pFallbackFonts ) const { if( !mpCTLine ) return 0; @@ -261,11 +313,14 @@ int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, i // TODO: iterate over cached layout CFArrayRef aGlyphRuns = CTLineGetGlyphRuns( mpCTLine ); const int nRunCount = CFArrayGetCount( aGlyphRuns ); - for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) { + + for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) + { CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex ); const CFIndex nGlyphsInRun = CTRunGetGlyphCount( pGlyphRun ); // skip to the first glyph run of interest - if( nSubIndex >= nGlyphsInRun ) { + if( nSubIndex >= nGlyphsInRun ) + { nSubIndex -= nGlyphsInRun; continue; } @@ -273,22 +328,26 @@ int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, i // get glyph run details const CGGlyph* pCGGlyphIdx = CTRunGetGlyphsPtr( pGlyphRun ); - if( !pCGGlyphIdx ) { + if( !pCGGlyphIdx ) + { aCGGlyphVec.reserve( nGlyphsInRun ); CTRunGetGlyphs( pGlyphRun, aFullRange, &aCGGlyphVec[0] ); pCGGlyphIdx = &aCGGlyphVec[0]; } const CGPoint* pCGGlyphPos = CTRunGetPositionsPtr( pGlyphRun ); - if( !pCGGlyphPos ) { + if( !pCGGlyphPos ) + { aCGPointVec.reserve( nGlyphsInRun ); CTRunGetPositions( pGlyphRun, aFullRange, &aCGPointVec[0] ); pCGGlyphPos = &aCGPointVec[0]; } const CGSize* pCGGlyphAdvs = NULL; - if( pGlyphAdvances) { + if( pGlyphAdvances) + { pCGGlyphAdvs = CTRunGetAdvancesPtr( pGlyphRun ); - if( !pCGGlyphAdvs) { + if( !pCGGlyphAdvs) + { aCGSizeVec.reserve( nGlyphsInRun ); CTRunGetAdvances( pGlyphRun, aFullRange, &aCGSizeVec[0] ); pCGGlyphAdvs = &aCGSizeVec[0]; @@ -296,9 +355,11 @@ int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, i } const CFIndex* pCGGlyphStrIdx = NULL; - if( pCharIndexes) { + if( pCharIndexes) + { pCGGlyphStrIdx = CTRunGetStringIndicesPtr( pGlyphRun ); - if( !pCGGlyphStrIdx) { + if( !pCGGlyphStrIdx) + { aCFIndexVec.reserve( nGlyphsInRun ); CTRunGetStringIndices( pGlyphRun, aFullRange, &aCFIndexVec[0] ); pCGGlyphStrIdx = &aCFIndexVec[0]; @@ -306,13 +367,15 @@ int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, i } const PhysicalFontFace* pFallbackFont = NULL; - if( pFallbackFonts ) { + if( pFallbackFonts ) + { CFDictionaryRef pRunAttributes = CTRunGetAttributes( pGlyphRun ); CTFontRef pRunFont = (CTFontRef)CFDictionaryGetValue( pRunAttributes, kCTFontAttributeName ); CFDictionaryRef pAttributes = mpTextStyle->GetStyleDict(); CTFontRef pFont = (CTFontRef)CFDictionaryGetValue( pAttributes, kCTFontAttributeName ); - if ( !CFEqual( pRunFont, pFont ) ) { + if ( !CFEqual( pRunFont, pFont ) ) + { CTFontDescriptorRef pFontDesc = CTFontCopyFontDescriptor( pRunFont ); ImplDevFontAttributes rDevFontAttr = DevFontFromCTFontDescriptor( pFontDesc, NULL ); pFallbackFont = new CoreTextFontData( rDevFontAttr, (sal_IntPtr)pFontDesc ); @@ -331,7 +394,8 @@ int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, i *(pCharIndexes++) = pCGGlyphStrIdx[ nSubIndex] + mnMinCharPos; if( pFallbackFonts ) *(pFallbackFonts++) = pFallbackFont; - if( !nCount++ ) { + if( !nCount++ ) + { const CGPoint& rCurPos = pCGGlyphPos[ nSubIndex ]; rPos = GetDrawPosition( Point( rCurPos.x, rCurPos.y) ); } @@ -348,7 +412,8 @@ double CTLayout::GetWidth() const if( (mnCharCount <= 0) || !mpCTLine ) return 0; - if( mfCachedWidth < 0.0 ) { + if( mfCachedWidth < 0.0 ) + { mfCachedWidth = CTLineGetTypographicBounds( mpCTLine, NULL, NULL, NULL); } @@ -367,9 +432,18 @@ long CTLayout::FillDXArray( sal_Int32* pDXArray ) const return GetTextWidth(); long nPixWidth = GetTextWidth(); - if( pDXArray ) { + if( pDXArray ) + { // prepare the sub-pixel accurate logical-width array ::std::vector<float> aWidthVector( mnCharCount ); + if( mnTrailingSpaceCount && (mfTrailingSpaceWidth > 0.0) ) + { + const double fOneWidth = mfTrailingSpaceWidth / mnTrailingSpaceCount; + ::std::fill_n(aWidthVector.begin() + (mnCharCount - mnTrailingSpaceCount), + mnTrailingSpaceCount, + fOneWidth); + } + // handle each glyph run CFArrayRef aGlyphRuns = CTLineGetGlyphRuns( mpCTLine ); const int nRunCount = CFArrayGetCount( aGlyphRuns ); @@ -377,15 +451,20 @@ long CTLayout::FillDXArray( sal_Int32* pDXArray ) const CGSizeVector aSizeVec; typedef std::vector<CFIndex> CFIndexVector; CFIndexVector aIndexVec; - for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) { + + for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) + { CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex ); const CFIndex nGlyphCount = CTRunGetGlyphCount( pGlyphRun ); const CFRange aFullRange = CFRangeMake( 0, nGlyphCount ); - aSizeVec.reserve( nGlyphCount ); - aIndexVec.reserve( nGlyphCount ); + + aSizeVec.resize( nGlyphCount ); + aIndexVec.resize( nGlyphCount ); CTRunGetAdvances( pGlyphRun, aFullRange, &aSizeVec[0] ); CTRunGetStringIndices( pGlyphRun, aFullRange, &aIndexVec[0] ); - for( int i = 0; i != nGlyphCount; ++i ) { + + for( int i = 0; i != nGlyphCount; ++i ) + { const int nRelIdx = aIndexVec[i]; aWidthVector[nRelIdx] += aSizeVec[i].width; } @@ -394,7 +473,8 @@ long CTLayout::FillDXArray( sal_Int32* pDXArray ) const // convert the sub-pixel accurate array into classic pDXArray integers float fWidthSum = 0.0; sal_Int32 nOldDX = 0; - for( int i = 0; i < mnCharCount; ++i) { + for( int i = 0; i < mnCharCount; ++i) + { const sal_Int32 nNewDX = rint( fWidthSum += aWidthVector[i]); pDXArray[i] = nNewDX - nOldDX; nOldDX = nNewDX; @@ -412,6 +492,7 @@ sal_Int32 CTLayout::GetTextBreak( long nMaxWidth, long /*nCharExtra*/, int nFact CTTypesetterRef aCTTypeSetter = CTTypesetterCreateWithAttributedString( mpAttrString ); const double fCTMaxWidth = (double)nMaxWidth / nFactor; CFIndex nIndex = CTTypesetterSuggestClusterBreak( aCTTypeSetter, 0, fCTMaxWidth ); + if( nIndex >= mnCharCount ) return -1; @@ -426,17 +507,20 @@ void CTLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const // initialize the caret positions for( int i = 0; i < nMaxIndex; ++i ) + { pCaretXArray[ i ] = -1; - + } for( int n = 0; n <= mnCharCount; ++n ) { // measure the characters cursor position CGFloat fPos2 = -1; const CGFloat fPos1 = CTLineGetOffsetForStringIndex( mpCTLine, n, &fPos2 ); (void)fPos2; // TODO: split cursor at line direction change + // update previous trailing position if( n > 0 ) pCaretXArray[ 2*n-1 ] = lrint( fPos1 ); + // update current leading position if( 2*n >= nMaxIndex ) break; |