| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #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); |
| } |
|
|