Esempio n. 1
0
        /// <summary>
        /// Renders <paramref name="message"/> using this font.
        /// </summary>
        /// <param name="message">The text to render.</param>
        /// <param name="smushOverride">Optional override for the smush settings. Defaults to <c>null</c>, meaning the font's default setting is used.</param>
        /// <returns></returns>
        public string Render(string message, int?smushOverride = null)
        {
            var smush = smushOverride ?? _smushMode;

            var outputLines = Enumerable.Range(0, Height).Select(_ => new StringBuilder()).ToList();

            FiggleCharacter lastCh = null;

            foreach (var c in message)
            {
                var ch = GetCharacter(c);

                if (ch == null)
                {
                    continue;
                }

                var fitMove = CalculateFitMove(lastCh, ch);

                for (var row = 0; row < Height; row++)
                {
                    var charLine   = ch.Lines[row];
                    var outputLine = outputLines[row];

                    if (fitMove != 0)
                    {
                        var toMove = fitMove;
                        if (lastCh != null)
                        {
                            var lineSpace = lastCh.Lines[row].SpaceAfter;
                            if (lineSpace != 0)
                            {
                                var lineSpaceTrim = Math.Min(lineSpace, toMove);
                                toMove            -= lineSpaceTrim;
                                outputLine.Length -= lineSpaceTrim;
                            }
                        }

                        var smushCharIndex = outputLine.Length - 1;
                        var cl             = outputLine[smushCharIndex];

                        outputLine.Append(toMove == 0 ? charLine.Content : charLine.Content.Substring(toMove));

                        if (toMove != 0 && outputLine.Length != 0 && ch.Lines[row].Content.Length != 0)
                        {
                            var cr = ch.Lines[row].Content[toMove - 1];
                            var sc = TrySmush(cl, cr);
                            if (sc != '\0' && smushCharIndex >= 0)
                            {
                                outputLine[smushCharIndex] = sc;
                            }
                        }
                    }
                    else
                    {
                        outputLine.Append(charLine.Content);
                    }
                }

                lastCh = ch;
            }

            var res = new StringBuilder();

            foreach (var outputLine in outputLines)
            {
                res.AppendLine(outputLine.Replace(_hardBlank, ' ').ToString());
            }

            return(res.ToString());

            int CalculateFitMove(FiggleCharacter l, FiggleCharacter r)
            {
                if (smush == SM_FULLWIDTH)
                {
                    return(0);
                }

                if (l == null)
                {
                    return(0); // TODO could still shift b if it had whitespace in the first column
                }
                var minMove = int.MaxValue;

                for (var row = 0; row < Height; row++)
                {
                    var ll = l.Lines[row];
                    var rl = r.Lines[row];

                    var move = ll.SpaceAfter + rl.SpaceBefore;

                    if (TrySmush(ll.BackChar, rl.FrontChar) != '\0')
                    {
                        move++;
                    }

                    if (move < minMove)
                    {
                        minMove = move;
                    }
                }

                Debug.Assert(minMove >= 0, "minMove >= 0");

                return(minMove);
            }

            // TODO disallow smushing if either char's line has a length < 2
            char TrySmush(char l, char r)
            {
                if (l == ' ')
                {
                    return(r);
                }
                if (r == ' ')
                {
                    return(l);
                }

                // kerning
                if ((_smushMode & SM_SMUSH) == 0)
                {
                    return('\0');
                }

                // universal smushing
                if ((_smushMode & 0b00111111) == 0)
                {
                    // prefer visible character in case of hard blanks
                    if (l == _hardBlank)
                    {
                        return(r);
                    }
                    if (r == _hardBlank)
                    {
                        return(l);
                    }

                    // prefer overlapping character depending upon text direction
                    return(Direction == FiggleTextDirection.LeftToRight ? r : l);
                }

                if ((_smushMode & SM_HARDBLANK) != 0 && l == _hardBlank && r == _hardBlank)
                {
                    return(l);
                }

                if (l == _hardBlank && r == _hardBlank)
                {
                    return('\0');
                }

                if ((_smushMode & SM_EQUAL) != 0 && l == r)
                {
                    return(l);
                }

                if ((_smushMode & SM_LOWLINE) != 0)
                {
                    const string lowLineChars = @"|/\[]{}()<>";
                    if (l == '_' && lowLineChars.Contains(r))
                    {
                        return(r);
                    }
                    if (r == '_' && lowLineChars.Contains(l))
                    {
                        return(l);
                    }
                }

                if ((_smushMode & SM_HIERARCHY) != 0)
                {
                    if (l == '|' && @"/\[]{}()<>".Contains(r))
                    {
                        return(r);
                    }
                    if (r == '|' && @"/\[]{}()<>".Contains(l))
                    {
                        return(l);
                    }
                    if ("/\\".Contains(l) && "[]{}()<>".Contains(r))
                    {
                        return(r);
                    }
                    if ("/\\".Contains(r) && "[]{}()<>".Contains(l))
                    {
                        return(l);
                    }
                    if ("[]".Contains(l) && "{}()<>".Contains(r))
                    {
                        return(r);
                    }
                    if ("[]".Contains(r) && "{}()<>".Contains(l))
                    {
                        return(l);
                    }
                    if ("{}".Contains(l) && "()<>".Contains(r))
                    {
                        return(r);
                    }
                    if ("{}".Contains(r) && "()<>".Contains(l))
                    {
                        return(l);
                    }
                    if ("()".Contains(l) && "<>".Contains(r))
                    {
                        return(r);
                    }
                    if ("()".Contains(r) && "<>".Contains(l))
                    {
                        return(l);
                    }
                }

                if ((_smushMode & SM_PAIR) != 0)
                {
                    if (l == '[' && r == ']')
                    {
                        return('|');
                    }
                    if (r == '[' && l == ']')
                    {
                        return('|');
                    }
                    if (l == '{' && r == '}')
                    {
                        return('|');
                    }
                    if (r == '{' && l == '}')
                    {
                        return('|');
                    }
                    if (l == '(' && r == ')')
                    {
                        return('|');
                    }
                    if (r == '(' && l == ')')
                    {
                        return('|');
                    }
                }

                if ((_smushMode & SM_BIGX) != 0)
                {
                    if (l == '/' && r == '\\')
                    {
                        return('|');
                    }
                    if (r == '/' && l == '\\')
                    {
                        return('Y');
                    }
                    if (l == '>' && r == '<')
                    {
                        return('X');
                    }
                }

                return('\0');
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Parses a FIGlet font description stream, and returns a usable <see cref="FiggleFont"/>.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="pool">An optional string pool for merging identical string references.</param>
        /// <returns>The font described by the stream.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <c>null</c>.</exception>
        /// <exception cref="FiggleException">The stream contained an error and could not be parsed.</exception>
        public static FiggleFont Parse(Stream stream, StringPool pool = null)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            // TODO allow specifying encoding
            var reader = new StreamReader(stream);

            var firstLine = reader.ReadLine();

            if (firstLine == null)
            {
                throw new FiggleException("Font file is empty.");
            }

            var match = _firstLinePattern.Match(firstLine);

            if (!match.Success)
            {
                throw new FiggleException("Font file has invalid first line.");
            }

            var hardBlank        = match.Groups["hardblank"].Value[0];
            var height           = int.Parse(match.Groups["height"].Value);
            var baseline         = int.Parse(match.Groups["baseline"].Value);
            var layoutOld        = int.Parse(match.Groups["layoutold"].Value);
            var commentLineCount = int.Parse(match.Groups["commentlinecount"].Value);

            var layoutNewMatch = match.Groups["layoutnew"];
            var layoutNew      = layoutNewMatch.Success
                ? int.Parse(layoutNewMatch.Value)
                : UpgradeLayout();

            int UpgradeLayout()
            {
                if (layoutOld == 0)
                {
                    return(SM_KERN);
                }
                if (layoutOld < 0)
                {
                    return(SM_FULLWIDTH);
                }
                return((layoutOld & 0x1F) | SM_SMUSH);
            }

            var dirMatch  = match.Groups["direction"];
            var direction = dirMatch.Success
                ? (FiggleTextDirection)int.Parse(dirMatch.Value)
                : FiggleTextDirection.LeftToRight;

            // skip comment lines
            for (var i = 0; i < commentLineCount; i++)
            {
                reader.ReadLine();
            }

            if (pool == null)
            {
                pool = new StringPool();
            }

            /*
             * Characters 0-31 are control characters.
             *
             * Characters 32-126 appear in order:
             *
             * 32 (blank/space) 64 @             96  `
             * 33 !             65 A             97  a
             * 34 "             66 B             98  b
             * 35 #             67 C             99  c
             * 36 $             68 D             100 d
             * 37 %             69 E             101 e
             * 38 &             70 F             102 f
             * 39 '             71 G             103 g
             * 40 (             72 H             104 h
             * 41 )             73 I             105 i
             * 42 *             74 J             106 j
             * 43 +             75 K             107 k
             * 44 ,             76 L             108 l
             * 45 -             77 M             109 m
             * 46 .             78 N             110 n
             * 47 /             79 O             111 o
             * 48 0             80 P             112 p
             * 49 1             81 Q             113 q
             * 50 2             82 R             114 r
             * 51 3             83 S             115 s
             * 52 4             84 T             116 t
             * 53 5             85 U             117 u
             * 54 6             86 V             118 v
             * 55 7             87 W             119 w
             * 56 8             88 X             120 x
             * 57 9             89 Y             121 y
             * 58 :             90 Z             122 z
             * 59 ;             91 [             123 {
             * 60 <             92 \             124 |
             * 61 =             93 ]             125 }
             * 62 >             94 ^             126 ~
             * 63 ?             95 _
             *
             * Then codes:
             *
             * 196 Ä
             * 214 Ö
             * 220 Ü
             * 228 ä
             * 246 ö
             * 252 ü
             * 223 ß
             *
             * If some of these characters are not desired, empty characters may be used, having endmarks flush with the margin.
             *
             * After the required characters come "code tagged characters" in range -2147483648 to +2147483647, excluding -1. The assumed mapping is to ASCII/Latin-1/Unicode.
             *
             * A zero character is treated as the character to be used whenever a required character is missing. If no zero character is available, nothing will be printed.
             */

            FiggleCharacter ReadCharacter()
            {
                var lines = new Line[height];

                for (var i = 0; i < height; i++)
                {
                    var line = reader.ReadLine();
                    if (line == null)
                    {
                        throw new FiggleException("Unexpected EOF in Font file.");
                    }
                    // TODO validate single endmark on all lines but last, and double endmark on last
                    // TODO validate all lines are the advertised width (without endmarks)
                    // TODO pool computed space counts too
                    var endmark = line[line.Length - 1];
                    line     = line.TrimEnd(endmark);
                    lines[i] = new Line(pool.Pool(line), CountSolSpaces(line), CountEolSpaces(line));
                }

                return(new FiggleCharacter(lines));

                byte CountSolSpaces(string s)
                {
                    byte count = 0;

                    for (; count < s.Length && s[count] == ' '; count++)
                    {
                    }
                    return(count);
                }

                byte CountEolSpaces(string s)
                {
                    byte count = 0;

                    for (var i = s.Length - 1; i > 0 && s[i] == ' '; i--, count++)
                    {
                    }
                    return(count);
                }
            }

            var requiredCharacters = new FiggleCharacter[256];

            for (var i = 32; i < 127; i++)
            {
                requiredCharacters[i] = ReadCharacter();
            }

            requiredCharacters[196] = ReadCharacter();
            requiredCharacters[214] = ReadCharacter();
            requiredCharacters[220] = ReadCharacter();
            requiredCharacters[228] = ReadCharacter();
            requiredCharacters[246] = ReadCharacter();
            requiredCharacters[252] = ReadCharacter();
            requiredCharacters[223] = ReadCharacter();

            // support code-tagged characters
            var sparseCharacters = new Dictionary <int, FiggleCharacter>();

            while (true)
            {
readLine:
                var line = reader.ReadLine();

                if (line == null)
                {
                    break;
                }

                if (string.IsNullOrWhiteSpace(line))
                {
                    goto readLine;
                }

                if (!ParseUtil.TryParse(line, out int code))
                {
                    throw new FiggleException($"Unsupported code-tagged character code string \"{line}\".");
                }

                if (code >= 0 && code < 256)
                {
                    requiredCharacters[code] = ReadCharacter();
                }
                else
                {
                    sparseCharacters[code] = ReadCharacter();
                }
            }

            return(new FiggleFont(requiredCharacters, sparseCharacters, hardBlank, height, baseline, direction, layoutNew));
        }