1a6b1873ce
Change-Id: Id8b9bec79e5673db738e16905eacd8e84cea89e1
674 lines
23 KiB
C++
674 lines
23 KiB
C++
/* -*- 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 <iostream>
|
|
#include <iomanip>
|
|
|
|
#include "quartz/utils.h"
|
|
#include "coretext/common.h"
|
|
#include "coretext/salcoretextstyle.hxx"
|
|
|
|
#ifdef MACOSX
|
|
#include "coretext/salgdi.h"
|
|
#else
|
|
#include "headless/svpgdi.hxx"
|
|
#endif
|
|
|
|
class CoreTextLayout SAL_FINAL : public SalLayout
|
|
{
|
|
public:
|
|
CoreTextLayout( QuartzSalGraphics* graphics, CoreTextStyleInfo* style);
|
|
~CoreTextLayout();
|
|
|
|
// Overrides in same order as in base class, without "virtual" and
|
|
// with explicit SAL_OVERRIDE. Just a question of taste;)
|
|
bool LayoutText( ImplLayoutArgs& ) SAL_OVERRIDE;
|
|
void AdjustLayout( ImplLayoutArgs& ) SAL_OVERRIDE;
|
|
void DrawText( SalGraphics& ) const SAL_OVERRIDE;
|
|
|
|
int GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const SAL_OVERRIDE;
|
|
long FillDXArray( sal_Int32* pDXArray ) const SAL_OVERRIDE;
|
|
long GetTextWidth() const SAL_OVERRIDE;
|
|
void GetCaretPositions( int nArraySize, sal_Int32* pCaretXArray ) const SAL_OVERRIDE;
|
|
|
|
int GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos, int&,
|
|
sal_Int32* pGlyphAdvances, int* pCharIndexes ) const SAL_OVERRIDE;
|
|
bool GetBoundRect( SalGraphics&, Rectangle& ) const SAL_OVERRIDE;
|
|
|
|
void MoveGlyph( int nStart, long nNewXPos ) SAL_OVERRIDE;
|
|
void DropGlyph( int nStart ) SAL_OVERRIDE;
|
|
void Simplify( bool bIsBase ) SAL_OVERRIDE;
|
|
|
|
private:
|
|
void GetMeasurements();
|
|
void InvalidateMeasurements();
|
|
void ApplyDXArray( ImplLayoutArgs& );
|
|
void Justify( long );
|
|
|
|
#ifndef NDEBUG
|
|
int mnSavedMinCharPos;
|
|
int mnSavedEndCharPos;
|
|
sal_Unicode *mpSavedStr;
|
|
#endif
|
|
|
|
QuartzSalGraphics* mpGraphics;
|
|
CoreTextStyleInfo* mpStyle;
|
|
|
|
int mnCharCount; // ==mnEndCharPos-mnMinCharPos
|
|
|
|
// cached details about the resulting layout
|
|
// mutable members since these details are all lazy initialized
|
|
mutable int mnGlyphCount;
|
|
|
|
mutable CGGlyph* mpGlyphs;
|
|
mutable CGFloat* mpCharWidths;
|
|
mutable int* mpGlyphs2Chars;
|
|
|
|
mutable CGSize* mpGlyphAdvances;
|
|
|
|
mutable CGPoint* mpGlyphPositions;
|
|
mutable CTTypesetterRef mpTypesetter;
|
|
mutable CTLineRef mpLine;
|
|
mutable bool mbHasBoundRectangle;
|
|
mutable Rectangle maBoundRectangle;
|
|
|
|
// x-offset relative to layout origin
|
|
// currently only used in RTL-layouts
|
|
CGFloat mnBaseAdvance;
|
|
|
|
mutable CFIndex mnCurrentRunIndex;
|
|
mutable CFIndex mnCurrentGlyphIndex;
|
|
mutable CFIndex mnCurrentGlyphRunIndex;
|
|
mutable CFArrayRef mpRuns;
|
|
};
|
|
|
|
CoreTextLayout::CoreTextLayout(QuartzSalGraphics* graphics, CoreTextStyleInfo* style) :
|
|
#ifndef NDEBUG
|
|
mpSavedStr(NULL),
|
|
#endif
|
|
mpGraphics(graphics),
|
|
mpStyle(style),
|
|
mnCharCount(-1),
|
|
mnGlyphCount(-1),
|
|
mpGlyphs(NULL),
|
|
mpCharWidths(NULL),
|
|
mpGlyphs2Chars(NULL),
|
|
mpGlyphAdvances(NULL),
|
|
mpGlyphPositions(NULL),
|
|
mpTypesetter(NULL),
|
|
mpLine(NULL),
|
|
mbHasBoundRectangle(false),
|
|
mnBaseAdvance(0),
|
|
mnCurrentRunIndex(0),
|
|
mnCurrentGlyphIndex(0),
|
|
mnCurrentGlyphRunIndex(0),
|
|
mpRuns(NULL)
|
|
{
|
|
SAL_INFO( "vcl.coretext.layout", "CoreTextLayout::CoreTextLayout() " << this << ", style=" << *style);
|
|
}
|
|
|
|
CoreTextLayout::~CoreTextLayout()
|
|
{
|
|
InvalidateMeasurements();
|
|
SafeCFRelease(mpTypesetter);
|
|
SafeCFRelease(mpLine);
|
|
#ifndef NDEBUG
|
|
delete[] mpSavedStr;
|
|
#endif
|
|
SAL_INFO( "vcl.coretext.layout", "~CoreTextLayout(" << this << ")" );
|
|
}
|
|
|
|
void CoreTextLayout::AdjustLayout( ImplLayoutArgs& rArgs )
|
|
{
|
|
SAL_INFO( "vcl.coretext.layout", "AdjustLayout(" << this << ",rArgs=" << rArgs << ")" );
|
|
|
|
#ifndef NDEBUG
|
|
assert( mnSavedMinCharPos == rArgs.mnMinCharPos );
|
|
assert( mnSavedEndCharPos == rArgs.mnEndCharPos );
|
|
assert( mnCharCount == (mnSavedEndCharPos - mnSavedMinCharPos) );
|
|
assert( memcmp( &mpSavedStr[0],
|
|
&rArgs.mpStr[mnSavedMinCharPos],
|
|
mnCharCount * sizeof( sal_Unicode ) ) == 0 );
|
|
#endif
|
|
|
|
SalLayout::AdjustLayout( rArgs );
|
|
|
|
// adjust positions if requested
|
|
if( rArgs.mpDXArray )
|
|
ApplyDXArray( rArgs );
|
|
else if( rArgs.mnLayoutWidth )
|
|
Justify( rArgs.mnLayoutWidth );
|
|
else
|
|
return;
|
|
}
|
|
|
|
void CoreTextLayout::ApplyDXArray( ImplLayoutArgs& rArgs )
|
|
{
|
|
Justify( rArgs.mpDXArray[mnCharCount-1] );
|
|
}
|
|
|
|
void CoreTextLayout::Justify( long nNewWidth )
|
|
{
|
|
CTLineRef justifiedLine = CTLineCreateJustifiedLine( mpLine, 1.0, nNewWidth - CTLineGetTrailingWhitespaceWidth( mpLine ) );
|
|
if ( !justifiedLine ) {
|
|
SAL_INFO( "vcl.coretext.layout", "Justify(): CTLineCreateJustifiedLine() failed" );
|
|
} else {
|
|
CFRelease( mpLine );
|
|
mpLine = justifiedLine;
|
|
// Justification can change the number of glyphs!
|
|
int oldGLyphCount = mnGlyphCount;
|
|
mnGlyphCount = CTLineGetGlyphCount( mpLine );
|
|
if ( mnGlyphCount != oldGLyphCount )
|
|
SAL_INFO( "vcl.coretext.layout", " glyph count changed, mnGlyphCount=" << mnGlyphCount );
|
|
GetMeasurements();
|
|
}
|
|
}
|
|
|
|
void CoreTextLayout::InvalidateMeasurements()
|
|
{
|
|
if( mpGlyphs ) {
|
|
delete[] mpGlyphs;
|
|
mpGlyphs = NULL;
|
|
}
|
|
if( mpGlyphs2Chars ) {
|
|
delete[] mpGlyphs2Chars;
|
|
mpGlyphs2Chars = NULL;
|
|
}
|
|
if( mpCharWidths ) {
|
|
delete[] mpCharWidths;
|
|
mpCharWidths = NULL;
|
|
}
|
|
if( mpGlyphAdvances ) {
|
|
delete[] mpGlyphAdvances;
|
|
mpGlyphAdvances = NULL;
|
|
}
|
|
if( mpGlyphPositions ) {
|
|
delete[] mpGlyphPositions;
|
|
mpGlyphPositions = NULL;
|
|
}
|
|
mbHasBoundRectangle = false;
|
|
}
|
|
|
|
void CoreTextLayout::DrawText( SalGraphics& rGraphics ) const
|
|
{
|
|
SAL_INFO( "vcl.coretext.layout", "DrawText(" << this << ")" );
|
|
|
|
QuartzSalGraphics& gr = static_cast<QuartzSalGraphics&>(rGraphics);
|
|
if( mnCharCount <= 0 || !gr.CheckContext() )
|
|
return;
|
|
|
|
CGFontRef cg_font = CTFontCopyGraphicsFont(mpStyle->GetFont(), NULL);
|
|
if( !cg_font ) {
|
|
SAL_INFO( "vcl.coretext.layout", "Error cg_font is NULL" );
|
|
return;
|
|
}
|
|
CGContextSaveGState( gr.mrContext );
|
|
CGContextSetFont(gr.mrContext, cg_font);
|
|
CGContextSetFontSize(gr.mrContext, CTFontGetSize(mpStyle->GetFont()));
|
|
CGContextSetTextDrawingMode(gr.mrContext, kCGTextFill);
|
|
CGContextSetShouldAntialias( gr.mrContext, true );
|
|
CGContextSetShouldSubpixelPositionFonts( gr.mrContext, false );
|
|
if( mpStyle->GetColor() ) {
|
|
CGContextSetFillColorWithColor(gr.mrContext, mpStyle->GetColor());
|
|
CGContextSetStrokeColorWithColor(gr.mrContext, mpStyle->GetColor());
|
|
}
|
|
else {
|
|
CGContextSetRGBFillColor(gr.mrContext, 0.0, 0.0, 0.0, 1.0);
|
|
}
|
|
CFRelease(cg_font);
|
|
CGContextSetTextMatrix(gr.mrContext, CGAffineTransformMakeScale(1.0, -1.0));
|
|
CGContextSetShouldAntialias( gr.mrContext, !gr.mbNonAntialiasedText );
|
|
|
|
Point pos = GetDrawPosition(Point(0,0));
|
|
CGPoint posDev = CGContextConvertPointToDeviceSpace(gr.mrContext, CGPointMake(pos.X(), pos.Y()));
|
|
SAL_INFO( "vcl.coretext.layout",
|
|
" context=" << gr.mrContext <<
|
|
" pos=(" << pos.X() << "," << pos.Y() <<")" <<
|
|
" posDev=" << posDev <<
|
|
" font=" << mpStyle->GetFont() );
|
|
|
|
CGContextTranslateCTM(gr.mrContext, pos.X(), pos.Y());
|
|
|
|
CGContextShowGlyphsWithAdvances(gr.mrContext, mpGlyphs, mpGlyphAdvances, mnGlyphCount);
|
|
|
|
#ifndef IOS
|
|
// Request an update of the changed window area. Like in the ATSUI
|
|
// code, I am not sure if this is actually necessary. Once this
|
|
// seems to work fine otherwise, let's try removing this.
|
|
if( gr.IsWindowGraphics() )
|
|
{
|
|
CGRect drawRect = CTLineGetImageBounds( mpLine, gr.mrContext );
|
|
SAL_INFO( "vcl.coretext.layout", "drawRect=" << drawRect );
|
|
if( !CGRectIsNull( drawRect ) ) {
|
|
#if 1
|
|
// For kicks, try the same silly (?) enlarging of the
|
|
// rectangle as in the ATSUI code
|
|
drawRect.origin.y -= drawRect.size.height;
|
|
drawRect.size.height += 2*drawRect.size.height;
|
|
SAL_INFO( "vcl.coretext.layout", "after enlarging drawRect=" << drawRect );
|
|
#endif
|
|
drawRect = CGContextConvertRectToDeviceSpace( gr.mrContext, drawRect );
|
|
SAL_INFO( "vcl.coretext.layout", "after convert: drawRect=" << drawRect );
|
|
gr.RefreshRect( drawRect );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// restore the original graphic context transformations
|
|
CGContextRestoreGState( gr.mrContext );
|
|
}
|
|
|
|
// not needed. CoreText manage fallback directly
|
|
void CoreTextLayout::DropGlyph( int /*nStart*/ )
|
|
{
|
|
}
|
|
|
|
// Note that the "DX array" here is filled with individual character
|
|
// widths, while ImplLayoutArgs::mpDXArray contains cumulative
|
|
// character positions. Consistency is over-rated.
|
|
|
|
long CoreTextLayout::FillDXArray( sal_Int32* pDXArray ) const
|
|
{
|
|
// Short circuit requests which don't need full details
|
|
if( !pDXArray ) {
|
|
return GetTextWidth();
|
|
}
|
|
|
|
// Distribute the widths among the string elements
|
|
long width = 0;
|
|
float scale = mpStyle->GetFontStretchFactor();
|
|
CGFloat accumulatedWidth = 0;
|
|
|
|
std::ostringstream DXArrayInfo;
|
|
for( int i = 0; i < mnCharCount; ++i ) {
|
|
// Convert and adjust for accumulated rounding errors
|
|
accumulatedWidth += mpCharWidths[ i ];
|
|
const long old_width = width;
|
|
width = round_to_long( accumulatedWidth * scale );
|
|
pDXArray[i] = width - old_width;
|
|
#ifdef SAL_LOG_INFO
|
|
if ( i < 7 )
|
|
DXArrayInfo << " " << pDXArray[i];
|
|
else if ( i == 7 )
|
|
DXArrayInfo << "...";
|
|
#endif
|
|
}
|
|
|
|
SAL_INFO( "vcl.coretext.layout", "FillDXArray(" << this << "):" << DXArrayInfo.str() << ", result=" << width );
|
|
|
|
return width;
|
|
}
|
|
|
|
bool CoreTextLayout::GetBoundRect( SalGraphics& rGraphics, Rectangle& rVCLRect ) const
|
|
{
|
|
SAL_INFO( "vcl.coretext.layout", "GetBoundRect(" << this << ")" );
|
|
|
|
QuartzSalGraphics& gr = static_cast<QuartzSalGraphics&>(rGraphics);
|
|
|
|
if( !gr.CheckContext() )
|
|
return false;
|
|
|
|
if ( !mbHasBoundRectangle ) {
|
|
CGRect bound_rect = CTLineGetImageBounds( mpLine, gr.mrContext );
|
|
if ( !CGRectIsNull( bound_rect ) ) {
|
|
maBoundRectangle = Rectangle(
|
|
Point( round_to_long(bound_rect.origin.x * mpStyle->GetFontStretchFactor()),
|
|
round_to_long(bound_rect.origin.y - bound_rect.size.height )),
|
|
Size( round_to_long((bound_rect.size.width + CTLineGetTrailingWhitespaceWidth( mpLine )) * mpStyle->GetFontStretchFactor()),
|
|
round_to_long(bound_rect.size.height)));
|
|
maBoundRectangle.Justify();
|
|
} else {
|
|
maBoundRectangle = Rectangle(
|
|
Point( 0, 0 ),
|
|
Size( round_to_long(CTLineGetTrailingWhitespaceWidth( mpLine ) * mpStyle->GetFontStretchFactor()),
|
|
0 ) );
|
|
maBoundRectangle.Justify();
|
|
}
|
|
mbHasBoundRectangle = true;
|
|
}
|
|
|
|
rVCLRect = maBoundRectangle;
|
|
SAL_INFO( "vcl.coretext.layout", "GetBoundRect() returning with rVCLRect={" << rVCLRect << "}" );
|
|
|
|
return true;
|
|
}
|
|
|
|
void CoreTextLayout::GetCaretPositions( int max_index, sal_Int32* caret_position ) const
|
|
{
|
|
SAL_INFO( "vcl.coretext.layout", "GetCaretPositions(" << this << ",max_index=" << max_index << ")" );
|
|
|
|
int local_max = max_index < mnCharCount * 2 ? max_index : mnCharCount;
|
|
for( int i = 0 ; i < max_index - 1; i+=2 ) {
|
|
CGFloat primary, secondary;
|
|
primary = CTLineGetOffsetForStringIndex(mpLine, i >> 1, &secondary);
|
|
caret_position[i] = round_to_long(mnBaseAdvance + primary);
|
|
caret_position[i+1] = round_to_long(mnBaseAdvance + secondary);
|
|
i += 2;
|
|
}
|
|
for( int i = local_max ; i < max_index ; ++i ) {
|
|
caret_position[i] = -1;
|
|
}
|
|
}
|
|
|
|
int CoreTextLayout::GetNextGlyphs( int nLen, sal_GlyphId* pGlyphIDs, Point& rPos, int& nStart,
|
|
sal_Int32* pGlyphAdvances, int* pCharIndexes ) const
|
|
{
|
|
SAL_INFO( "vcl.coretext.layout", "GetNextGlyphs(" << this << ",nLen=" << nLen << ",nStart=" << nStart << ")" );
|
|
|
|
if( nStart < 0 ) { // first glyph requested?
|
|
nStart = 0;
|
|
mnCurrentRunIndex = 0;
|
|
mnCurrentGlyphIndex = 0;
|
|
mnCurrentGlyphRunIndex = 0;
|
|
}
|
|
else if( nStart >= mnGlyphCount ) {
|
|
mnCurrentRunIndex = 0;
|
|
mnCurrentGlyphIndex = 0;
|
|
mnCurrentGlyphRunIndex = 0;
|
|
return 0;
|
|
}
|
|
if( !mpRuns ) {
|
|
mpRuns = CTLineGetGlyphRuns(mpLine);
|
|
}
|
|
CFIndex nRuns = CFArrayGetCount( mpRuns );
|
|
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex( mpRuns, mnCurrentRunIndex );
|
|
CFIndex nb_glyphs = CTRunGetGlyphCount( run );
|
|
|
|
int i = 0;
|
|
bool first = true;
|
|
while( i < nLen ) {
|
|
if( mnCurrentGlyphRunIndex >= nb_glyphs ) {
|
|
mnCurrentRunIndex += 1;
|
|
if( mnCurrentRunIndex >= nRuns ) {
|
|
break;
|
|
}
|
|
run = (CTRunRef)CFArrayGetValueAtIndex( mpRuns, mnCurrentRunIndex );
|
|
nb_glyphs = CTRunGetGlyphCount( run );
|
|
mnCurrentGlyphRunIndex = 0;
|
|
}
|
|
if( first ) {
|
|
CGPoint first_pos;
|
|
CTRunGetPositions(run, CFRangeMake(mnCurrentGlyphRunIndex,1), &first_pos);
|
|
Point pos(first_pos.x, first_pos.y);
|
|
rPos = GetDrawPosition(pos);
|
|
SAL_INFO( "vcl.coretext.layout", "rPos(" << rPos.X() << "," << rPos.Y() << ")" );
|
|
first = false;
|
|
}
|
|
pGlyphIDs[i] = mpGlyphs[mnCurrentGlyphIndex];
|
|
if( pGlyphAdvances ) {
|
|
pGlyphAdvances[i] = mpGlyphAdvances[mnCurrentGlyphIndex].width;
|
|
}
|
|
if( pCharIndexes ) {
|
|
pCharIndexes[i] = mpGlyphs2Chars[mnCurrentGlyphIndex];
|
|
}
|
|
mnCurrentGlyphIndex += 1;
|
|
mnCurrentGlyphRunIndex += 1;
|
|
i += 1;
|
|
nStart += 1;
|
|
}
|
|
|
|
SAL_INFO( "vcl.coretext.layout", "GetNextGlyphs() returning " << i );
|
|
|
|
return i;
|
|
}
|
|
|
|
int CoreTextLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const
|
|
{
|
|
SAL_INFO( "vcl.coretext.layout", "GetTextBreak(" << this << ",nMaxWidth=" << nMaxWidth << ",nCharExtra=" << nCharExtra << ",nFactor=" << nFactor << ")" );
|
|
|
|
if( !mpLine ) {
|
|
SAL_INFO( "vcl.coretext.layout", "GetTextBreak() returning STRING_LEN" );
|
|
return STRING_LEN;
|
|
}
|
|
|
|
// the semantics of the legacy use case (nCharExtra!=0) cannot be mapped to ATSUBreakLine()
|
|
if( nCharExtra != 0 )
|
|
{
|
|
#if 0
|
|
// prepare the measurement by layouting and measuring the un-expanded/un-condensed text
|
|
if( !InitGIA() )
|
|
return STRING_LEN;
|
|
|
|
// TODO: use a better way than by testing each the char position
|
|
ATSUTextMeasurement nATSUSumWidth = 0;
|
|
const ATSUTextMeasurement nATSUMaxWidth = Vcl2Fixed( nMaxWidth / nFactor );
|
|
const ATSUTextMeasurement nATSUExtraWidth = Vcl2Fixed( nCharExtra ) / nFactor;
|
|
for( int i = 0; i < mnCharCount; ++i ) {
|
|
nATSUSumWidth += mpCharWidths[i];
|
|
if( nATSUSumWidth >= nATSUMaxWidth )
|
|
return (mnMinCharPos + i);
|
|
nATSUSumWidth += nATSUExtraWidth;
|
|
if( nATSUSumWidth >= nATSUMaxWidth )
|
|
if( i+1 < mnCharCount )
|
|
return (mnMinCharPos + i);
|
|
}
|
|
|
|
return STRING_LEN;
|
|
#endif
|
|
}
|
|
|
|
// get a quick overview on what could fit
|
|
const CGFloat nPixelWidth = (nMaxWidth - (nCharExtra * mnCharCount)) / nFactor;
|
|
if( nPixelWidth <= 0 ) {
|
|
SAL_INFO( "vcl.coretext.layout", "GetTextBreak(): nPixelWidth=" << nPixelWidth << ", returning mnMinCharPos=" << mnMinCharPos );
|
|
return mnMinCharPos;
|
|
}
|
|
|
|
CFIndex nBreakPos = CTTypesetterSuggestLineBreak( mpTypesetter, 0, nPixelWidth ) + mnMinCharPos;
|
|
|
|
// upper layers expect STRING_LEN if everything fits
|
|
if( nBreakPos >= mnEndCharPos ) {
|
|
SAL_INFO( "vcl.coretext.layout", "GetTextBreak(): nBreakPos=" << nBreakPos << " >= mnEndCharPos=" << mnEndCharPos << ", returning STRING_LEN" );
|
|
return STRING_LEN;
|
|
}
|
|
|
|
SAL_INFO( "vcl.coretext.layout", "GetTextBreak() returning nBreakPos=" << nBreakPos );
|
|
|
|
return nBreakPos;
|
|
}
|
|
|
|
long CoreTextLayout::GetTextWidth() const
|
|
{
|
|
CGContextRef context = mpGraphics->GetContext();
|
|
if (!context) {
|
|
SAL_INFO( "vcl.coretext.layout", "GetTextWidth(): no context!?");
|
|
return 0;
|
|
}
|
|
CGRect bound_rect = CTLineGetImageBounds(mpLine, context);
|
|
long w = round_to_long((bound_rect.size.width + CTLineGetTrailingWhitespaceWidth(mpLine)) * mpStyle->GetFontStretchFactor());
|
|
|
|
SAL_INFO( "vcl.coretext.layout", "GetTextWidth(" << this << ") returning " << w );
|
|
|
|
return w;
|
|
}
|
|
|
|
bool CoreTextLayout::LayoutText( ImplLayoutArgs& rArgs)
|
|
{
|
|
SAL_INFO( "vcl.coretext.layout", "LayoutText(" << this << ",rArgs=" << rArgs << ")" );
|
|
|
|
mnCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
|
|
|
|
/* don't layout empty (or worse negative size) strings */
|
|
if(mnCharCount <= 0)
|
|
return false;
|
|
|
|
#ifndef NDEBUG
|
|
mnSavedMinCharPos = rArgs.mnMinCharPos;
|
|
mnSavedEndCharPos = rArgs.mnEndCharPos;
|
|
mpSavedStr = new sal_Unicode[mnCharCount];
|
|
memcpy( mpSavedStr, &rArgs.mpStr[mnSavedMinCharPos], mnCharCount * sizeof( sal_Unicode ) );
|
|
#endif
|
|
|
|
// Note that unlike the ATSUI code, we store only the part of the
|
|
// buffer addressed by mnMinCharPos--mnEndCharPos. Not the whole
|
|
// buffer. I.e. all indexing of the string as referenced to by
|
|
// mpTypesetter should be relative to mnMinCharPos.
|
|
CFStringRef string = CFStringCreateWithCharacters( NULL, &(rArgs.mpStr[rArgs.mnMinCharPos]), mnCharCount );
|
|
if ( !string ) {
|
|
SAL_INFO( "vcl.coretext.layout", " CFStringCreateWithCharacter() returned NULL, returning false" );
|
|
return false;
|
|
}
|
|
|
|
CFStringRef keys[1];
|
|
CFTypeRef values[1];
|
|
|
|
keys[0] = kCTFontAttributeName;
|
|
values[0] = CFRetain( mpStyle->GetFont() );
|
|
|
|
CFDictionaryRef attributes = CFDictionaryCreate( kCFAllocatorDefault,
|
|
(const void**)&keys,
|
|
(const void**)&values,
|
|
1,
|
|
&kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks );
|
|
|
|
CFAttributedStringRef attributed_string = CFAttributedStringCreate( NULL, string, attributes );
|
|
CFRelease( string );
|
|
CFRelease( attributes );
|
|
if ( !attributed_string ) {
|
|
SAL_INFO( "vcl.coretext.layout", " CFAttributedStringCreate() returned NULL, returning false" );
|
|
return false;
|
|
}
|
|
|
|
mpTypesetter = CTTypesetterCreateWithAttributedString( attributed_string );
|
|
CFRelease( attributed_string );
|
|
if ( !mpTypesetter ) {
|
|
SAL_INFO( "vcl.coretext.layout", " CTTypesetterCreateWithAttributedString() returned NULL, returning false" );
|
|
return false;
|
|
}
|
|
|
|
mpLine = CTTypesetterCreateLine( mpTypesetter, CFRangeMake( 0, 0 ) );
|
|
if ( !mpLine ) {
|
|
SAL_INFO( "vcl.coretext.layout", " CTTypesetterCreateLine() returned NULL, returning false" );
|
|
return false;
|
|
}
|
|
|
|
mnGlyphCount = CTLineGetGlyphCount( mpLine );
|
|
|
|
GetMeasurements();
|
|
|
|
SAL_INFO( "vcl.coretext.layout", "LayoutText() returning, mnGlyphCount=" << mnGlyphCount );
|
|
|
|
return true;
|
|
}
|
|
|
|
void CoreTextLayout::GetMeasurements()
|
|
{
|
|
InvalidateMeasurements();
|
|
|
|
mpGlyphs = new CGGlyph[ mnGlyphCount ];
|
|
mpCharWidths = new CGFloat[ mnCharCount ];
|
|
mpGlyphs2Chars = new int[ mnGlyphCount ];
|
|
mpGlyphAdvances = new CGSize[ mnGlyphCount ];
|
|
mpGlyphPositions = new CGPoint[ mnGlyphCount ];
|
|
|
|
CFArrayRef runs = CTLineGetGlyphRuns( mpLine );
|
|
const CFIndex nRuns = CFArrayGetCount( runs );
|
|
|
|
CFIndex lineGlyphIx = 0;
|
|
for ( CFIndex runIx = 0; runIx < nRuns; runIx++ )
|
|
{
|
|
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex( runs, runIx );
|
|
if ( !run )
|
|
continue;
|
|
|
|
std::ostringstream glyphPositionInfo;
|
|
std::ostringstream glyphAdvancesInfo;
|
|
std::ostringstream charWidthInfo;
|
|
|
|
const CFIndex runGlyphCount = CTRunGetGlyphCount( run );
|
|
if ( runGlyphCount )
|
|
{
|
|
assert( lineGlyphIx + runGlyphCount <= mnGlyphCount );
|
|
|
|
const CFIndex lineRunGlyphStartIx = lineGlyphIx;
|
|
(void) lineRunGlyphStartIx;
|
|
|
|
CFIndex runStringIndices[ runGlyphCount ];
|
|
CTRunGetStringIndices( run, CFRangeMake( 0, 0 ), runStringIndices );
|
|
|
|
CTRunGetGlyphs( run, CFRangeMake( 0, 0 ), &mpGlyphs[ lineGlyphIx ] );
|
|
|
|
CTRunGetPositions( run, CFRangeMake( 0, 0 ), &mpGlyphPositions[ lineGlyphIx ] );
|
|
CTRunGetAdvances( run, CFRangeMake( 0, 0 ), &mpGlyphAdvances[ lineGlyphIx ] );
|
|
|
|
bool isVerticalRun = false;
|
|
CFDictionaryRef aDict = CTRunGetAttributes( run );
|
|
if ( aDict ) {
|
|
const CFBooleanRef aValue = (const CFBooleanRef)CFDictionaryGetValue( aDict, kCTVerticalFormsAttributeName );
|
|
isVerticalRun = (aValue == kCFBooleanTrue);
|
|
}
|
|
|
|
for ( CFIndex runGlyphIx = 0 ; runGlyphIx < runGlyphCount; lineGlyphIx++, runGlyphIx++ )
|
|
{
|
|
const CFIndex charIx = runStringIndices[ runGlyphIx ];
|
|
assert( charIx < mnCharCount );
|
|
mpGlyphs2Chars[ lineGlyphIx ] = charIx;
|
|
|
|
mpCharWidths[ charIx ] = mpGlyphAdvances[ lineGlyphIx ].width;
|
|
}
|
|
#ifdef SAL_LOG_INFO
|
|
for ( int i = 0; i < runGlyphCount; i++ ) {
|
|
const int ix = lineRunGlyphStartIx + i;
|
|
if ( i < 7 ) {
|
|
glyphPositionInfo << " " << mpGlyphs[ ix ] << "@" << mpGlyphPositions[ ix ];
|
|
glyphAdvancesInfo << " " << mpGlyphAdvances[ ix ];
|
|
} else if (i == 7 ) {
|
|
glyphPositionInfo << "...";
|
|
glyphAdvancesInfo << "...";
|
|
}
|
|
}
|
|
SAL_INFO( "vcl.coretext.layout", " run " << runIx << ": " << runGlyphCount << " glyphs:" << glyphPositionInfo.str() );
|
|
SAL_INFO( "vcl.coretext.layout", " run " << runIx << ": advances:" << glyphAdvancesInfo.str() );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef SAL_LOG_INFO
|
|
std::ostringstream charWidthInfo;
|
|
|
|
for ( int ix = 0; ix < mnCharCount; ix++ ) {
|
|
if ( ix < 7 )
|
|
charWidthInfo << " " << mpCharWidths[ ix ];
|
|
else if ( ix == 7 )
|
|
charWidthInfo << "...";
|
|
}
|
|
SAL_INFO( "vcl.coretext.layout", " char widths:" << charWidthInfo.str() );
|
|
#endif
|
|
}
|
|
|
|
|
|
// not needed. CoreText manage fallback directly
|
|
void CoreTextLayout::MoveGlyph( int /*nStart*/, long /*nNewXPos*/ )
|
|
{
|
|
}
|
|
|
|
// not needed. CoreText manage fallback directly
|
|
void CoreTextLayout::Simplify( bool /*bIsBase*/ )
|
|
{
|
|
}
|
|
|
|
SalLayout* QuartzSalGraphics::GetTextLayout( ImplLayoutArgs&, int /*nFallbackLevel*/ )
|
|
{
|
|
CoreTextLayout* layout = new CoreTextLayout( this, m_style );
|
|
|
|
return layout;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|