[gelöst] ASCII Schriftart einlesen
-
std::string header; std::getline(fstream, header); if (header.empty()) return false; std::stringstream sstream(header); std::string signature; if (!(sstream << signature << fontInfo.height << fontInfo.baseLine << fontInfo.maxLength)) return false; // hier noch genauere Auswertung für signature (fontInfo.signature + fontInfo.hardblank)... sstream << fontInfo.oldLayout /* << ... */; return true;
-
Oh! Besten Dank Da habe ich jetzt ja was.
-
Vielen Dank nochmal.
bool readFontInfo(std::ifstream& fstream, const std::string& fontFile) { fontInfo.name = fontFile.substr(0, fontFile.length() - 4); std::string header; std::getline(fstream, header); std::stringstream sstream(header); std::string signature; if (!(sstream >> signature >> fontInfo.height >> fontInfo.baseLine >> fontInfo.maxLength >> fontInfo.oldLayout >> fontInfo.commentLines)) return false; fontInfo.hardBlank = signature.back(); signature.pop_back(); if (signature != fontInfo.signature) return false; sstream >> fontInfo.printDirection >> fontInfo.fullLayout >> fontInfo.codetagCount; return true; }
-
Wtf? Ich muss einen offenen
fstream
und den Dateinamen übergeben!? Echt jetzt?
-
Ja? Und?
Das fontFile brauche ich für den Namen. Das ich den fstream brauche ist doch offensichtlich, oder was?
Wenn das ein Fehler ist, warum kommst Du in der letzten Minute angekleckert? Den "offenen fstream'" habe ich von Anfang an benutzt.
EDIT: Und was soll überhaupt dies blöde Fragestellung? "WTF Echt jetzt"? Jetzt weiß ich bestimmt, was Du mir mitteilen willst???
-
Ich sehe da jetzt auch kein Problem. Wahrscheinlich hat @Swordfish gemeint, du könntest dann direkt über den Dateinamen einen Stream öffnen, aber diese Funktion ist ja nur eine Teilfunktion der
loadFromFile
.Du könntest allerdings direkt den passenden Font-Namen übergeben, anstatt in der Funktion die Extension mittels
substr
wegzuschneiden.Und dazu noch eine Kleinigkeit bzgl.
if (!readFontInfo(fstream, fontName)) return Error(fontInfo.name + ": invalid header");
Hier greifst du im Fehlerfall auf
fontInfo.name
zu. Du setzt zwar zuerst in der FunktionreadFontInfo
diesen Namen und erst danach wird der Fehlerfall geprüft, aber dies ist eher ein Code-Smell.
Dies würde auch dafür sprechen vor der Funktion den reinen Font-Namen zu ermitteln und diesen dann weiter zu benutzen.
-
Ein paar Dinge fallen mir noch auf. Ein Fehlerhandling hat die Funktion gar nicht.
Was ist wenn der Stream nicht offen ist? Kann man direkt am Anfang abfangen.
Was ist wenn fontfile leer ist, oder keine Endung hat?Abgesehen davon bin ich bei Swordfish. Das übergeben der Referenzen auf den stream finde ich nur dann tragbar, wenn in der Datei, aus der gelesen wird, noch andere Dinge stehen als nur das was in dieser Funktion rausgelesen wird. Wenn dort nur die Font-Daten drin stehen, ist eine Blackbox viel schöner, die den Dateinamen übernimmt und Daten zurückgibt oder irgendwo speichert bzw. false zurückgibt ( oder ersatzweise eine intensivere Fehlerkommunikation ). Aber das ist ein stückweit auch Geschmackssache.
Abgesehen davon empfinde ich "WTF? Echt jetzt?" als keine schöne Art der Gegenfrage
-
Sehe ich anders.
readFontInfo
ist eine private Methode, da kann man als Precondition ruhig festlegen, dass der stream schon geöffnet sein muss. Wenn die Methode alsistream& operator>>( ifstream& is, FontInfo& fi )
implementiert wäre muss der stream ja ebenfalls geöffnet sein. Die Übergabe des Dateinamens halte ich allerdings auch für fraglich, das gehört für mich auf die Ebene, in derreadFontInfo
aufgerufen wird. Schließlich wird der Name ja nicht aus der Datei gelesen.Ansonsten:
- Du gibst in deinem Code überall Kopien zurück (
info(), font(), error()
). Das geht auch mit Referenzen auf konstante Objekte. - Die Methode
Error
ist groß geschrieben, im Gegensatz zu allen anderen Methoden. Außerdem setzt sie die Variableerr
, was (für mich) aus dem Namen der Methode nicht ersichtlich ist. Ich würde hier
if( Fehlerfall ) { setError( ... ); return false; }
bevorzugen, auch wenn´s eine Zeile mehr ist.
- Ich bin auch kein Fan von internen Klassen/Strukturen, warum
Info
eine interne Struktur vonFont
ist verstehe ich nicht. - Variablen im namespace
figlet
erlauben nur die Benutzung einesFont
Objekts gleichzeitig. In die KlasseFont
gehören sie mMn auch nicht, da könnte man eine KlasseFontReader
drumrumbauen. Oder man wirft eine entsprechende Exception. FontChar::clear()
ist überflüssig, wenn du die entsprechende Variable zurücksetzen möchtest geht das auch mitvar = FontChar()
- ist
characterSet.push_back(std::move(fontChar))
nicht das Gleiche wiecharacterSet.emplace_back( fontChar );
?
- Du gibst in deinem Code überall Kopien zurück (
-
@DocShoe sagte in ASCII Schriftart einlesen:
ist characterSet.push_back(std::move(fontChar)) nicht das Gleiche wie characterSet.emplace_back( fontChar );?
Nein.
Emplace_back ist kein automatisches move. Empace_back kannst du verwenden, wenn du mehrere Argumente für den Konstruktor hast, also z.B. wenn du einen
vector<pair<int, int>> vp
hast, dann kannst du mitvp.emplace_back(1, 2)
da ein pair einfügen und 1 und 2 werden weitergeleitet. Wenn du dem Ding aber eine Variable (lvalue) gibst, dann wird die auch so (also als lvalue) geforwarded. Du rufst also in der richtigen Stelle den Constructor mitfontChar
im Arguement auf und das wird dann den normale copy-constructor mit const T& sein.Der push_back-Code movet prinzipiell, aber man müsste mal gucken, ob da nicht ggf. irgendwo ein noexcept fehlt - falls nämlich der vector geresizet werden muss. (gut, das ist kein exklusives push_back-Problem).
-
Weiters kann man
emplace_back
verwenden um move/copy Construction zu vermeiden.std::vector<std::string> vec; vec.push_back("meh"); // construct string + move-construct string into vector vec.emplace_back("meh"); // directly construct string in vector
-
Dieser Beitrag wurde gelöscht!
-
Habe inzwischen ein wenig weitergewerkelt, danke an alle Teilnehmer.
@DocShoe sagte in ASCII Schriftart einlesen:
- Ich bin auch kein Fan von internen Klassen/Strukturen, warum
Info
eine interne Struktur vonFont
ist verstehe ich nicht. - Variablen im namespace
figlet
erlauben nur die Benutzung einesFont
Objekts gleichzeitig. In die KlasseFont
gehören sie mMn auch nicht, da könnte man eine KlasseFontReader
drumrumbauen. Oder man wirft eine entsprechende Exception.
- Ich gehe mal davon aus, das dies nicht grundsätzlich gilt? Manche Strukturen können doch ruhig intern sein? Habe aber überarbeitet.
- Da bin ich mir nicht sicher, ob ich das verstanden habe, kann aber sein, das ich deshalb in der Vergangenheit schon mal ein Problem hatte, ohne den Fehler zu finden. Ich hab das aber unverändert gelassen, aber eine Klasse
FontViewer
geschrieben. Die zeige ich dann auch.
Hier ist erstmal die überarbeitete
figlet::Font
: (Wie immer gilt, wer darauf keine Lust hat, kein Ding )#pragma once #include <filesystem> #include <fstream> #include <string> #include <vector> #include <sstream> namespace figlet { // Da ich den ErrorState überall verwende, habe ich jetzt eine globale ErrorState KLasse ErrorState error; struct Directory { std::string path; std::vector<std::string> folders; std::vector<std::string> files; void clear() { folders.clear(); files.clear(); } } directory; void getDirectory() { directory.clear(); for (const auto& entry : std::filesystem::directory_iterator(directory.path)) { std::string name = entry.path().filename().string(); if (std::filesystem::is_directory(entry)) directory.folders.push_back(name); else if (std::filesystem::is_regular_file(entry)) directory.files.push_back(name); } } bool setPath(const std::string& path) { std::filesystem::path fsPath = path; if (!std::filesystem::exists(fsPath)) { error.setError(path + ": path not exists"); return false; } directory.path = path; getDirectory(); return true; } struct FontInfo { std::string name; const std::string signature = "flf2a"; char lineMarker = '@'; char hardBlank = '$'; int height = 0; int baseLine = 0; int maxLength = 0; int oldLayout = 0; int commentLines = 0; int printDirection = 0; int fullLayout = 0; int codetagCount = 0; }; struct FontChar { int width = 0; int height = 0; std::vector<std::string> data; }; class Font { FontInfo fontInfo; std::string contentGlyphs; // das sind die ascii-Zeichen, aus denen der Font besteht // kann ich vielleicht mal für die Ausgabe benutzen std::vector<FontChar> characterSet; ErrorState err; public: Font() = default; const FontInfo& info() const { return fontInfo; } const std::string& glyphs() const { return contentGlyphs; } const std::vector<FontChar>& font() const { return characterSet; } const ErrorState& errorState() const { return err; } bool loadFromFile(const std::string& fontFile) { if (directory.path.empty()) return error("no path to font set"); if (fontFile.length() < 5) return error(fontFile + ": invalid font name"); if (fontFile.substr(fontFile.length() - 4, fontFile.length()) != ".flf") return error(fontFile + ": no flf file"); std::string fileName = directory.path + fontFile; std::ifstream fstream(fileName); if (!fstream) return error(fileName + ": " + strerror(errno)); if (!readFontInfo(fstream)) return error(fontFile + ": invalid header"); if (!readFont(fstream)) return error(fontFile + ": invalid font"); fontInfo.name = fontFile.substr(0, fontFile.length() - 4); return true; } private: bool readFontInfo(std::ifstream& fstream) { std::string header; std::getline(fstream, header); std::stringstream sstream(header); std::string signature; if (!(sstream >> signature >> fontInfo.height >> fontInfo.baseLine >> fontInfo.maxLength >> fontInfo.oldLayout >> fontInfo.commentLines)) return false; fontInfo.hardBlank = signature.back(); signature.pop_back(); if (signature != fontInfo.signature) return false; sstream >> fontInfo.printDirection >> fontInfo.fullLayout >> fontInfo.codetagCount; return true; } bool readFont(std::ifstream& fstream) { std::string line; for (int i = 0; i < fontInfo.commentLines; ++i) std::getline(fstream, line); // skip comments FontChar fontChar; while (std::getline(fstream, line)) { if (line.empty() || line.back() != fontInfo.lineMarker) continue; // skip invalid lines line.pop_back(); // remove data line marker fontChar.width = line.length() - 1; if (fontChar.width > fontInfo.maxLength) return false; const bool isLastLineOfChar = !line.empty() && line.back() == fontInfo.lineMarker; if (isLastLineOfChar) line.pop_back(); // remove last-line-of-char marker fontChar.data.push_back(line); getContentGlyphs(line); // hole die ascii-Zeichen, aus denen der Font besteht if (isLastLineOfChar) { fontChar.height = std::size(fontChar.data); if (fontChar.height != fontInfo.height) return false; characterSet.push_back(std::move(fontChar)); } } return true; } void getContentGlyphs(const std::string& line) { contentGlyphs += line; // remove doubles std::sort(contentGlyphs.begin(), contentGlyphs.end()); contentGlyphs.erase(std::unique( contentGlyphs.begin(), contentGlyphs.end()), contentGlyphs.end()); } bool error(const std::string& errType) { err.setError(errType); return false; } }; }
Die
FontViewer
:#pragma once #include "Console.h" #include "FigletFont.h" #include <map> // Ich kenn die Kritik, FontViewer ist keine Console ;-) Sehe das aber so, das dies eine Konsolenanwendung ist // Außerdem finde ich das zur Zeit echt bequem so class FontViewer : public Console { struct Param { int x = 0; int y = 0; std::string key; std::size_t keyCounter = 0; std::size_t fontCounter = 0; }; figlet::Directory fontDirectory; std::string pathToFont; std::map<std::string, std::vector<figlet::Font>> figletFonts; figlet::Font defaultFont; Param param; public: FontViewer(const std::string& path, const figlet::Font& font) : pathToFont(path), defaultFont{ font } {}; private: bool onUserCreate() override { if (!figlet::setPath(pathToFont)) return error(figlet::error.type()); if (figlet::directory.folders.empty()) return error(pathToFont + ": directory is empty"); fontDirectory = figlet::directory; // get own directory for (const auto& folder : fontDirectory.folders) { if (!figlet::setPath(pathToFont + folder + "/")) // get standard directory return error(figlet::error.type()); for (const auto& file : figlet::directory.files) { figlet::Font font; if (!font.loadFromFile(file)) continue; figletFonts[folder].push_back(font); } } if (std::size(fontDirectory.folders) != std::size(figletFonts)) return false; param.key = fontDirectory.folders[param.keyCounter]; fillScreen(console::BLUE, console::BLUE); return true; } bool onUserUpdate(float elapsedTime) override { fillBuffer(); // clear screen without write buffer // exit if (getKey(VK_ESCAPE).pressed) return false; // change folder if (getKey(VK_ADD).pressed) { param.keyCounter++; if (param.keyCounter >= std::size(figletFonts)) param.keyCounter = 0; } if (getKey(VK_SUBTRACT).pressed) { param.keyCounter--; if (param.keyCounter >= std::size(figletFonts)) param.keyCounter = std::size(figletFonts) - 1; } // change font if (getKey(VK_UP).pressed) { param.fontCounter--; if (param.fontCounter >= std::size(figletFonts[param.key])) param.fontCounter = std::size(figletFonts[param.key]) - 1; } if (getKey(VK_DOWN).pressed) { param.fontCounter++; if (param.fontCounter >= std::size(figletFonts[param.key])) param.fontCounter = 0; } // scroll font if (getKey(VK_RIGHT).held) { param.x--; } if (getKey(VK_LEFT).held) { param.x++; } param.key = fontDirectory.folders[param.keyCounter]; figlet::Font font = figletFonts[param.key][param.fontCounter]; plotFontName(font, 0, 1, console::DGREY, console::GREY); plotFont(font, param.x, 3 * (defaultFont.info().height + 1), console::BLACK); plotExample(font, param.x, 3 * (defaultFont.info().height + 1) + 2 * (font.info().height + 1), console::BLACK); return true; } void plotChar(const figlet::Font& font, const int x, const int y, const std::size_t fontIndex, const int fontColor) { int cx = x; int cy = y; for (const auto& line : font.font()[fontIndex].data) { for (const auto c : line) { if (c == font.info().hardBlank) setDot(cx, cy, console::Dot(' ', fontColor), console::BGROUND); else if (c == '#') setDot(cx, cy, console::Dot(console::SOLID, fontColor), console::BGROUND); else setDot(cx, cy, console::Dot(c, fontColor), console::BGROUND); cx++; } cx = x; cy++; } } void plotFont(const figlet::Font& font, const int x, const int y, const int fontColor) { int cx = x; for (std::size_t i = 0; i < std::size(font.font()); ++i) { plotChar(font, cx, y, i, fontColor); cx += font.font()[i].width; } } void plotString(const figlet::Font& font, const int x, const int y, const std::string& string, const int fontColor) { if (font.font().empty()) return; int sx = x; for (const auto ch : string) { std::size_t fontIndex = ch - ' '; if (fontIndex >= std::size(font.font())) fontIndex = 0; plotChar(font, sx, y, fontIndex, fontColor); sx += font.font()[fontIndex].width; } } void plotFontName(const figlet::Font& font, const int x, const int y, const int fontColor1, const int fontColor2) { plotString(defaultFont, x, y, "Folder: " + param.key, fontColor1); plotString(defaultFont, x, y + defaultFont.info().height + 1, "Font: " + font.info().name, fontColor2); } void plotExample(const figlet::Font& font, const int x, const int y, const int fontColor) { int sy = y; int yLine = font.info().height + 1; // hier sehe ich gerade, kann ich auch gleich font.info().baseLine Nehmen plotString(font, x, sy, "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG", fontColor); plotString(font, x, sy += yLine, "The Quick Brown Fox Jumps Over The Lazy Dog", fontColor); sy += yLine; plotString(font, x, sy += yLine, "VATIS SCHOSSHUND JAGT ZWÖLF BOXKÄMPFER QUER ÜBER SYLT", fontColor); plotString(font, x, sy += yLine, "Vatis Schoßhund jagt zwölf Boxkämpfer quer über Sylt", fontColor); sy += yLine; plotString(font, x, sy += yLine, "123.456.789.000", fontColor); plotString(font, x, sy += yLine, "+-*/= .,:; <>|_-'~ !$%&/()?", fontColor); } };
- Ich bin auch kein Fan von internen Klassen/Strukturen, warum
-
Bekomme dort aber manchmal üble Fehler, wenn ich durch die Folders blättere. Irgendwas in
xstring
oderxutilities
oder manchmal meckert ganz Visual Studio.EDIT: Vielleicht sollte ich den
param.fontCounter
zurücksetzen, wenn ich denparam.key
wechsele?
Sorry, aber sobald ich hier Code reinstelle, fang ich an, ganz neu drüber nachzudenken