|
|
|
/* This file is part of the KDE project
|
|
|
|
Copyright (C) 2002, The Karbon Developers
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Library General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
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
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <tqdom.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <KoPoint.h>
|
|
|
|
#include <KoRect.h>
|
|
|
|
|
|
|
|
#include "vpath.h"
|
|
|
|
#include "vtext.h"
|
|
|
|
#include "vtext_iface.h"
|
|
|
|
#include "vstroke.h"
|
|
|
|
#include "vfill.h"
|
|
|
|
#include "vvisitor.h"
|
|
|
|
#include "vsegment.h"
|
|
|
|
#include "vgroup.h"
|
|
|
|
#include "vpainter.h"
|
|
|
|
#include "commands/vtransformcmd.h"
|
|
|
|
|
|
|
|
#ifdef HAVE_KARBONTEXT
|
|
|
|
|
|
|
|
#include <ft2build.h>
|
|
|
|
#include <fontconfig/fontconfig.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include FT_FREETYPE_H
|
|
|
|
#include FT_OUTLINE_H
|
|
|
|
#include FT_GLYPH_H
|
|
|
|
|
|
|
|
#define FT_TOFLOAT(x) ((x) * (1.0 / 64.0))
|
|
|
|
#define FT_FROMFLOAT(x) ((int) floor ((x) * 64.0 + 0.5))
|
|
|
|
|
|
|
|
|
|
|
|
// Trace routines for ttf / ps font -> VSubpath
|
|
|
|
|
|
|
|
int traceMoveto( FT_Vector *to, VPath *composite )
|
|
|
|
{
|
|
|
|
double tox = ( to->x / 64.0 );
|
|
|
|
double toy = ( -to->y / 64.0 );
|
|
|
|
|
|
|
|
//TQString add = "M" + TQString::number(tox) + "," + TQString::number(toy) + " ";
|
|
|
|
//kdDebug(38000) << add.latin1() << endl;
|
|
|
|
composite->moveTo( KoPoint( tox, toy ) );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int traceLineto( FT_Vector *to, VPath *composite )
|
|
|
|
{
|
|
|
|
double tox = ( to->x / 64.0 );
|
|
|
|
double toy = ( -to->y / 64.0 );
|
|
|
|
|
|
|
|
//TQString add = "L" + TQString::number(tox) + "," + TQString::number(toy) + " ";
|
|
|
|
//kdDebug(38000) << add.latin1() << endl;
|
|
|
|
|
|
|
|
composite->lineTo( KoPoint( tox, toy ) );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int traceQuadraticBezier( FT_Vector *control, FT_Vector *to, VPath *composite )
|
|
|
|
{
|
|
|
|
double x1 = ( control->x / 64.0 );
|
|
|
|
double y1 = ( -control->y / 64.0 );
|
|
|
|
double x2 = ( to->x / 64.0 );
|
|
|
|
double y2 = ( -to->y / 64.0 );
|
|
|
|
|
|
|
|
//TQString add = "Q" + TQString::number(x1) + "," + TQString::number(y1) + "," + TQString::number(x2) + "," + TQString::number(y2) + " ";
|
|
|
|
//kdDebug(38000) << add.latin1() << endl;
|
|
|
|
composite->curveTo( KoPoint( x1, y1 ), KoPoint( x2, y2 ), KoPoint( x2, y2 ) );
|
|
|
|
//composite->curve2To( KoPoint( x1, y1 ), KoPoint( x2, y2 ) );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int traceCubicBezier( FT_Vector *p, FT_Vector *q, FT_Vector *to, VPath *composite )
|
|
|
|
{
|
|
|
|
double x1 = ( p->x / 64.0 );
|
|
|
|
double y1 = ( -p->y / 64.0 );
|
|
|
|
double x2 = ( q->x / 64.0 );
|
|
|
|
double y2 = ( -q->y / 64.0 );
|
|
|
|
double x3 = ( to->x / 64.0 );
|
|
|
|
double y3 = ( -to->y / 64.0 );
|
|
|
|
|
|
|
|
//TQString add = "C" + TQString::number(x1) + "," + TQString::number(y1) + "," + TQString::number(x2) + "," + TQString::number(y2) + "," + TQString::number(x3) + "," + TQString::number(y3);
|
|
|
|
//kdDebug(38000) << add.latin1() << endl;
|
|
|
|
|
|
|
|
composite->curveTo( KoPoint( x1, y1 ), KoPoint( x2, y2 ), KoPoint( x3, y3 ) );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
FT_Outline_Funcs OutlineMethods =
|
|
|
|
{
|
|
|
|
(FT_Outline_MoveTo_Func) traceMoveto,
|
|
|
|
(FT_Outline_LineTo_Func) traceLineto,
|
|
|
|
(FT_Outline_ConicTo_Func) traceQuadraticBezier,
|
|
|
|
(FT_Outline_CubicTo_Func) traceCubicBezier,
|
|
|
|
0,
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif // HAVE_KARBONTEXT
|
|
|
|
|
|
|
|
VText::VText( VObject* parent, VState state )
|
|
|
|
: VObject( parent, state ), m_basePath( 0L )
|
|
|
|
{
|
|
|
|
m_glyphs.setAutoDelete( true );
|
|
|
|
m_boundingBoxIsInvalid = true;
|
|
|
|
m_stroke = new VStroke( this );
|
|
|
|
m_fill = new VFill();
|
|
|
|
m_position = (VText::Position)0;
|
|
|
|
m_alignment = (VText::Alignment)0;
|
|
|
|
m_shadow = false;
|
|
|
|
m_translucentShadow = false;
|
|
|
|
m_shadowAngle = 0;
|
|
|
|
m_shadowDistance = 0;
|
|
|
|
m_offset = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
VText::VText( const TQFont &font, const VSubpath& basePath, Position position, Alignment alignment, const TQString& text )
|
|
|
|
: VObject( 0L ), m_font( font ), m_basePath( basePath ), m_position( position ), m_alignment( alignment ), m_text( text )
|
|
|
|
{
|
|
|
|
m_glyphs.setAutoDelete( true );
|
|
|
|
m_boundingBoxIsInvalid = true;
|
|
|
|
m_stroke = new VStroke( this );
|
|
|
|
m_fill = new VFill();
|
|
|
|
m_offset = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
VText::VText( const VText& text )
|
|
|
|
: VObject( text ), m_font( text.m_font ), m_basePath( text.m_basePath ), m_position( text.m_position ), m_alignment( text.m_alignment ), m_text( text.m_text ), m_shadow( text.m_shadow ), m_translucentShadow( text.m_translucentShadow ), m_shadowDistance( text.m_shadowDistance ), m_shadowAngle( text.m_shadowAngle ), m_offset( text.m_offset )
|
|
|
|
{
|
|
|
|
m_stroke = new VStroke( *text.m_stroke );
|
|
|
|
m_stroke->setParent( this );
|
|
|
|
m_fill = new VFill( *text.m_fill );
|
|
|
|
|
|
|
|
// copy glyphs
|
|
|
|
VPathListIterator itr( text.m_glyphs );
|
|
|
|
for( ; itr.current() ; ++itr )
|
|
|
|
{
|
|
|
|
VPath* c = itr.current()->clone();
|
|
|
|
c->setParent( this );
|
|
|
|
m_glyphs.append( c );
|
|
|
|
}
|
|
|
|
|
|
|
|
m_boundingBoxIsInvalid = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
VText::~VText()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
DCOPObject* VText::dcopObject()
|
|
|
|
{
|
|
|
|
if( !m_dcop )
|
|
|
|
m_dcop = new VTextIface( this );
|
|
|
|
|
|
|
|
return m_dcop;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
VText::draw( VPainter* painter, const KoRect* /*rect*/ ) const
|
|
|
|
{
|
|
|
|
if(
|
|
|
|
state() == deleted ||
|
|
|
|
state() == hidden ||
|
|
|
|
state() == hidden_locked )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
painter->save();
|
|
|
|
|
|
|
|
VPathListIterator itr( m_glyphs );
|
|
|
|
|
|
|
|
if( state() != edit )
|
|
|
|
{
|
|
|
|
// paint fill:
|
|
|
|
painter->newPath();
|
|
|
|
|
|
|
|
if ( m_shadow )
|
|
|
|
{
|
|
|
|
VColor color;
|
|
|
|
if ( m_translucentShadow )
|
|
|
|
{
|
|
|
|
color.set( 0., 0., 0. );
|
|
|
|
color.setOpacity( .3 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
color.set( .3, .3, .3 );
|
|
|
|
color.setOpacity( 1. );
|
|
|
|
}
|
|
|
|
int shadowDx = int( m_shadowDistance * cos( m_shadowAngle / 360. * 6.2832 ) );
|
|
|
|
int shadowDy = int( m_shadowDistance * sin( m_shadowAngle / 360. * 6.2832 ) );
|
|
|
|
|
|
|
|
VTransformCmd trafo( 0L, TQWMatrix() );
|
|
|
|
for( itr.toFirst(); itr.current(); ++itr )
|
|
|
|
{
|
|
|
|
trafo.setMatrix( TQWMatrix( 1, 0, 0, 1, shadowDx, shadowDy ) );
|
|
|
|
trafo.visit( *( itr.current() ) );
|
|
|
|
itr.current()->setFill( VFill( color ) );
|
|
|
|
itr.current()->setStroke( VStroke( color ) );
|
|
|
|
itr.current()->draw( painter );
|
|
|
|
trafo.setMatrix( TQWMatrix( 1, 0, 0, 1, -shadowDx, -shadowDy ) );
|
|
|
|
trafo.visit( *( itr.current() ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for( itr.toFirst(); itr.current(); ++itr )
|
|
|
|
{
|
|
|
|
itr.current()->setFill( *m_fill );
|
|
|
|
itr.current()->setStroke( *m_stroke );
|
|
|
|
itr.current()->draw( painter );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw simplistic contour:
|
|
|
|
if( state() == edit )//|| state() == selected )
|
|
|
|
{
|
|
|
|
painter->newPath();
|
|
|
|
painter->setRasterOp( TQt::XorROP );
|
|
|
|
painter->setPen( TQt::yellow );
|
|
|
|
painter->setBrush( TQt::NoBrush );
|
|
|
|
|
|
|
|
for( itr.toFirst(); itr.current(); ++itr )
|
|
|
|
itr.current()->draw( painter );
|
|
|
|
|
|
|
|
painter->strokePath();
|
|
|
|
}
|
|
|
|
|
|
|
|
painter->restore();
|
|
|
|
}
|
|
|
|
|
|
|
|
const KoRect&
|
|
|
|
VText::boundingBox() const
|
|
|
|
{
|
|
|
|
if( m_boundingBoxIsInvalid )
|
|
|
|
{
|
|
|
|
VPathListIterator itr( m_glyphs );
|
|
|
|
itr.toFirst();
|
|
|
|
// clear:
|
|
|
|
m_boundingBox = itr.current() ? itr.current()->boundingBox() : KoRect();
|
|
|
|
for( ++itr; itr.current(); ++itr )
|
|
|
|
if( !itr.current()->boundingBox().isEmpty() )
|
|
|
|
m_boundingBox |= itr.current()->boundingBox();
|
|
|
|
|
|
|
|
// take line width into account:
|
|
|
|
m_boundingBox.setCoords(
|
|
|
|
m_boundingBox.left() - 0.5 * stroke()->lineWidth(),
|
|
|
|
m_boundingBox.top() - 0.5 * stroke()->lineWidth(),
|
|
|
|
m_boundingBox.right() + 0.5 * stroke()->lineWidth(),
|
|
|
|
m_boundingBox.bottom() + 0.5 * stroke()->lineWidth() );
|
|
|
|
|
|
|
|
m_boundingBoxIsInvalid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_boundingBox;
|
|
|
|
}
|
|
|
|
|
|
|
|
VText*
|
|
|
|
VText::clone() const
|
|
|
|
{
|
|
|
|
return new VText( *this );
|
|
|
|
}
|
|
|
|
|
|
|
|
VGroup* VText::toVGroup() const
|
|
|
|
{
|
|
|
|
VGroup* group = new VGroup( parent() );
|
|
|
|
|
|
|
|
VPathListIterator itr( m_glyphs );
|
|
|
|
for( itr.toFirst(); itr.current(); ++itr )
|
|
|
|
{
|
|
|
|
VPath* c = itr.current()->clone();
|
|
|
|
c->setParent( group );
|
|
|
|
group->append( c );
|
|
|
|
}
|
|
|
|
|
|
|
|
group->setFill( *m_fill );
|
|
|
|
group->setStroke( *m_stroke );
|
|
|
|
|
|
|
|
return group;
|
|
|
|
} // VText::toVGroup
|
|
|
|
|
|
|
|
void
|
|
|
|
VText::save( TQDomElement& element ) const
|
|
|
|
{
|
|
|
|
if( state() != deleted )
|
|
|
|
{
|
|
|
|
TQDomElement me = element.ownerDocument().createElement( "TEXT" );
|
|
|
|
|
|
|
|
VPath path( 0L );
|
|
|
|
path.combinePath( m_basePath );
|
|
|
|
path.save( me );
|
|
|
|
|
|
|
|
VObject::save( me );
|
|
|
|
|
|
|
|
// save font properties
|
|
|
|
me.setAttribute( "text", m_text );
|
|
|
|
me.setAttribute( "family", m_font.family() );
|
|
|
|
me.setAttribute( "size", m_font.pointSize() );
|
|
|
|
me.setAttribute( "italic", m_font.italic() );
|
|
|
|
me.setAttribute( "bold", m_font.bold() );
|
|
|
|
me.setAttribute( "position", m_position );
|
|
|
|
me.setAttribute( "alignment", m_alignment );
|
|
|
|
me.setAttribute( "shadow", m_shadow );
|
|
|
|
me.setAttribute( "translucentshadow", m_translucentShadow );
|
|
|
|
me.setAttribute( "shadowangle", m_shadowAngle );
|
|
|
|
me.setAttribute( "shadowdist", m_shadowDistance );
|
|
|
|
me.setAttribute( "offset", m_offset );
|
|
|
|
element.appendChild( me );
|
|
|
|
|
|
|
|
// save all glyphs / paths
|
|
|
|
VPathListIterator itr = m_glyphs;
|
|
|
|
for( itr.toFirst(); itr.current(); ++itr )
|
|
|
|
itr.current()->save( me );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
VText::load( const TQDomElement& element )
|
|
|
|
{
|
|
|
|
m_glyphs.clear();
|
|
|
|
|
|
|
|
m_font.setFamily( element.attribute( "family", "Times" ) );
|
|
|
|
m_font.setPointSize( element.attribute( "size", "12" ).toInt() );
|
|
|
|
m_font.setItalic( element.attribute( "italic" ).toInt() == 1 );
|
|
|
|
m_font.setWeight( TQFont::Normal );
|
|
|
|
m_font.setBold( element.attribute( "bold" ).toInt() == 1 );
|
|
|
|
m_position = (Position)element.attribute( "position", "0" ).toInt();
|
|
|
|
m_alignment = (Alignment)element.attribute( "alignment", "0" ).toInt();
|
|
|
|
m_shadow = ( element.attribute( "shadow" ).toInt() == 1 );
|
|
|
|
m_translucentShadow = ( element.attribute( "translucentshadow" ).toInt() == 1 );
|
|
|
|
m_shadowAngle = element.attribute( "shadowangle" ).toInt();
|
|
|
|
m_shadowDistance = element.attribute( "shadowdist" ).toInt();
|
|
|
|
m_offset = element.attribute( "offset" ).toDouble();
|
|
|
|
m_text = element.attribute( "text", "" );
|
|
|
|
|
|
|
|
VObject::load( element );
|
|
|
|
|
|
|
|
TQDomNodeList list = element.childNodes();
|
|
|
|
TQDomElement e = list.item( 0 ).toElement();
|
|
|
|
|
|
|
|
// element to start with reading glyph paths and stroke, fill, etc.
|
|
|
|
uint startElement = 0;
|
|
|
|
|
|
|
|
if( e.tagName() == "PATH" )
|
|
|
|
{
|
|
|
|
VPath path( 0L );
|
|
|
|
path.load( e );
|
|
|
|
m_basePath = *path.paths().getFirst();
|
|
|
|
startElement++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// load text glyphs:
|
|
|
|
for( uint i = startElement; i < list.count(); ++i )
|
|
|
|
{
|
|
|
|
if( list.item( i ).isElement() )
|
|
|
|
{
|
|
|
|
e = list.item( i ).toElement();
|
|
|
|
if( e.tagName() == "PATH" )
|
|
|
|
{
|
|
|
|
VPath *composite = new VPath( this );
|
|
|
|
composite->load( e );
|
|
|
|
m_glyphs.append( composite );
|
|
|
|
}
|
|
|
|
if( e.tagName() == "STROKE" )
|
|
|
|
m_stroke->load( e );
|
|
|
|
if( e.tagName() == "FILL" )
|
|
|
|
m_fill->load( e );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if no glyphs yet, trace them
|
|
|
|
#ifdef HAVE_KARBONTEXT
|
|
|
|
if( m_glyphs.count() == 0 )
|
|
|
|
traceText();
|
|
|
|
#endif
|
|
|
|
m_boundingBoxIsInvalid = true;
|
|
|
|
//m_fill->setFillRule( VFill::evenOdd );
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
VText::setText( const TQString& text )
|
|
|
|
{
|
|
|
|
if( m_text != text )
|
|
|
|
{
|
|
|
|
m_text = text;
|
|
|
|
m_glyphs.clear();
|
|
|
|
#ifdef HAVE_KARBONTEXT
|
|
|
|
traceText();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
VText::setState( const VState state )
|
|
|
|
{
|
|
|
|
VObject::setState( state );
|
|
|
|
|
|
|
|
VPathListIterator itr( m_glyphs );
|
|
|
|
for( itr.toFirst(); itr.current(); ++itr )
|
|
|
|
{
|
|
|
|
itr.current()->setState( state );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
VText::accept( VVisitor& visitor )
|
|
|
|
{
|
|
|
|
visitor.visitVText( *this );
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_KARBONTEXT
|
|
|
|
|
|
|
|
void
|
|
|
|
VText::traceText()
|
|
|
|
{
|
|
|
|
if( m_basePath.count() == 0 )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "Can't draw a text without base path (was: " << m_text << ")." << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO : set more options
|
|
|
|
int slant = FC_SLANT_ROMAN;
|
|
|
|
if( m_font.italic() )
|
|
|
|
slant = FC_SLANT_ITALIC;
|
|
|
|
|
|
|
|
int weight = 0;
|
|
|
|
if( m_font.bold() )
|
|
|
|
weight = FC_WEIGHT_BOLD;
|
|
|
|
|
|
|
|
// Build FontConfig request pattern
|
|
|
|
int id = -1;
|
|
|
|
TQString filename = buildRequest( m_font.family(), weight, slant, m_font.pointSize(), id );
|
|
|
|
m_glyphs.clear();
|
|
|
|
|
|
|
|
kdDebug(38000) << "Loading " << filename.latin1() << " for requested font \"" << TQString(m_font.family()).latin1() << "\", " << m_font.pointSize() << " pt." << endl;
|
|
|
|
|
|
|
|
FT_UInt glyphIndex;
|
|
|
|
FT_Face fontFace;
|
|
|
|
// TODO : this lib should probably be a singleton (Rob)
|
|
|
|
FT_Library library;
|
|
|
|
FT_Init_FreeType( &library );
|
|
|
|
FT_Error error = FT_New_Face( library, TQFile::encodeName(filename), id, &fontFace );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), could not load font. Aborting!" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool foundCharmap = false;
|
|
|
|
|
|
|
|
// Try to choose unicode charmap
|
|
|
|
for( int charmap = 0; charmap < fontFace->num_charmaps; charmap++ )
|
|
|
|
{
|
|
|
|
if( fontFace->charmaps[charmap]->encoding == ft_encoding_unicode )
|
|
|
|
{
|
|
|
|
FT_Error error = FT_Set_Charmap( fontFace, fontFace->charmaps[charmap] );
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), unable to select unicode charmap." << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
foundCharmap = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Choose first charmap if no unicode charmap was found
|
|
|
|
if( ! foundCharmap )
|
|
|
|
{
|
|
|
|
error = FT_Set_Charmap( fontFace, fontFace->charmaps[0] );
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), unable to select charmap. Aborting!" << endl;
|
|
|
|
FT_Done_Face( fontFace );
|
|
|
|
FT_Done_FreeType( library );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
error = FT_Set_Char_Size( fontFace, FT_FROMFLOAT( m_font.pointSize() ), FT_FROMFLOAT( m_font.pointSize() ), 0, 0 );
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), unable to set font size. Aborting!" << endl;
|
|
|
|
FT_Done_Face( fontFace );
|
|
|
|
FT_Done_FreeType( library );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// storing glyphs.
|
|
|
|
float l = 0;
|
|
|
|
TQValueList<float> glyphXAdvance;
|
|
|
|
TQValueList<float> glyphYAdvance;
|
|
|
|
for( unsigned int i = 0; i < m_text.length(); i++ )
|
|
|
|
{
|
|
|
|
// get the glyph index for the current character
|
|
|
|
TQChar character = m_text.at( i );
|
|
|
|
glyphIndex = FT_Get_Char_Index( fontFace, character.unicode() );
|
|
|
|
if( ! glyphIndex )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), unable get index of char : " << character << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//kdDebug(38000) << "glyphIndex : " << glyphIndex << endl;
|
|
|
|
FT_Error error = FT_Load_Glyph( fontFace, glyphIndex, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP );
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), unable to load glyph : " << error << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// decompose to vpaths
|
|
|
|
FT_OutlineGlyph g;
|
|
|
|
error = FT_Get_Glyph( fontFace->glyph, reinterpret_cast<FT_Glyph *>( &g ) );
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), unable to get glyph: " << error << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
VPath *composite = new VPath( this );
|
|
|
|
error = FT_Outline_Check( &g->outline );
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), outline is broken : " << error << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
error = FT_Outline_Decompose(&g->outline, &OutlineMethods, composite );
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "traceText(), unable to decompose outline : " << error << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_glyphs.append( composite );
|
|
|
|
glyphXAdvance.append( FT_TOFLOAT( fontFace->glyph->advance.x ) );
|
|
|
|
glyphYAdvance.append( FT_TOFLOAT( fontFace->glyph->advance.y ) );
|
|
|
|
l += FT_TOFLOAT( fontFace->glyph->advance.x );
|
|
|
|
FT_Done_Glyph( reinterpret_cast<FT_Glyph>( g ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Placing the stored glyphs.
|
|
|
|
float pathLength = 0;
|
|
|
|
VSubpathIterator pIt( m_basePath );
|
|
|
|
|
|
|
|
VSegment* seg;
|
|
|
|
for( ; pIt.current(); ++pIt )
|
|
|
|
if( (seg = pIt.current() ) )
|
|
|
|
pathLength += seg->length();
|
|
|
|
|
|
|
|
kdDebug(38000) << "traceText(), using offset : " << m_offset << endl;
|
|
|
|
float x = m_offset * pathLength;
|
|
|
|
|
|
|
|
switch( m_alignment )
|
|
|
|
{
|
|
|
|
case Left: x += 0; break;
|
|
|
|
case Center: x -= 0.5 * l; break;
|
|
|
|
case Right: x -= l; break;
|
|
|
|
}
|
|
|
|
float y = 0;
|
|
|
|
float dx = 0;
|
|
|
|
float sp = 0;
|
|
|
|
KoPoint point;
|
|
|
|
KoPoint normal;
|
|
|
|
KoPoint tangent;
|
|
|
|
VSubpathIterator pathIt( m_basePath );
|
|
|
|
VSegment* oldSeg = pathIt.current();
|
|
|
|
seg = ++pathIt;
|
|
|
|
KoPoint extPoint;
|
|
|
|
bool ext = false;
|
|
|
|
float fsx = 0;
|
|
|
|
float yoffset = ( m_position == Above ? 0 : ( m_position == On ? m_font.pointSize() / 3 : m_font.pointSize() / 1.5 ) );
|
|
|
|
kdDebug(38000) << "Position: " << m_position << " -> " << yoffset << endl;
|
|
|
|
for( unsigned int i = 0; i < m_text.length(); i++ )
|
|
|
|
{
|
|
|
|
VPath* composite = m_glyphs.at( i );
|
|
|
|
if( ! composite )
|
|
|
|
continue;
|
|
|
|
// Step 1: place (0, 0) to the rotation center of the glyph.
|
|
|
|
dx = *glyphXAdvance.at( i ) / 2;
|
|
|
|
x += dx;
|
|
|
|
VTransformCmd trafo( 0L, TQWMatrix( 1, 0, 0, 1, -dx, y + yoffset ) );
|
|
|
|
trafo.visit( *composite );
|
|
|
|
|
|
|
|
// Step 2: find the position where to draw.
|
|
|
|
// 3 possibilities: before, on, and after the basePath...
|
|
|
|
if ( x < 0 )
|
|
|
|
{
|
|
|
|
if( !ext )
|
|
|
|
seg->pointTangentNormalAt( 0, &extPoint, &tangent, &normal );
|
|
|
|
point = extPoint + x * tangent;
|
|
|
|
ext = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
while ( seg && x > fsx + seg->length() )
|
|
|
|
{
|
|
|
|
fsx += seg->length();
|
|
|
|
oldSeg = seg;
|
|
|
|
seg = ++pathIt;
|
|
|
|
}
|
|
|
|
if( seg )
|
|
|
|
{
|
|
|
|
ext = false;
|
|
|
|
sp = ( x - fsx ) / seg->length();
|
|
|
|
seg->pointTangentNormalAt( sp, &point, &tangent, &normal );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if( !ext )
|
|
|
|
oldSeg->pointTangentNormalAt( 1, &extPoint, &tangent, &normal );
|
|
|
|
point = extPoint + ( x - fsx ) * tangent;
|
|
|
|
ext = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 3: transform glyph and append it. That's it, we've got
|
|
|
|
// text following a path. Really easy, isn't it ;) ?
|
|
|
|
trafo.setMatrix( TQWMatrix( tangent.x(), tangent.y(), tangent.y(), -tangent.x(), point.x(), point.y() ) );
|
|
|
|
trafo.visit( *composite );
|
|
|
|
composite->setState( state() );
|
|
|
|
|
|
|
|
//kdDebug(38000) << "Glyph: " << (TQString)character << " [String pos: " << x << ", " << y << " / Canvas pos: " << point.x() << ", " << point.y() << "]" << endl;
|
|
|
|
|
|
|
|
x += dx;
|
|
|
|
y += *glyphYAdvance.at( i );
|
|
|
|
}
|
|
|
|
FT_Done_Face( fontFace );
|
|
|
|
FT_Done_FreeType( library );
|
|
|
|
m_boundingBoxIsInvalid = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This routine is copied from KSVGFont (Rob)
|
|
|
|
TQString
|
|
|
|
VText::buildRequest( TQString family, int weight, int slant, double size, int &id )
|
|
|
|
{
|
|
|
|
// Strip those stupid [Xft or whatever]...
|
|
|
|
int pos;
|
|
|
|
if( ( pos = family.find( '[' ) ) )
|
|
|
|
family = family.left( pos );
|
|
|
|
|
|
|
|
// Use FontConfig to locate & select fonts and use FreeType2 to open them
|
|
|
|
FcPattern *pattern;
|
|
|
|
TQString fileName;
|
|
|
|
|
|
|
|
pattern = FcPatternBuild( 0, FC_WEIGHT, FcTypeInteger, weight,
|
|
|
|
FC_SLANT, FcTypeInteger, slant,
|
|
|
|
FC_SIZE, FcTypeDouble, size, NULL );
|
|
|
|
|
|
|
|
// Add font name
|
|
|
|
FcPatternAddString( pattern, FC_FAMILY, reinterpret_cast<const FcChar8 *>( family.latin1() ) );
|
|
|
|
|
|
|
|
// Disable hinting
|
|
|
|
FcPatternAddBool( pattern, FC_HINTING, FcFalse );
|
|
|
|
// Enforce scalability
|
|
|
|
FcPatternAddBool( pattern, FC_SCALABLE, FcTrue );
|
|
|
|
|
|
|
|
// Perform the default font pattern modification operations.
|
|
|
|
FcDefaultSubstitute( pattern );
|
|
|
|
FcConfigSubstitute( FcConfigGetCurrent(), pattern, FcMatchPattern );
|
|
|
|
|
|
|
|
FcResult result;
|
|
|
|
|
|
|
|
// we dont want to use bitmap fonts, so get a list of fonts sorted by closeness to pattern
|
|
|
|
// and use the best matching scalable font
|
|
|
|
FcFontSet *fset = FcFontSort( 0, pattern, FcFalse, 0L, &result );
|
|
|
|
|
|
|
|
// Destroy pattern
|
|
|
|
FcPatternDestroy( pattern );
|
|
|
|
|
|
|
|
if( fset )
|
|
|
|
{
|
|
|
|
FcBool scalable;
|
|
|
|
FcChar8 *temp;
|
|
|
|
|
|
|
|
// iterate over font list and take best scaleable font
|
|
|
|
for( int i = 0; i < fset->nfont; ++i )
|
|
|
|
{
|
|
|
|
pattern = fset->fonts[i];
|
|
|
|
if( FcResultMatch != FcPatternGetBool( pattern, FC_SCALABLE, 0, &scalable ) )
|
|
|
|
continue;
|
|
|
|
if( scalable == FcTrue )
|
|
|
|
{
|
|
|
|
// Get index & filename
|
|
|
|
if( FcPatternGetString(pattern, FC_FILE, 0, &temp) != FcResultMatch ||
|
|
|
|
FcPatternGetInteger(pattern, FC_INDEX, 0, &id) != FcResultMatch )
|
|
|
|
{
|
|
|
|
kdDebug(38000) << "VText::buildRequest(), could not load font file for requested font \"" << family.latin1() << "\"" << endl;
|
|
|
|
return TQString();
|
|
|
|
}
|
|
|
|
|
|
|
|
fileName = TQFile::decodeName(reinterpret_cast<const char *>( temp ));
|
|
|
|
|
|
|
|
// get family name of matched font
|
|
|
|
TQString newFamily;
|
|
|
|
|
|
|
|
if( FcResultMatch == FcPatternGetString( pattern, FC_FAMILY, 0, &temp ) )
|
|
|
|
m_font.setFamily( reinterpret_cast<const char *>( temp ) );
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FcFontSetDestroy( fset );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return fileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VText::setOffset( double offset )
|
|
|
|
{
|
|
|
|
if( offset < 0.0 )
|
|
|
|
m_offset = 0.0;
|
|
|
|
else if( offset > 1.0 )
|
|
|
|
m_offset = 1.0;
|
|
|
|
else
|
|
|
|
m_offset = offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // HAVE_KARBONTEXT
|