ASCII Schriftart einlesen



  • Hallo,
    Ich sitz jetzt schon viel zu lange daran und ich muss jetzt nachfragen, egal wie einfach dann die Lösung wird.

    Ich versuche FIGlet Fonts in einen vector zu schreiben, bekomme einen Absturz bei

    if (str.back() == '@') // last line of font char
    

    wegen 'back() auf einen leeren string'. Direkt als Absturzmeldung.

    Die Fonts sind so aufgebaut:

    flf2a$ 8 7 54 0 12 0 64
     $ $@
     $ $@
     $ $@
     $ $@
     $ $@
     $ $@
     $ $@
     $ $@@
     ###$@
     ###$@
     ###$@
      # $@
        $@
     ###$@
     ###$@
        $@@
     ### ###$@
     ### ###$@
      #   # $@
     $      $@
     $      $@
            $@
            $@
            $@@
       # #  $@
       # #  $@
     #######$@
       # #  $@
     #######$@
       # #  $@
       # #  $@
            $@@
      ##### $@
     #  #  #$@
     #  #   $@
      ##### $@
        #  #$@
     #  #  #$@
      ##### $@
            $@@
    

    Das doppelte @@ ist Ende des Zeichen. Meine Umsetzung:

            // Variablen
            struct FontChar
    	{
    		int width = 0;
    		int height = 0;
    		std::vector<std::string> data;
    
    		void clear()
    		{
    			width = 0;
    			height = 0;
    			data.clear();
    		}
    	};
    
    	std::string headInfo;
    	std::vector<FontChar> characterSet;
    
            // read function
          	std::string str;
          	std::getline(fstream, headInfo); // read head info
          	// read font
          	FontChar fontChar;
          	while (std::getline(fstream, str))
          	{
          		str.erase(str.end() - 1); // last char is no font char
          		fontChar.width = str.length() - 1; // get width of font char
          		if (str.back() == '@') // last line of font char
          		{
          			str.erase(str.end() - 1); // last char is no font char
          			fontChar.data.push_back(str); // write string in fontCHar.data vector
          			fontChar.height++; // finally height of font char
          
          			characterSet.push_back(fontChar); // write font char in characterSet vector
          			//printFontChar(fontChar);
          			//_key();
          			fontChar.clear();
          			// new font char
          			continue; // go to while
          		}
          		fontChar.data.push_back(str);
          		fontChar.height++;
          	}
    

    Ich habe mal versucht, englische Kommentare zu nehmen, hoffe, ist immer noch verständlich.
    Der Absturz kommt beim letzten Zeichen im figlet font. Aber ich dachte, bei

    while (std::getline(fstream, str))
    

    wird sofort verlassen, wenn nichts mehr kommt?
    Und wie gesagt, sitze schon viel zu lange daran, ist doof so. Da frage ich lieber 😉


  • Mod

    @zeropage sagte in ASCII Schriftart einlesen:

    while (std::getline(fstream, str))
    

    wird sofort verlassen, wenn nichts mehr kommt?

    Aber was ist, wenn die letzte Zeile leer ist? Eine leere Zeile ist auch eine gültige Zeile.



  • Wie sieht denn die Zeile aus, die vor dem Fehler noch verarbeitet wurde?



  • Wenn das File mit einem Zeilenumbruch endet, dann wird danach noch eine leere Zeile "gelesen". Dabei wird das eofbit gesetzt. Da gibt operator bool aber noch true zurück, d.h. die Schleife wird nochmals durchlaufen. Erst beim Versuch mit gesetzem eofbit noch etwas zu lesen wird dann failbit gesetzt. Und erst ab da liefert operator bool dann false und die Schleife wird verlassen.

    Und so zerlegt es dein Programm halt bei

    str.erase(str.end() - 1); // last char is no font char
    

    Ganz unabhängig vom Verhalten von getline bei Files die mit einem Zeilenumbruch enden sollte dein Programm aber auf jeden Fall mit leeren Zeilen klarkommen. Selbst wenn das vom File-Format nicht erlaubt sein sollte, dein Programm sollte niemals wegen eines Formatfehlers abstürzen.



  • Danke. Sorry, ich hatte wieder was getrunken.
    Ich schau mir im Moment erst mal nur die Reaktionen an. Aber ich bedanke mich schon mal. Das sieht aus, als ob ich das verstehen könnte.

    Bis dann.



  • Ich musste eigentlich nur 2x eine Abfrage, ob string leer ist einfügen. Das umgeht dann noch andere Punkte, wie das es machmal auch 'leere' Zeichen gibt:

    @
    @
    @
    @
    @
    @
    @
    @@
    

    Außerdem brauch ich auch die Höhe nicht ständig mitzählen, sondern kann sie gleich aus fontChar.datanehmen.

    	    	                std::string str;
    	        		std::getline(fstream, headInfo); // read head info
    	        		// read font
    	        		FontChar fontChar;
    	        		while (std::getline(fstream, str))
    	        		{
    	        			if (!str.empty()) // if string is not empty / no  blank line
    	        			{
    	        				str.erase(str.end() - 1); // last char is no font char
    	        				fontChar.width = str.length() - 1; // get width of font char
    	        				if (!str.empty() && str.back() == '@') // last line of font char
    	        				{
    	        					str.erase(str.end() - 1); // last char is no font char
    	        					fontChar.data.push_back(str); // write string in fontChar.data vector
    	        					fontChar.height = std::size(fontChar.data);  // get height of font char
    	        
    	        					characterSet.push_back(fontChar); // write font char in characterSet vector
    	        					//printFontChar(fontChar);
    	        					//_key();
    	        					fontChar.clear();
    	        					// new font char
    	        					continue; // go to while
    	        				}
    	        				fontChar.data.push_back(str);
    	        			}
    	        		}
    	        	
    

    In den Original Fonts gibt es noch Kommentare zwischen dem Head Info und den Font, aber ohne jedes Zeichen, die einen Kommentar einleiten würde. Die editiere ich weg. Das würde zu weit führen, die auch noch einzubeziehen.



  • @zeropage sagte in ASCII Schriftart einlesen:

    In den Original Fonts gibt es noch Kommentare zwischen dem Head Info und den Font, aber ohne jedes Zeichen, die einen Kommentar einleiten würde. Die editiere ich weg. Das würde zu weit führen, die auch noch einzubeziehen.

    Wieso? Die zu ignorieren sollte einfach genug sein.
    Die Schleife kann man dann auch noch etwas vereinfachen:

        while (std::getline(fstream, str)) {
            if (str.empty()) // skip empty lines
                continue;
            if (str.back() != '@') // skip comments
                continue;
    
            str.pop_back(); // remove data line marker
    
            bool const isLastLineOfChar = !str.empty() && str.back() == '@';
            if (isLastLineOfChar)
                str.pop_back(); // remove last-line-of-char marker
    
            fontChar.data.push_back(str);
            if (isLastLineOfChar) {
                fontChar.height = std::size(fontChar.data);
                characterSet.push_back(std::move(fontChar));
                fontChar.clear();
            }
        }
    


  • Mit Kommentar sieht zB so aus:

    flf2a$ 8 7 54 0 12 0 64
    banner.flf version 2 by Ryan Youck (youck@cs.uregina.ca)
    (From a unix program called banner)
    I am not responsible for use of this font  
    Thanks to Glenn Chappell for his help
    Katakana characters by Vinney Thai <ssfiit@eris.cs.umb.edu>
    Cyrillic characters from "koi8x8" BDF font.
    Date: August 11, 1994
    
    Merged by John Cowan <cowan@ccil.org>
    Modified by Paul Burton <solution@earthlink.net> 12/96 to include new parameter
    supported by FIGlet and FIGWin.  May also be slightly modified for better use
    of new full-width/kern/smush alternatives, but default output is NOT changed.
     $ $@
     $ $@
     $ $@
     $ $@
     $ $@
     $ $@
     $ $@
     $ $@@
    

    Manche Fonts schauen so aus:

    flf2a 25 25 45 0 3
    doh.flf by Curtis Wanner (cwanner@acs.bu.edu)
    latest revision - 4/95
    
    
                                     @
                                     @
                   AAA               @
                  A:::A              @
                 A:::::A             @
                A:::::::A            @
               A:::::::::A           @
              A:::::A:::::A          @
             A:::::A A:::::A         @
            A:::::A   A:::::A        @
           A:::::A     A:::::A       @
          A:::::AAAAAAAAA:::::A      @
         A:::::::::::::::::::::A     @
        A:::::AAAAAAAAAAAAA:::::A    @
       A:::::A             A:::::A   @
      A:::::A               A:::::A  @
     A:::::A                 A:::::A @
    AAAAAAA                   AAAAAAA@
                                     @
                                     @
                                     @
                                     @
                                     @
                                     @
                                     @@
                        @
                        @
    BBBBBBBBBBBBBBBBB   @
    B::::::::::::::::B  @
    B::::::BBBBBB:::::B @
    BB:::::B     B:::::B@
      B::::B     B:::::B@
      B::::B     B:::::B@
      B::::BBBBBB:::::B @
      B:::::::::::::BB  @
      B::::BBBBBB:::::B @
      B::::B     B:::::B@
      B::::B     B:::::B@
      B::::B     B:::::B@
    BB:::::BBBBBB::::::B@
    B:::::::::::::::::B @
    B::::::::::::::::B  @
    BBBBBBBBBBBBBBBBB   @
                        @
                        @
                        @
                        @
                        @
                        @
                        @@
    

    Gibts dann auch mit Kleinbuchstaben. Wie soll ich wissen, welches Zeichen zum Kommentar, welches zum Zeichen gehört? Bzw, wann beginnt der Font?

    Danke für die Vereinfachung, hatte ich auch schon begonnen, aber nicht so schick 😉



  • 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?


Log in to reply