r16365 by mrdocs - Pierre's patch for OTF kerning resolves #7910

scribus-commit scribus-commit at lists.scribus.net
Wed Feb 16 10:31:37 CET 2011


Author: mrdocs
Date: Wed Feb 16 09:31:37 2011
New Revision: 16365

URL: http://scribus.info/websvn/listing.php?repname=Scribus&sc=1&rev=16365
Log:
Pierre's patch for OTF kerning resolves #7910

Modified:
    branches/Version135/Scribus/scribus/fonts/ftface.h
    branches/Version135/Scribus/scribus/fonts/scface_ttf.cpp
    branches/Version135/Scribus/scribus/fonts/scface_ttf.h

Modified: branches/Version135/Scribus/scribus/fonts/ftface.h
URL: http://scribus.info/websvn/diff.php?repname=Scribus&rev=16365&path=/branches/Version135/Scribus/scribus/fonts/ftface.h
==============================================================================
--- branches/Version135/Scribus/scribus/fonts/ftface.h (original)
+++ branches/Version135/Scribus/scribus/fonts/ftface.h Wed Feb 16 09:31:37 2011
@@ -81,7 +81,7 @@
 	
 	uint         char2CMap(QChar ch)                         const;
 
-	qreal       glyphKerning (uint gl1, uint gl2, qreal sz) const;
+	virtual qreal       glyphKerning (uint gl1, uint gl2, qreal sz) const;
 //	GlyphMetrics glyphBBox (uint gl,               qreal sz) const;
 
 	void RawData   (QByteArray & bb)            const;

Modified: branches/Version135/Scribus/scribus/fonts/scface_ttf.cpp
URL: http://scribus.info/websvn/diff.php?repname=Scribus&rev=16365&path=/branches/Version135/Scribus/scribus/fonts/scface_ttf.cpp
==============================================================================
--- branches/Version135/Scribus/scribus/fonts/scface_ttf.cpp (original)
+++ branches/Version135/Scribus/scribus/fonts/scface_ttf.cpp Wed Feb 16 09:31:37 2011
@@ -8,6 +8,7 @@
 #include <QFile>
 #include <QString>
 #include <QObject>
+#include <QDebug>
 
 #include <sys/types.h>
 
@@ -16,6 +17,363 @@
 #include "util.h"
 #include "scconfig.h"
 
+KernFeature::KernFeature ( FT_Face face )
+		:m_valid ( true )
+{
+	FontName = QString ( face->family_name ) + " " + QString ( face->style_name )   ;
+// 	qDebug() <<"KF"<<FontName;
+// 	QTime t;
+// 	t.start();
+	FT_ULong length = 0;
+	if ( !FT_Load_Sfnt_Table ( face, TTAG_GPOS , 0, NULL, &length ) )
+	{
+// 		qDebug() <<"\t"<<"GPOS table len"<<length;
+		if ( length > 32 )
+		{
+			GPOSTableRaw.resize ( length );
+			FT_Load_Sfnt_Table ( face, TTAG_GPOS, 0, reinterpret_cast<FT_Byte*> ( GPOSTableRaw.data() ), &length );
+
+			makeCoverage();
+		}
+		else
+			m_valid = false;
+
+		GPOSTableRaw.clear();
+		coverages.clear();
+	}
+	else
+		m_valid = false;
+
+	if ( !m_valid )
+		pairs.clear();
+// 	qDebug() <<"\t"<<m_valid;
+// 	qDebug() <<"\t"<<t.elapsed();
+}
+
+KernFeature::KernFeature ( const KernFeature & kf )
+{
+	m_valid = kf.m_valid;
+	if ( m_valid )
+		pairs = kf.pairs;
+
+}
+
+
+KernFeature::~ KernFeature()
+{
+}
+
+double KernFeature::getPairValue ( unsigned int glyph1, unsigned int glyph2 ) const
+{
+	if ( m_valid )
+	{
+		if ( pairs.contains ( glyph1 ) )
+		{
+			if ( pairs[glyph1].contains ( glyph2 ) )
+			{
+				return pairs[glyph1][glyph2];
+			}
+		}
+	}
+	return 0.0;
+}
+
+void KernFeature::makeCoverage()
+{
+	if ( GPOSTableRaw.isEmpty() )
+		return;
+
+	quint16 FeatureList_Offset= toUint16 ( 6 );
+	quint16 LookupList_Offset = toUint16 ( 8 );
+
+	// Find the offsets of the kern feature tables
+	quint16 FeatureCount = toUint16 ( FeatureList_Offset );;
+	QList<quint16> FeatureKern_Offset;
+	for ( quint16 FeatureRecord ( 0 ); FeatureRecord < FeatureCount; ++ FeatureRecord )
+	{
+		int rawIdx ( FeatureList_Offset + 2 + ( 6 * FeatureRecord ) );
+		quint32 tag ( FT_MAKE_TAG ( GPOSTableRaw.at ( rawIdx ),
+					    GPOSTableRaw.at ( rawIdx + 1 ),
+					    GPOSTableRaw.at ( rawIdx + 2 ),
+					    GPOSTableRaw.at ( rawIdx + 3 ) ) );
+		if ( tag == TTAG_kern )
+		{
+			FeatureKern_Offset << ( toUint16 ( rawIdx + 4 ) + FeatureList_Offset );
+
+		}
+	}
+
+	// Extract indices of lookups for feture kern
+	QList<quint16> LookupListIndex;
+	foreach ( quint16 kern, FeatureKern_Offset )
+	{
+		quint16 LookupCount ( toUint16 ( kern + 2 ) );
+		for ( int llio ( 0 ) ; llio < LookupCount; ++llio )
+		{
+			quint16 Idx ( toUint16 ( kern + 4 + ( llio * 2 ) ) );
+			if ( !LookupListIndex.contains ( Idx ) )
+			{
+				LookupListIndex <<Idx ;
+			}
+		}
+	}
+
+
+	// Extract offsets of lookup tables for feature kern
+	QList<quint16> LookupTables;
+	QList<quint16> PairAdjustmentSubTables;
+	for ( int i ( 0 ); i < LookupListIndex.count(); ++i )
+	{
+		int rawIdx ( LookupList_Offset + 2 + ( LookupListIndex[i] * 2 ) );
+		quint16 Lookup ( toUint16 ( rawIdx )  + LookupList_Offset );
+		quint16 SubTableCount ( toUint16 ( Lookup + 4 ) );
+		for ( int stIdx ( 0 ); stIdx < SubTableCount; ++ stIdx )
+		{
+			quint16 SubTable ( toUint16 ( Lookup + 6 + ( 2 * stIdx ) ) + Lookup );
+
+// 			quint16 PosFormat ( toUint16 ( SubTable ) );
+			quint16 Coverage_Offset ( toUint16 ( SubTable + 2 ) + SubTable );
+			quint16 CoverageFormat ( toUint16 ( Coverage_Offset ) );
+
+			if ( 1 == CoverageFormat ) // glyph indices based
+			{
+				quint16 GlyphCount ( toUint16 ( Coverage_Offset + 2 ) );
+				quint16 GlyphID ( Coverage_Offset + 4 );
+
+				for ( unsigned int gl ( 0 ); gl < GlyphCount; ++gl )
+				{
+					coverages[SubTable] << toUint16 ( GlyphID + ( gl * 2 ) );
+				}
+			}
+			else if ( 2 == CoverageFormat ) // Coverage Format2 => ranges based
+			{
+				quint16 RangeCount ( toUint16 ( Coverage_Offset + 2 ) );
+
+// 				int gl_base ( 0 );
+				for ( int r ( 0 ); r < RangeCount; ++r )
+				{
+					quint16 rBase ( Coverage_Offset + 4 + ( r * 6 ) );
+					quint16 Start ( toUint16 ( rBase ) );
+					quint16 End ( toUint16 ( rBase + 2 ) );
+// 					quint16 StartCoverageIndex ( toUint16 ( rBase + 4 ) );
+					for ( unsigned int gl ( Start ); gl <= End; ++gl )
+						coverages[SubTable]  << gl;
+				}
+			}
+			else
+			{
+//				qDebug() <<"Unknow Coverage Format:"<<CoverageFormat;
+				continue;
+			}
+
+			makePairs ( SubTable );
+		}
+
+	}
+
+
+}
+
+
+void KernFeature::makePairs ( quint16 subtableOffset )
+{
+	/*
+	Lookup Type 2:
+	Pair Adjustment Positioning Subtable
+	*/
+
+	quint16 PosFormat ( toUint16 ( subtableOffset ) );
+
+	if ( PosFormat == 1 )
+	{
+		quint16 ValueFormat1 ( toUint16 ( subtableOffset +4 ) );
+		quint16 ValueFormat2 ( toUint16 ( subtableOffset +6 ) );
+		quint16 PairSetCount ( toUint16 ( subtableOffset +8 ) );
+		if ( ValueFormat1 && ValueFormat2 )
+		{
+			for ( int psIdx ( 0 ); psIdx < PairSetCount; ++ psIdx )
+			{
+				unsigned int FirstGlyph ( coverages[subtableOffset][psIdx] );
+				quint16 PairSetOffset ( toUint16 ( subtableOffset +10 + ( 2 * psIdx ) ) +  subtableOffset );
+				quint16 PairValueCount ( toUint16 ( PairSetOffset ) );
+				quint16 PairValueRecord ( PairSetOffset + 2 );
+				for ( int pvIdx ( 0 );pvIdx < PairValueCount; ++pvIdx )
+				{
+					quint16 recordBase ( PairValueRecord + ( ( 2 + 2 + 2 ) * pvIdx ) );
+					quint16 SecondGlyph ( toUint16 ( recordBase ) );
+					qint16 Value1 ( toInt16 ( recordBase + 2 ) );
+					pairs[FirstGlyph][SecondGlyph] = double ( Value1 );
+
+				}
+
+			}
+		}
+		else if ( ValueFormat1 && ( !ValueFormat2 ) )
+		{
+			for ( int psIdx ( 0 ); psIdx < PairSetCount; ++ psIdx )
+			{
+				unsigned int FirstGlyph ( coverages[subtableOffset][psIdx] );
+				quint16 PairSetOffset ( toUint16 ( subtableOffset +10 + ( 2 * psIdx ) ) +  subtableOffset );
+				quint16 PairValueCount ( toUint16 ( PairSetOffset ) );
+				quint16 PairValueRecord ( PairSetOffset + 2 );
+				for ( int pvIdx ( 0 );pvIdx < PairValueCount; ++pvIdx )
+				{
+					quint16 recordBase ( PairValueRecord + ( ( 2 + 2 ) * pvIdx ) );
+					quint16 SecondGlyph ( toUint16 ( recordBase ) );
+					qint16 Value1 ( toInt16 ( recordBase + 2 ) );
+					pairs[FirstGlyph][SecondGlyph] = double ( Value1 );
+
+				}
+
+			}
+		}
+		else
+		{
+//			qDebug() <<"ValueFormat1 is null or both ValueFormat1 and ValueFormat2 are null";
+		}
+
+	}
+	else if ( PosFormat == 2 )
+	{
+		quint16 ValueFormat1 ( toUint16 ( subtableOffset +4 ) );
+		quint16 ValueFormat2 ( toUint16 ( subtableOffset +6 ) );
+		quint16 ClassDef1 ( toUint16 ( subtableOffset +8 )  + subtableOffset );
+		quint16 ClassDef2 ( toUint16 ( subtableOffset +10 ) + subtableOffset );
+		quint16 Class1Count ( toUint16 ( subtableOffset +12 ) );
+		quint16 Class2Count ( toUint16 ( subtableOffset +14 ) );
+		quint16 Class1Record ( subtableOffset +16 );
+
+		// first extract classses
+		ClassDefTable Class1Data ( getClass ( ClassDef1 , subtableOffset ) );
+		ClassDefTable Class2Data ( getClass ( ClassDef2 , subtableOffset ) );
+
+		if ( ValueFormat1 && ValueFormat2 )
+		{
+			for ( quint16 C1 ( 0 );C1 < Class1Count; ++C1 )
+			{
+				QList<quint16> Class1 ( Class1Data[C1] );
+				quint16 Class2Record ( Class1Record + ( C1 * ( 2 * 2 * Class2Count ) ) );
+				for ( quint16 C2 ( 0 );C2 < Class2Count; ++C2 )
+				{
+					qint16 Value1 ( toInt16 ( Class2Record + ( C2 * ( 2 * 2 ) ) ) );
+					QList<quint16> Class2 ( Class2Data[C2] );
+					// keep it barbarian :D
+					foreach ( quint16 FirstGlyph, Class1 )
+					{
+						foreach ( quint16 SecondGlyph, Class2 )
+						{
+							if ( Value1 )
+								pairs[FirstGlyph][SecondGlyph] = double ( Value1 );
+						}
+					}
+				}
+			}
+		}
+		else if ( ValueFormat1 && ( !ValueFormat2 ) )
+		{
+			for ( quint16 C1 ( 0 );C1 < Class1Count; ++C1 )
+			{
+				QString cdbg ( QString::number ( C1 ).rightJustified ( 5,QChar ( 32 ) ) );
+				QList<quint16> Class1 ( Class1Data[C1] );
+				quint16 Class2Record ( Class1Record + ( C1 * ( 2 * Class2Count ) ) );
+				for ( quint16 C2 ( 0 );C2 < Class2Count; ++C2 )
+				{
+					qint16 Value1 ( toInt16 ( Class2Record + ( C2 * 2 ) ) );
+					QList<quint16> Class2 ( Class2Data[C2] );
+
+					foreach ( quint16 FirstGlyph, Class1 )
+					{
+						foreach ( quint16 SecondGlyph, Class2 )
+						{
+							if ( Value1 )
+								pairs[FirstGlyph][SecondGlyph] = double ( Value1 );
+						}
+					}
+				}
+			}
+		}
+		else
+		{
+//			qDebug() <<"ValueFormat1 is null or both ValueFormat1 and ValueFormat2 are null";
+		}
+
+	}
+	else
+		qDebug() <<"unknown PosFormat"<<PosFormat;
+}
+
+KernFeature::ClassDefTable KernFeature::getClass ( quint16 classDefOffset, quint16 coverageId )
+{
+	ClassDefTable ret;
+	ret[0] = coverages[coverageId];
+	quint16 ClassFormat ( toUint16 ( classDefOffset ) );
+	if ( ClassFormat == 1 )
+	{
+		quint16 StartGlyph ( toUint16 ( classDefOffset +2 ) );
+		quint16 GlyphCount ( toUint16 ( classDefOffset +4 ) );
+		quint16 ClassValueArray ( classDefOffset + 6 );
+
+		for ( quint16 CV ( 0 );CV < GlyphCount; ++CV )
+		{
+			ret[0].removeAll ( StartGlyph + CV );
+			ret[ toUint16 ( ClassValueArray + ( CV * 2 ) ) ] << StartGlyph + CV;
+		}
+	}
+	else if ( ClassFormat == 2 )
+	{
+		quint16 ClassRangeCount ( toUint16 ( classDefOffset + 2 ) );
+		quint16 ClassRangeRecord ( classDefOffset + 4 );
+		for ( int CRR ( 0 ); CRR < ClassRangeCount; ++CRR )
+		{
+			quint16 Start ( toUint16 ( ClassRangeRecord + ( CRR * 6 ) ) );
+			quint16 End ( toUint16 ( ClassRangeRecord + ( CRR * 6 ) + 2 ) );
+			quint16 Class ( toUint16 ( ClassRangeRecord + ( CRR * 6 ) + 4 ) );
+
+			for ( quint16 gl ( Start ); gl <= End; ++gl )
+			{
+				ret[0].removeAll ( gl );
+				ret[Class] << gl;
+			}
+		}
+	}
+	else
+		qDebug() <<"Unknown Class Table type";
+
+	return ret;
+}
+
+quint16 KernFeature::toUint16 ( quint16 index )
+{
+	if ( ( index + 2 ) > GPOSTableRaw.count() )
+	{
+//                qDebug() << "HORROR!" << index << GPOSTableRaw.count() << FontName ;
+		// Rather no kerning at all than random kerning
+// 		m_valid = false;
+		return 0;
+	}
+	// FIXME I just do not know how it has to be done *properly*
+	quint16 c1 ( GPOSTableRaw.at ( index ) );
+	quint16 c2 ( GPOSTableRaw.at ( index + 1 ) );
+	c1 &= 0xFF;
+	c2 &= 0xFF;
+	quint16 ret ( ( c1 << 8 ) | c2 );
+	return ret;
+}
+
+qint16 KernFeature::toInt16 ( quint16 index )
+{
+	if ( ( index + 2 ) > GPOSTableRaw.count() )
+	{
+		return 0;
+	}
+	// FIXME I just do not know how it has to be done *properly*
+	quint16 c1 ( GPOSTableRaw.at ( index ) );
+	quint16 c2 ( GPOSTableRaw.at ( index + 1 ) );
+	c1 &= 0xFF;
+	c2 &= 0xFF;
+	qint16 ret ( ( c1 << 8 ) | c2 );
+	return ret;
+}
 
 
 namespace {
@@ -53,6 +411,46 @@
 	return true;
 }
 } //namespace
+
+ScFace_ttf::ScFace_ttf ( QString fam, QString sty, QString alt, QString scname, QString psname, QString path, int face )
+		: FtFace ( fam, sty, alt, scname, psname, path, face )
+{
+	formatCode = ScFace::SFNT;
+	kernFeature = 0;
+}
+
+ScFace_ttf::~ ScFace_ttf()
+{
+	if ( kernFeature )
+		delete kernFeature;
+}
+
+
+void ScFace_ttf::load() const
+{
+	if ( !kernFeature )
+		kernFeature = new KernFeature ( ftFace() );
+	FtFace::load();
+}
+
+void ScFace_ttf::unload() const
+{
+	if ( kernFeature )
+		delete kernFeature;
+	kernFeature = 0;
+	FtFace::unload();
+}
+
+qreal ScFace_ttf::glyphKerning ( uint gl1, uint gl2, qreal sz ) const
+{
+	if ( kernFeature->isValid() )
+	{
+		return kernFeature->getPairValue ( gl1,gl2 ) / m_uniEM * sz;
+
+	}
+	else
+		return FtFace::glyphKerning ( gl1, gl2, sz );
+}
 
 
 void ScFace_ttf::RawData(QByteArray & bb) const {

Modified: branches/Version135/Scribus/scribus/fonts/scface_ttf.h
URL: http://scribus.info/websvn/diff.php?repname=Scribus&rev=16365&path=/branches/Version135/Scribus/scribus/fonts/scface_ttf.h
==============================================================================
--- branches/Version135/Scribus/scribus/fonts/scface_ttf.h (original)
+++ branches/Version135/Scribus/scribus/fonts/scface_ttf.h Wed Feb 16 09:31:37 2011
@@ -10,6 +10,70 @@
 #include "scribusapi.h"
 #include "fonts/ftface.h"
 
+
+#include FT_TRUETYPE_TABLES_H
+#include FT_TRUETYPE_TAGS_H
+
+
+/**
+	An object holding a table of kerning pairs extracted from
+	a kern feature such as found in a GPOS table
+ */
+class SCRIBUS_API KernFeature
+{
+	public:
+		/**
+		* Build a ready-to-use kerning pairs table
+		* @param face a valid FT_Face, It won’t be store by KernFeature
+		 */
+		KernFeature ( FT_Face face );
+		KernFeature ( const KernFeature& kf );
+		~KernFeature();
+
+		/**
+		 * Get the kerning value for a pair of glyph indexes.
+		 * @param glyph1 Index of the left glyph in logical order
+		 * @param glyph2 Index of the right glyph in logical order
+		 * @return the unscaled delta to apply to xadvance of the first glyph
+		 */
+		double getPairValue ( unsigned int glyph1, unsigned int glyph2 ) const;
+
+		/**
+		 * The table can have been invalidated if something went wrong at any moment.
+		 * @return True if valid, False otherwise.
+		 */
+		bool isValid() const {return m_valid;}
+
+	private:
+		bool m_valid;
+		QByteArray GPOSTableRaw;
+		QMap<quint16,QList<quint16> > coverages;
+		QMap<quint16, QMap<quint16, double> > pairs;
+
+		void makeCoverage();
+		void makePairs ( quint16 subtableOffset );
+
+		typedef QMap<quint16, QList<quint16> > ClassDefTable; // <Class , list<GLyph> >
+		ClassDefTable getClass ( quint16 classDefOffset, quint16 coverageId );
+		inline quint16 toUint16 ( quint16 index );
+		inline qint16 toInt16 ( quint16 index );
+
+		enum ValueFormat
+		{
+			XPlacement = 0x0001,
+			YPlacement = 0x0002,
+			XAdvance = 0x0004,
+			YAdvance = 0x0008,
+			XPlaDevice =0x0010,
+			YPlaDevice =0x0020,
+			XAdvDevice =0x0040,
+			YAdvDevice =0x0080
+		};
+		QString FontName;// for debugging purpose
+};
+
+
+
 /*
 	Class ScFace_ttf
 	Subclass of ScFace, specifically for TrueType fonts.
@@ -19,13 +83,19 @@
 class SCRIBUS_API ScFace_ttf : public FtFace
 {
 public:
-	ScFace_ttf(QString fam, QString sty, QString alt, QString scname, QString psname, QString path, int face) 
-	: FtFace(fam, sty, alt, scname, psname, path, face )
-	{
-		formatCode = ScFace::SFNT;
-	}
+	ScFace_ttf ( QString fam, QString sty, QString alt, QString scname, QString psname, QString path, int face );
+	~ScFace_ttf();
+
+	void load () const;
+	void unload () const;
+
 	bool EmbedFont(QString &str) const;
 	void RawData(QByteArray & bb) const;
+
+	qreal glyphKerning ( uint gl1, uint gl2, qreal sz ) const;
+
+private:
+	mutable KernFeature * kernFeature;
 };
 
 #endif




More information about the scribus-commit mailing list