ASCII Schriftart einlesen



  • PS:

    @hustbaer sagte in ASCII Schriftart einlesen:

    Wieso? Die zu ignorieren sollte einfach genug sein.

            if (str.back() != '@') // skip comments
                continue;
    

    Wenn ich das jetzt richtig verstehe, wie kann ich mir sicher sein, das ein Kommentar dieses Zeichen nicht als letztes hat? EDIT: Obwohl das natürlich recht unwahrscheinlich sein wird. Vielleicht sollte man erst dann anfangen zu editieren, wenn ein Font nach dem Einlesen offensichtlich kaputt ist?



  • Also ich kenne die Spec von dem Format nicht. Nur... so wie das aussieht wüsste ich nicht wie man Kommentar/nicht-Kommentar sonst unterscheiden sollte. Kann mMn. fast nur das fehlende @ am Zeilenende sein.



  • OK, hab mal gegoogelt: https://github.com/Marak/asciimo/issues/3
    Wie viele Kommentar-Zeilen es gibt steht in der Header. Also noch einfacher zu ignorieren.
    Dafür muss das @ kein @ sein sondern kann ein beliebiges Zeichen sein - es muss nur im ganze File das selbe verwendet werden.
    Spec: http://www.jave.de/docs/figfont.txt



  • Die (besser lesbare) Spezifikation gibt es unter The FIGfont Version 2 FIGfont and FIGdriver Standard.



  • Oh, ich hatte mal versucht, diesen Header selbst verstehen zu wollen, bin aber gescheitert 😉
    Mit Anleitung sieht das schon besser aus. Besten Dank.



  • Ich habe auf einen Schlag 400 mir noch unbekannte Fonts eingelesen und wieder ausgegeben (erst mal nur in reiner ascii-Form). Für mich ist das zu meiner Zufriedenheit gelöst.
    Ich zeige trotzdem die komplette Klasse, falls doch noch eine Baustelle ist.

    #pragma once
    #include <filesystem>
    #include <fstream>
    #include <string>
    #include <vector>
    #include <sstream> 
    
    namespace figlet
    {
    	struct ErrorState
    	{
    		bool state = false;
    		std::string type;
    	} 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 = { true, path + ": path not exists" };
    			return false;
    		}
    		directory.path = fsPath.string();
    		getDirectory();
    		return true;
    	}
    
    
    	class Font
    	{
    		struct Info
    		{
    			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;
    		};
    
    		Info fontInfo;
    		std::vector<FontChar> characterSet;
    		ErrorState err;
    
    	public:
    		Font() = default;
    
    		Info info() const { return fontInfo; }
    		std::vector<FontChar> font() const { return characterSet; }
    		ErrorState error() const { return err; }
    
    		bool loadFromFile(const std::string& fontName)
    		{
    			if (directory.path.empty())
    				return Error("no path to font set");
    
    			if (fontName.length() < 5)
    				return Error(fontName + ": invalid font name");
    
    			if (fontName.substr(fontName.length() - 4, fontName.length()) != ".flf")
    				return Error(fontName + ": no flf file");
    
    			std::string fileName = directory.path + fontName;
    			std::ifstream fstream(fileName);
    			if (!fstream)
    				return Error(fileName + ": " + strerror(errno));
    
    			if (!readFontInfo(fstream, fontName))
    				return Error(fontInfo.name + ": invalid header");
    
    			if (!readFont(fstream))
    				return Error(fontInfo.name + ": invalid font");
    
    			return true;
    		}
    
    	private:
    		std::vector<std::string> splitString(const std::string& str, const char delim)  const
    		{
    			std::vector<std::string> vec;
    			std::stringstream sstream(str);
    			std::string item;
    
    			while (std::getline(sstream, item, delim))
    				vec.push_back(item);
    
    			return vec;
    		}
    
    		bool readFontInfo(std::ifstream& fstream, const std::string& fontName)
    		{
    			// geht das alles eleganter?
    			fontInfo.name = fontName.substr(0, fontName.length() - 4);
    			std::string header;
    			std::getline(fstream, header);
    			if (header.empty()) return false;
    			std::vector<std::string> head = splitString(header, ' ');
    
    			std::size_t n = 0;
    			std::string signature = head[n];
    			fontInfo.hardBlank = signature.back();
    			signature.pop_back();
    			if (signature != fontInfo.signature) return false;
    
    			if (++n >= std::size(head)) return false;
    			fontInfo.height = std::stoi(head[n]); // height is essential
    
    			if (++n >= std::size(head)) return false;
    			fontInfo.baseLine = std::stoi(head[n]);
    
    			if (++n >= std::size(head)) return false;
    			fontInfo.maxLength = std::stoi(head[n]); // maxLength is essential
    
    			if (++n >= std::size(head)) return true;
    			fontInfo.oldLayout = std::stoi(head[n]);
    
    			if (++n >= std::size(head)) return true;
    			fontInfo.commentLines = std::stoi(head[n]);
    
    			if (++n >= std::size(head)) return true;
    			fontInfo.printDirection = std::stoi(head[n]);
    
    			if (++n >= std::size(head)) return true;
    			fontInfo.fullLayout = std::stoi(head[n]);
    
    			if (++n >= std::size(head)) return true;
    			fontInfo.codetagCount = std::stoi(head[n]);
    
    			return true;
    		}
    
    		bool readFont(std::ifstream& fstream)
    		{
    			std::string str;
    			for (int i = 0; i < fontInfo.commentLines; ++i)
    				std::getline(fstream, str); // skip comments
    
    			FontChar fontChar;
    			while (std::getline(fstream, str))
    			{
    				if (str.empty() || str.back() != fontInfo.lineMarker)
    					continue; // skip invalid lines
    
    				str.pop_back(); // remove data line marker
    				fontChar.width = str.length() - 1;
    				if (fontChar.width > fontInfo.maxLength)
    					return false;
    
    				const bool isLastLineOfChar = !str.empty() && str.back() == fontInfo.lineMarker;
    				if (isLastLineOfChar) str.pop_back(); // remove last-line-of-char marker
    				fontChar.data.push_back(str);
    
    				if (isLastLineOfChar)
    				{
    					fontChar.height = std::size(fontChar.data);
    					if (fontChar.height != fontInfo.height)
    						return false;
    
    					characterSet.push_back(std::move(fontChar));
    				}
    			}
    			return true;
    		}
    
    		bool Error(const std::string& errType)
    		{
    			err = { true, errType };
    			return false;
    		}
    	};
    }
    

    Wen es interessiert, die Fonts habe ich von hier:
    http://www.figlet.org/



  • @zeropage sagte in ASCII Schriftart einlesen:

    bool readFontInfo(std::ifstream& fstream, const std::string& fontName)
    {
    	// geht das alles eleganter?
    	// ...
    	std::vector<std::string> head = splitString(header, ' ');
    	// ...
    }
    

    Warum liest du erst alles in einen std::vector<std::string> ein, um dann wieder jeden einzelnen Zahlenwert mittels std::stoi auszulesen? Benutze doch einfach direkt einen std::stringstreamund lese daraus die einzelnen Werte.



  • Danke, da komme ich aber noch nicht richtig mit der Benutzung klar. Muss ich noch schauen.



  • 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 Funktion readFontInfo 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 als istream& 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 der readFontInfo 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 Variable err, 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 von Font ist verstehe ich nicht.
    • Variablen im namespace figlet erlauben nur die Benutzung eines Font Objekts gleichzeitig. In die Klasse Font gehören sie mMn auch nicht, da könnte man eine Klasse FontReader drumrumbauen. Oder man wirft eine entsprechende Exception.
    • FontChar::clear() ist überflüssig, wenn du die entsprechende Variable zurücksetzen möchtest geht das auch mit var = FontChar()
    • ist characterSet.push_back(std::move(fontChar)) nicht das Gleiche wie characterSet.emplace_back( fontChar );?


  • @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 mit vp.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 mit fontChar 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
    

Log in to reply