| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <iostream> |
| |
|
| | #include <QRegularExpression> |
| | #include <QTextStream> |
| | #include "rs_font.h" |
| |
|
| | #include <QFileInfo> |
| |
|
| | #include "rs_arc.h" |
| | #include "rs_debug.h" |
| | #include "rs_fontchar.h" |
| | #include "rs_line.h" |
| | #include "rs_math.h" |
| | #include "rs_polyline.h" |
| | #include "rs_system.h" |
| |
|
| | class RS_FontChar; |
| |
|
| | namespace { |
| |
|
| | |
| | |
| | QString charFromHex(const QString& hexCode) |
| | { |
| | bool okay=false; |
| | char32_t ucsCode = hexCode.toUInt(&okay, 16); |
| | |
| | constexpr char32_t invalidCode = 0xFFFD; |
| | return (okay) ? QString::fromUcs4(&ucsCode, 1) : QString::fromUcs4(&invalidCode, 1); |
| | } |
| |
|
| | |
| | std::pair<QString, bool> extractFontChar(const QString& line) |
| | { |
| | |
| | static QRegularExpression regexp("[0-9A-Fa-f]{1,5}"); |
| | QRegularExpressionMatch match=regexp.match(line); |
| | if (!match.hasMatch()) |
| | return {}; |
| |
|
| | QString cap = match.captured(0); |
| | bool okay=false; |
| | std::uint32_t code = cap.toUInt(&okay, 16); |
| |
|
| | if (!okay) { |
| | LC_ERR<<__func__<<"() line "<<__LINE__<<": invalid font code in "<<line; |
| | return {}; |
| | } |
| | char32_t ucsCode{code}; |
| | return {QString::fromUcs4(&ucsCode, 1), true}; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | RS_Font::RS_Font(const QString& fileName, bool owner) |
| | : letterList(owner), m_fileName(fileName), fileLicense("unknown") { |
| | loaded = false; |
| | letterSpacing = 3.0; |
| | wordSpacing = 6.75; |
| | lineSpacingFactor = 1.0; |
| | rawLffFontList.clear(); |
| | } |
| |
|
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | bool RS_Font::loadFont() { |
| | RS_DEBUG->print("RS_Font::loadFont"); |
| |
|
| | if (loaded) { |
| | return true; |
| | } |
| |
|
| | QString path; |
| |
|
| | |
| | if (!m_fileName.contains(".cxf", Qt::CaseInsensitive) && |
| | !m_fileName.contains(".lff", Qt::CaseInsensitive)) { |
| | QStringList fonts = RS_SYSTEM->getNewFontList(); |
| | fonts.append(RS_SYSTEM->getFontList()); |
| |
|
| | for (const QString& font: fonts) { |
| | if (QFileInfo(font).baseName().toLower()==m_fileName.toLower()) { |
| | path = font; |
| | break; |
| | } |
| | } |
| | } |
| |
|
| | |
| | else { |
| | path = m_fileName; |
| | } |
| |
|
| | |
| | if (path.isEmpty()) { |
| | RS_DEBUG->print(RS_Debug::D_WARNING, |
| | "RS_Font::loadFont: No fonts available."); |
| | return false; |
| | } |
| |
|
| | |
| | QFile f(path); |
| | if (!f.open(QIODevice::ReadOnly)) { |
| | LC_LOG(RS_Debug::D_WARNING)<<"RS_Font::loadFont: Cannot open font file: "<<path; |
| | return false; |
| | } else { |
| | LC_LOG(RS_Debug::D_WARNING)<<"RS_Font::loadFont: Successfully opened font file: "<<path; |
| | } |
| | f.close(); |
| |
|
| | if (path.contains(".cxf")) |
| | readCXF(path); |
| | if (path.contains(".lff")) |
| | readLFF(path); |
| |
|
| | RS_Block* bk = letterList.find(QChar(0xfffd)); |
| | if (!bk) { |
| | |
| | RS_FontChar* letter = new RS_FontChar(nullptr, QChar(0xfffd), RS_Vector(0.0, 0.0)); |
| | RS_Polyline* pline = new RS_Polyline(letter, RS_PolylineData()); |
| | pline->setPen(RS_Pen(RS2::FlagInvalid)); |
| | pline->setLayer(nullptr); |
| | pline->addVertex(RS_Vector(1, 0), 0); |
| | pline->addVertex(RS_Vector(0, 2), 0); |
| | pline->addVertex(RS_Vector(1, 4), 0); |
| | pline->addVertex(RS_Vector(2, 2), 0); |
| | pline->addVertex(RS_Vector(1, 0), 0); |
| | letter->addEntity(pline); |
| | letter->calculateBorders(); |
| | letterList.add(letter); |
| | } |
| |
|
| | loaded = true; |
| |
|
| | RS_DEBUG->print("RS_Font::loadFont OK"); |
| |
|
| | return true; |
| | } |
| |
|
| |
|
| | void RS_Font::readCXF(const QString& path) { |
| | QFile f(path); |
| | f.open(QIODevice::ReadOnly); |
| | QTextStream ts(&f); |
| |
|
| | |
| | while (!ts.atEnd()) { |
| | QString line = ts.readLine(); |
| |
|
| | if (line.isEmpty()) |
| | continue; |
| |
|
| | |
| | if (line.at(0)=='#') { |
| | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
| | QStringList lst = ( line.right(line.length()-1) ).split(':', Qt::SkipEmptyParts); |
| | #else |
| | QStringList lst = ( line.right(line.length()-1) ).split(':', QString::SkipEmptyParts); |
| | #endif |
| | QStringList::Iterator it3 = lst.begin(); |
| |
|
| | |
| | if (lst.size()<2) |
| | continue; |
| |
|
| | QString identifier = (*it3).trimmed(); |
| | it3++; |
| | QString value = (*it3).trimmed(); |
| |
|
| | if (identifier.toLower()=="letterspacing") { |
| | letterSpacing = value.toDouble(); |
| | } else if (identifier.toLower()=="wordspacing") { |
| | wordSpacing = value.toDouble(); |
| | } else if (identifier.toLower()=="linespacingfactor") { |
| | lineSpacingFactor = value.toDouble(); |
| | } else if (identifier.toLower()=="author") { |
| | authors.append(value); |
| | } else if (identifier.toLower()=="name") { |
| | names.append(value); |
| | } else if (identifier.toLower()=="encoding") { |
| | ts.setEncoding(QStringConverter::encodingForName(value.toLatin1()).value()); |
| | encoding = value; |
| | } |
| | } |
| |
|
| | |
| | else if (line.at(0)=='[') { |
| |
|
| | |
| | QString ch; |
| |
|
| | |
| | QRegularExpression regexp("[0-9A-Fa-f]{4,4}"); |
| | QRegularExpressionMatch match=regexp.match(line); |
| | if (match.hasMatch()) { |
| | ch = charFromHex(match.captured(0)); |
| | } |
| |
|
| | |
| | else if (line.indexOf(']')>=3) { |
| | int i = line.indexOf(']'); |
| | QString mid = line.mid(1, i-1); |
| | ch = QString::fromUtf8(mid.toLatin1()).first(1); |
| | } |
| |
|
| | |
| | else { |
| | ch = line.first(1); |
| | } |
| |
|
| | |
| | RS_FontChar* letter = |
| | new RS_FontChar(nullptr, ch, RS_Vector(0.0, 0.0)); |
| |
|
| | |
| | QString coordsStr; |
| | QStringList coords; |
| | QStringList::Iterator it2; |
| | do { |
| | line = ts.readLine(); |
| |
|
| | if (line.isEmpty()) { |
| | continue; |
| | } |
| |
|
| | coordsStr = line.right(line.length()-2); |
| | |
| | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
| | coords = coordsStr.split(',', Qt::SkipEmptyParts); |
| | #else |
| | coords = coordsStr.split(',', QString::SkipEmptyParts); |
| | #endif |
| | it2 = coords.begin(); |
| |
|
| | |
| | if (line.at(0)=='L') { |
| | double x1 = (*it2++).toDouble(); |
| | double y1 = (*it2++).toDouble(); |
| | double x2 = (*it2++).toDouble(); |
| | double y2 = (*it2).toDouble(); |
| |
|
| | RS_Line* line = new RS_Line{letter, {{x1, y1}, {x2, y2}}}; |
| | line->setPen(RS_Pen(RS2::FlagInvalid)); |
| | line->setLayer(nullptr); |
| | letter->addEntity(line); |
| | } |
| |
|
| | |
| | else if (line.at(0)=='A') { |
| | double cx = (*it2++).toDouble(); |
| | double cy = (*it2++).toDouble(); |
| | double r = (*it2++).toDouble(); |
| | double a1 = RS_Math::deg2rad((*it2++).toDouble()); |
| | double a2 = RS_Math::deg2rad((*it2).toDouble()); |
| | bool reversed = (line.at(1)=='R'); |
| |
|
| | RS_ArcData ad(RS_Vector(cx,cy), |
| | r, a1, a2, reversed); |
| | RS_Arc* arc = new RS_Arc(letter, ad); |
| | arc->setPen(RS_Pen(RS2::FlagInvalid)); |
| | arc->setLayer(nullptr); |
| | letter->addEntity(arc); |
| | } |
| | } while (!line.isEmpty()); |
| |
|
| | if (letter->isEmpty()) { |
| | delete letter; |
| | } else { |
| | letter->calculateBorders(); |
| | letterList.add(letter); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | QString letterNameToHexUnicodeCode(QString originalName) { |
| | QString uCode; |
| | uCode.setNum(originalName.at(0).unicode(), 16); |
| | while (uCode.length()<4) { |
| | uCode="0"+uCode; |
| | } |
| | return QString("[%1] %2").arg(uCode).arg(originalName.at(0)); |
| | } |
| |
|
| | void RS_Font::readLFF(const QString& path) { |
| | QFile f(path); |
| | encoding = "UTF-8"; |
| | f.open(QIODevice::ReadOnly); |
| | QTextStream ts(&f); |
| |
|
| | |
| | while (!ts.atEnd()) { |
| | QString line = ts.readLine(); |
| |
|
| | if (line.isEmpty()) |
| | continue; |
| |
|
| | |
| | if (line.at(0)=='#') { |
| | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
| | QStringList lst =line.remove(0,1).split(':', Qt::SkipEmptyParts); |
| | #else |
| | QStringList lst =line.remove(0,1).split(':', QString::SkipEmptyParts); |
| | #endif \ |
| | |
| | if (lst.size()<2) |
| | continue; |
| |
|
| | QString identifier = lst.at(0).trimmed(); |
| | QString value = lst.at(1).trimmed(); |
| |
|
| | if (identifier.toLower()=="letterspacing") { |
| | letterSpacing = value.toDouble(); |
| | } else if (identifier.toLower()=="wordspacing") { |
| | wordSpacing = value.toDouble(); |
| | } else if (identifier.toLower()=="linespacingfactor") { |
| | lineSpacingFactor = value.toDouble(); |
| | } else if (identifier.toLower()=="author") { |
| | authors.append(value); |
| | } else if (identifier.toLower()=="name") { |
| | names.append(value); |
| | } else if (identifier.toLower()=="license") { |
| | fileLicense = value; |
| | } else if (identifier.toLower()=="encoding") { |
| | ts.setEncoding(QStringConverter::encodingForName(value.toLatin1()).value()); |
| | encoding = value; |
| | } else if (identifier.toLower()=="created") { |
| | fileCreate = value; |
| | } |
| | } |
| |
|
| | |
| | else if (line.at(0)=='[') { |
| |
|
| | |
| | const auto [ch, okay] = extractFontChar(line); |
| | if (!okay) { |
| | LC_LOG(RS_Debug::D_WARNING)<<"Ignoring code from LFF font file: "<<line; |
| | continue; |
| | } |
| |
|
| | |
| | |
| | QString letterName = ch; |
| |
|
| | QStringList fontData; |
| | do { |
| | line = ts.readLine(); |
| | if(line.isEmpty()) break; |
| | fontData.push_back(line); |
| | } while(true); |
| | if (!fontData.isEmpty() |
| | && !rawLffFontList.contains( letterName)) { |
| | rawLffFontList[letterName] = fontData; |
| | } |
| | } |
| | } |
| | } |
| |
|
| | void RS_Font::generateAllFonts() |
| | { |
| | for(const QString& key : rawLffFontList.keys()) |
| | generateLffFont(key); |
| | } |
| |
|
| | RS_Block* RS_Font::generateLffFont(const QString& key) |
| | { |
| | if (key.isEmpty()) { |
| | LC_ERR<<__LINE__<<" "<<__func__<<"("<<key<<"): empty key"; |
| | } |
| |
|
| | if (!rawLffFontList.contains( key)) { |
| | LC_ERR<<QString{"RS_Font::generateLffFont([%1]) : can not find the letter in LFF file %2"}.arg(key.at(0)).arg(m_fileName); |
| | return nullptr; |
| | } |
| |
|
| | |
| | auto letter = std::make_unique<RS_FontChar>(nullptr, key, RS_Vector(0.0, 0.0)); |
| |
|
| | |
| | QStringList fontData = rawLffFontList[key]; |
| |
|
| | while(!fontData.isEmpty()) { |
| | QString line = fontData.takeFirst(); |
| |
|
| | if (line.isEmpty()) { |
| | continue; |
| | } |
| |
|
| | |
| | if (line.at(0)=='C') { |
| | line.remove(0,1); |
| | auto uCode = line.toUInt(nullptr, 16); |
| | auto ch = charFromHex(line); |
| | if (ch == key) { |
| | LC_ERR<<QString{"RS_Font::generateLffFont([%1]) : recursion, ignore this character from %2"}.arg(uCode, 4, 16).arg(m_fileName); |
| | return nullptr; |
| | } |
| |
|
| | RS_Block* bk = letterList.find(ch); |
| | if (nullptr == bk) { |
| | if (!rawLffFontList.contains(ch)) { |
| | LC_ERR<<QString{"RS_Font::generateLffFont([%1]) : can not find the letter C%04X in LFF file %2"}.arg(QChar(key.at(0))).arg(m_fileName); |
| | return nullptr; |
| | } |
| | generateLffFont(ch); |
| | bk = letterList.find(ch); |
| | } |
| | if (nullptr != bk) { |
| | RS_Entity* bk2 = bk->clone(); |
| | bk2->setPen(RS_Pen(RS2::FlagInvalid)); |
| | bk2->setLayer(nullptr); |
| | letter->addEntity(bk2); |
| | } |
| | } |
| | |
| | else { |
| | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
| | QStringList vertex = line.split(';', Qt::SkipEmptyParts); |
| | #else |
| | QStringList vertex = line.split(';', QString::SkipEmptyParts); |
| | #endif \ |
| | |
| | if (vertex.size()<2) |
| | continue; |
| | RS_Polyline* pline = new RS_Polyline(letter.get(), RS_PolylineData()); |
| | pline->setPen(RS_Pen(RS2::FlagInvalid)); |
| | pline->setLayer(nullptr); |
| | foreach(const QString& point, vertex) { |
| | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
| | QStringList coords = point.split(',', Qt::SkipEmptyParts); |
| | #else |
| | QStringList coords = point.split(',', QString::SkipEmptyParts); |
| | #endif \ |
| | |
| | double x1 = coords.at(0).toDouble(); |
| | |
| | double y1 = coords.size() >= 2 ? coords.at(1).toDouble() : 0.; |
| | |
| | double bulge = 0; |
| | if (coords.size() >= 3 && coords.at(2).at(0) == QChar('A')){ |
| | QString bulgeStr = coords.at(2); |
| | bulge = bulgeStr.remove(0,1).toDouble(); |
| | } |
| | pline->setNextBulge(bulge); |
| | pline->addVertex(RS_Vector(x1, y1), bulge); |
| | } |
| | letter->addEntity(pline); |
| | } |
| |
|
| | } |
| |
|
| | if (!letter->isEmpty()) { |
| | letter->calculateBorders(); |
| | letterList.add(letter.get()); |
| | auto ret = letter.get(); |
| | letter.release(); |
| | return ret; |
| | } |
| | return nullptr; |
| | } |
| |
|
| | RS_Block* RS_Font::findLetter(const QString& name) { |
| | RS_Block* ret= letterList.find(name); |
| | return (ret != nullptr) ? ret : generateLffFont(name); |
| |
|
| | } |
| |
|
| | |
| | |
| | |
| | std::ostream& operator << (std::ostream& os, const RS_Font& f) { |
| | os << " Font file name: " << f.getFileName().toLatin1().data() << "\n"; |
| | |
| | return os; |
| | } |
| |
|
| | unsigned RS_Font::countLetters() const { |
| | return letterList.count(); |
| | } |
| | RS_Block* RS_Font::letterAt(unsigned i) { |
| | return letterList.at(i); |
| | } |
| |
|