[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