[fondue-commits] [SCM] Fondue Font Editor branch, master, updated. cb80ff8c06e34e34cd88cee1e2ba8b569d27ef4f

Eugeniy Meshcheryakov eugen at debian.org
Wed Apr 2 12:16:51 UTC 2008


The following commit has been merged in the master branch:
commit a77770ca326e6de6f42b35e39bda39949e8fdf2c
Author: Eugeniy Meshcheryakov <eugen at debian.org>
Date:   Wed Apr 2 12:57:06 2008 +0200

    Add SFD import filter written in C++

diff --git a/Makefile.am b/Makefile.am
index 0e9d6b3..5b3cf94 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -14,6 +14,7 @@ include gui/gui.rules
 include ruby/ruby.rules
 include scripts/scripts.rules
 include qscript/qscript.rules
+include filters/filters.rules
 
 EXTRA_DIST += data/instructions.xml schema/fondue-font.rng
 
@@ -26,4 +27,4 @@ EXTRA_DIST += data/instructions.xml schema/fondue-font.rng
 .awk.awked.cxx:
 	$(AWK) -f $< $(DATAFILE) > $@
 
-INCLUDES = -I$(srcdir)/src -I$(srcdir)/ruby -I$(srcdir)/gui -I$(srcdir)/nongui -I$(srcdir)/qscript
+INCLUDES = -I$(srcdir)/src -I$(srcdir)/ruby -I$(srcdir)/gui -I$(srcdir)/nongui -I$(srcdir)/qscript -I$(srcdir)/filters
diff --git a/filters/filters.rules b/filters/filters.rules
new file mode 100644
index 0000000..235fde2
--- /dev/null
+++ b/filters/filters.rules
@@ -0,0 +1,12 @@
+noinst_LIBRARIES += libfonduefilters.a
+
+libfonduefilters_a_SOURCES =		\
+	filters/sfdimportfilter.cxx
+
+libfonduefilters_a_CPPFLAGS = $(QtCore_CFLAGS)
+
+noinst_HEADERS +=			\
+	filters/sfdimportfilter.h	\
+	filters/importfilterbase.h
+
+## vim:ft=automake
diff --git a/gui/colorcombobox.h b/filters/importfilterbase.h
similarity index 62%
copy from gui/colorcombobox.h
copy to filters/importfilterbase.h
index c94a5fe..41e21ea 100644
--- a/gui/colorcombobox.h
+++ b/filters/importfilterbase.h
@@ -1,4 +1,4 @@
-/* Copyright © 2007 Євгеній Мещеряков <eugen at debian.org>
+/* Copyright © 2008 Євгеній Мещеряков <eugen at debian.org>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -13,19 +13,20 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-#ifndef COLORCOMBOBOX_H
-#define COLORCOMBOBOX_H
-#include <QComboBox>
+#ifndef IMPORTFILTERBASE_H
+#define IMPORTFILTERBASE_H
+#include <QString>
+#include <QStringList>
 
-class QColor;
+class QFile;
+class FontDocument;
 
-class ColorComboBox : public QComboBox {
-	Q_OBJECT
-	Q_PROPERTY(QColor color READ color WRITE setColor USER true)
+class ImportFilterBase {
 public:
-	ColorComboBox(QWidget *parent = 0);
-	QColor color() const;
-	void setColor(QColor c);
+	virtual QString menuName() const = 0;
+	virtual QString fileMask() const = 0;
+	virtual FontDocument *importFile(QFile *file) = 0;
+	virtual QStringList errors() const {return QStringList();}
 };
 
 #endif
diff --git a/filters/sfdimportfilter.cxx b/filters/sfdimportfilter.cxx
new file mode 100644
index 0000000..99f6382
--- /dev/null
+++ b/filters/sfdimportfilter.cxx
@@ -0,0 +1,517 @@
+/* Copyright © 2008 Євгеній Мещеряков <eugen at debian.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "sfdimportfilter.h"
+#include <QFile>
+#include "fontdocument.h"
+#include <QTextStream>
+#include <QDebug>
+#include <QStringList>
+#include "glyphcontour.h"
+#include "glyphreference.h"
+
+class SFDImportFilterPriv {
+public:
+	SFDImportFilterPriv(SFDImportFilter *filter);
+	FontDocument *importFile(QFile *file);
+private:
+	bool readSFD(FontDocument *doc, QTextStream *sfd);
+	QString readTTTable(QTextStream *sfd);
+	bool readCvt(FontDocument *doc, QTextStream *sfd);
+	bool readMaxp(FontDocument *doc, QTextStream *sfd);
+	bool readGlyphs(FontDocument *doc, QTextStream *sfd);
+	Glyph *readGlyph(const QString &name, QTextStream *sfd);
+	bool readGlyphContent(Glyph *g, QTextStream *sfd);
+	bool processReferences(FontDocument *doc);
+
+	SFDImportFilter *m_filter;
+	QHash<int, QString> toNameMap;
+	QHash<QString, QStringList> references;
+};
+
+QString SFDImportFilter::menuName() const
+{
+	return "FontForge SFD File...";
+}
+
+QString SFDImportFilter::fileMask() const
+{
+	return "FontForge SFD files (*.sfd)";
+}
+
+FontDocument *SFDImportFilter::importFile(QFile *file)
+{
+	Q_ASSERT(file);
+
+	SFDImportFilterPriv priv(this);
+	m_errors.clear();
+	return priv.importFile(file);
+}
+
+QStringList SFDImportFilter::errors() const
+{
+	return m_errors;
+}
+
+void SFDImportFilter::addError(const QString &text)
+{
+	m_errors << QString("Error: %1").arg(text);
+}
+
+/***************************** Private part ********************************/
+
+SFDImportFilterPriv::SFDImportFilterPriv(SFDImportFilter *filter) : m_filter(filter)
+{
+	Q_ASSERT(m_filter);
+}
+
+FontDocument *SFDImportFilterPriv::importFile(QFile *file)
+{
+	Q_ASSERT(file);
+
+	QTextStream sfd(file);
+	sfd.setCodec("UTF-8"); // FIXME
+
+	FontDocument *doc = new FontDocument;
+
+	if (readSFD(doc, &sfd))
+		return doc;
+	delete doc;
+	return 0;
+}
+
+QString SFDImportFilterPriv::readTTTable(QTextStream *sfd)
+{
+	Q_ASSERT(sfd);
+
+	QStringList code;
+
+	for (;;) {
+		QString line = sfd->readLine();
+		if (line.isNull()) {
+			m_filter->addError("Unexpected end of file");
+			return QString();
+		}
+		if (line == "EndTTInstrs")
+			break;
+		code << line;
+	}
+
+	// XXX FIXME TODO convert instructions
+	return code.join("\n");
+}
+
+bool SFDImportFilterPriv::readCvt(FontDocument *doc, QTextStream *sfd)
+{
+	Q_ASSERT(doc);
+	Q_ASSERT(sfd);
+
+	for (;;) {
+		QString line = sfd->readLine();
+		if (line.isNull()) {
+			m_filter->addError("Unexpected end of file");
+			return false;
+		}
+		if (line == "EndShort")
+			break;
+		doc->addCvtEntry(line.toInt());
+	}
+	return true;
+}
+
+bool SFDImportFilterPriv::readMaxp(FontDocument *doc, QTextStream *sfd)
+{
+	Q_ASSERT(doc);
+	Q_ASSERT(sfd);
+
+	QString line;
+
+#define IGNORE()							\
+	do {								\
+		line = sfd->readLine();					\
+		if (line.isNull()) {					\
+			m_filter->addError("Unexpected end of file");	\
+			return false;					\
+		}							\
+		if (line == "EndShort") {				\
+			m_filter->addError("Bad file format: too short 'maxp' table");\
+			return false;					\
+		}							\
+	} while (0)
+
+#define READ_AND_CHECK(fun)						\
+	do {								\
+		line = sfd->readLine();					\
+		if (line.isNull()) {					\
+			m_filter->addError("Unexpected end of file");	\
+			return false;					\
+		}							\
+		if (line == "EndShort") {				\
+			m_filter->addError("Bad file format: too short 'maxp' table");\
+			return false;					\
+		}							\
+		doc->fun(line.toInt());					\
+	} while(0)
+
+	IGNORE(); // table version (1.0), TODO maybe check?
+	IGNORE();
+	IGNORE(); // Number of glyphs
+	IGNORE(); // max points
+	IGNORE(); // max contours
+	IGNORE(); // max component points
+	IGNORE(); // max component contours
+	READ_AND_CHECK(setZonesCount);
+	READ_AND_CHECK(setMaxTwilightPoints);
+	READ_AND_CHECK(setMaxStorage);
+	READ_AND_CHECK(setMaxFDEFs);
+	READ_AND_CHECK(setMaxIDEFs);
+	READ_AND_CHECK(setMaxStackDepth);
+	IGNORE(); // max size of instructions
+	IGNORE(); // max component elements
+	IGNORE(); // max depth
+
+	line = sfd->readLine();
+	if (line.isNull()) {
+		m_filter->addError("Unexpected end of file");
+		return false;
+	}
+	if (line != "EndShort") {
+		m_filter->addError("Bad file format: too long 'maxp' table");
+		return false;
+	}
+	return true;
+}
+
+bool SFDImportFilterPriv::readSFD(FontDocument *doc, QTextStream *sfd)
+{
+	// FIXME TODO check if file is realy SFD
+	Q_ASSERT(doc);
+	Q_ASSERT(sfd);
+
+	bool ret = true;
+
+	for (;;) {
+		QString line = sfd->readLine();
+		if (line.isNull()) {
+			m_filter->addError("Unexpected end of file");
+			return false;
+		}
+
+		if (line.trimmed() == "EndSplineFont")
+			break;
+
+		int delimIdx = line.indexOf(": ");
+		if (delimIdx != -1) {
+			QString tag = line.left(delimIdx);
+			QString value = line.right(line.length() - delimIdx - 2);
+
+			if (tag == "BeginChars")
+				ret = readGlyphs(doc, sfd);
+			else if (tag == "Copyright") {
+				value.replace("\\n", "\n");
+				doc->setCopyright(value);
+			}
+			else if (tag == "FullName")
+				doc->setFontName(value);
+			else if (tag == "FamilyName")
+				doc->setFontFamily(value);
+			else if (tag == "Weight")
+				doc->setFontWeight(value);
+			else if (tag == "Ascent")
+				doc->setAscent(value.toDouble());
+			else if (tag == "Descent")
+				doc->setDescent(value.toDouble());
+			else if (tag == "ItalicAngle")
+				doc->setItalicAngle(value.toDouble());
+			else if (tag == "UnderlinePosition")
+				doc->setUnderlinePosition(value.toDouble());
+			else if (tag == "UnderlineWidth")
+				doc->setUnderlineThickness(value.toDouble());
+			else if (tag == "TtTable") {
+				QString table = readTTTable(sfd);
+				if (!table.isNull()) {
+					if (value == "prep")
+						doc->setPrep(table);
+					else if (value == "fpgm")
+						doc->setFpgm(table);
+					else {
+						qDebug() << "Unknown table:" << value;
+					}
+				}
+				else {
+					// no need to raise error here
+				// this is done by readTTTable()
+					return false;
+				}
+			}
+			else if (tag == "ShortTable") {
+				if (value.startsWith("cvt"))
+					ret = readCvt(doc, sfd);
+				else if (value.startsWith("maxp"))
+					ret = readMaxp(doc, sfd);
+				else
+					qDebug() << "Unknown table:" << value;
+			}
+			// XXX TODO
+		}
+		else {
+			// TODO
+		}
+		if (!ret)
+			break;
+	}
+
+	if (ret)
+		ret = processReferences(doc);
+	return ret;
+}
+
+bool SFDImportFilterPriv::readGlyphs(FontDocument *doc, QTextStream *sfd)
+{
+	Q_ASSERT(doc);
+	Q_ASSERT(sfd);
+
+	for (;;) {
+		QString line = sfd->readLine();
+		if (line.isNull()) {
+			m_filter->addError("Unexpected end of file");
+			return false;
+		}
+
+		if (line == "EndChars")
+			break;
+
+		int delimIdx = line.indexOf(": ");
+		if (delimIdx == -1) {
+			m_filter->addError("Bad file format while reading 'Glyphs' table");
+			return false;
+		}
+
+		QString tag = line.left(delimIdx);
+		QString value = line.right(line.length() - delimIdx - 2);
+		if (tag == "StartChar") {
+			Glyph *g = readGlyph(value, sfd);
+			if (!g) {
+				qDebug() << "Cannot import glyph:" << value;
+				return false;
+			}
+			// TODO XXX FIXME this can cause assertion failure later
+			if (!doc->addGlyph(g))
+				delete g; // FIXME ignore errors for now
+		}
+		else {
+			m_filter->addError("Bad file format: unexpected tag in 'Glyphs' table");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+Glyph *SFDImportFilterPriv::readGlyph(const QString &name, QTextStream *sfd)
+{
+	Q_ASSERT(sfd);
+	QString flags;
+	int glyphIndex = -1;
+	QStringList refs;
+
+	Glyph *g = new Glyph(name);
+	for (;;) {
+		QString line = sfd->readLine();
+		if (line.isNull()) {
+			m_filter->addError("Unexpected end of file");
+			goto fail;
+		}
+
+		if (line == "EndChar")
+			break;
+
+		if (line == "TtInstrs:") {
+			QString tti = readTTTable(sfd);
+			if (tti.isEmpty())
+				goto fail;
+			g->setInstructions(tti);
+			continue;
+		}
+		else if (line == "Fore") {
+			if (!readGlyphContent(g, sfd))
+				goto fail;
+			continue;
+		}
+		int delimIdx = line.indexOf(": ");
+		if (delimIdx == -1)
+			continue; // ignore this for now;
+
+		QString tag = line.left(delimIdx);
+		QString value = line.right(line.length() - delimIdx - 2);
+
+		if (tag == "Encoding") {
+			// <something> <unicode or -1> <glyph index>
+			QStringList values = value.split(" ", QString::SkipEmptyParts);
+
+			int unicode = values[1].toInt();
+			if (unicode > 0)
+				g->setUnicode(unicode);
+			glyphIndex = values[2].toInt();
+		}
+		else if (tag == "Width")
+			g->setHorizAdvX(value.toDouble());
+		else if (tag == "Flags")
+			flags = value;
+		else if (tag == "Refer") {
+			// <glyphIndex> <unicode> <xx> <TRANSFORM> <flags>
+			refs.append(value);
+		}
+		else if (tag == "Colour") {
+			uint colorVal = value.toUInt(0, 16);
+			g->setColorString(QString("#%1").arg(colorVal, 6, 16, QChar('0')));
+		}
+		else if (tag == "Comment") {
+			g->setComment(value); // TODO decode UTF7
+		}
+	}
+	if (!flags.contains("W"))
+		g->unsetHorizAdvX();
+	// TODO invalid instructions, etc...
+
+	if (glyphIndex < 0) {
+		m_filter->addError(QString("Glyph index is invalid for '%1' - no 'Encoding' field?")
+				.arg(name));
+		goto fail;
+	}
+
+	toNameMap[glyphIndex] = name;
+	// store references for post processing
+	// FIXME order of references/contours is not important,
+	// because TTF does not support mixed glyphs
+	if (!refs.isEmpty())
+		references[name] = refs;
+	return g;
+fail:
+	delete g;
+	return 0;
+}
+
+// TODO FIXME XXX function was translated from Ruby. Try to remember how it works
+bool SFDImportFilterPriv::readGlyphContent(Glyph *g, QTextStream *sfd)
+{
+	Q_ASSERT(g);
+	Q_ASSERT(sfd);
+
+	QList<GlyphPoint> points;
+	GlyphPoint lastPt;
+	bool hasCnt = false;
+	bool lastIn = false; // last point is interpolated
+
+#define PT_EQ(p1,p2) ((abs(p1.x()-p2.x()) < 0.1) && (abs(p1.y()-p2.y()) < 0.1))
+
+	for (;;) {
+		QString line = sfd->readLine();
+		if (line.isNull()) {
+			m_filter->addError("Unexpected end of file");
+			return false;
+		}
+
+		QStringList ops;
+		bool lastLine = false;
+
+		if (line == "EndSplineSet")
+			lastLine = true;
+		else
+			ops = line.split(" ", QString::SkipEmptyParts);
+
+		if (lastLine || ops[2] == "m") {
+			if (!points.isEmpty()) {
+				bool cntOpen = true;
+				if ((points.size() > 1) && PT_EQ(points.first(), lastPt)) {
+					// contour is closed
+					cntOpen = false;
+				}
+				if (lastIn) {
+					GlyphPoint pt = points.takeLast();
+					points.removeFirst();
+					points.prepend(pt);
+				}
+				if (!cntOpen && (PT_EQ(points.first(), points.last())))
+					points.removeLast();
+
+				GlyphContour *c = new GlyphContour(cntOpen);
+				foreach(const GlyphPoint &p, points)
+					c->appendPoint(p);
+				points.clear();
+				g->appendContour(c);
+				if (lastLine)
+					break;
+			}
+			lastPt = GlyphPoint(ops[0].toDouble(), ops[1].toDouble());
+			points << lastPt;
+			lastIn = false;
+			hasCnt = true;
+		}
+		else if (ops[2] == "l") {
+			if (!hasCnt)
+				return false; // TODO bad file format
+			lastPt = GlyphPoint(ops[0].toDouble(), ops[1].toDouble());
+			points << lastPt;
+			lastIn = false;
+		}
+		else if (ops[6] == "c") {
+			if (!hasCnt)
+				return false; // TODO bad file format
+			QStringList flags = ops[7].split(",");
+			// FIXME this only works for valid truetype splines
+			points << GlyphPoint(ops[0].toDouble(), ops[1].toDouble(), false);
+			lastIn = true;
+			lastPt = GlyphPoint(ops[4].toDouble(), ops[5].toDouble());
+			if ((flags[0].toInt() & 0x80) == 0) {
+				// point is not interpolated
+				points << lastPt;
+				lastIn = false;
+			}
+		}
+		else {
+			m_filter->addError(QString("Bad file format: unknown drawing command (\"%1\")")
+					.arg(line));
+			return false;
+		}
+	}
+	return true;
+}
+
+bool SFDImportFilterPriv::processReferences(FontDocument *doc)
+{
+	QHashIterator<QString, QStringList> i(references);
+
+	while (i.hasNext()) {
+		i.next();
+
+		Glyph *g = doc->getGlyph(i.key());
+		if (!g)
+			continue; // FIXME do so for now
+		// TODO Q_ASSERT(g);
+
+		foreach(const QString &ref, i.value()) {
+			QStringList args = ref.split(" ", QString::SkipEmptyParts);
+			int glyphIndex = args[0].toInt();
+			QString otherName = toNameMap[glyphIndex];
+			if (otherName.isNull())
+				continue; // TODO skipped ref
+			QPointF offset(args[7].toDouble(), args[8].toDouble());
+			GlyphReference *gref = new GlyphReference(otherName, offset);
+			g->appendReference(gref);
+		}
+	}
+	return true;
+}
diff --git a/gui/cvteditor.h b/filters/sfdimportfilter.h
similarity index 64%
copy from gui/cvteditor.h
copy to filters/sfdimportfilter.h
index f3728a2..f6dd0eb 100644
--- a/gui/cvteditor.h
+++ b/filters/sfdimportfilter.h
@@ -1,4 +1,4 @@
-/* Copyright © 2007 Євгеній Мещеряков <eugen at debian.org>
+/* Copyright © 2008 Євгеній Мещеряков <eugen at debian.org>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -13,24 +13,19 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-#ifndef CVTEDITOR_H
-#define CVTEDITOR_H
-#include <QDialog>
+#ifndef SFDIMPORTFILTER_H
+#define SFDIMPORTFILTER_H
+#include "importfilterbase.h"
 
-class QAbstractItemModel;
-class QTableView;
-
-class CVTEditor : public QDialog
-{
-	Q_OBJECT
+class SFDImportFilter : public ImportFilterBase {
 public:
-	CVTEditor(QAbstractItemModel *model, QWidget *parent = 0);
-public slots:
-	void addRow();
-	void removeRow();
-	void clearTable();
+	QString menuName() const;
+	QString fileMask() const;
+	FontDocument *importFile(QFile *file);
+	QStringList errors() const;
+	void addError(const QString &text);
 private:
-	QTableView *table;
+	QStringList m_errors;
 };
 
 #endif

-- 
Fondue Font Editor



More information about the fondue-commits mailing list