From 7b17a53ceb84fb1ccc3ee9404934ff25fb3aa100 Mon Sep 17 00:00:00 2001 From: David Allemang Date: Thu, 25 May 2017 16:16:28 -0400 Subject: [PATCH] corrected grouping behavior --- WpfTex/LatexEditor/Fonts/GlyphDescriptor.cs | 37 +++ WpfTex/LatexEditor/Fonts/GlyphInfo.cs | 34 --- WpfTex/LatexEditor/LatexEditor.csproj | 10 +- WpfTex/LatexEditor/LatexViewer.cs | 33 +-- WpfTex/LatexEditor/MainWindow.xaml | 28 +-- .../Parser/{Parser.cs => LatexParser.cs} | 12 +- .../LatexEditor/Parser/Segments/LatexNull.cs | 13 -- .../Parser/Segments/LatexReturn.cs | 18 -- .../LatexEditor/Parser/Segments/LatexRun.cs | 41 ---- .../Parser/Segments/LatexSegment.cs | 214 ++++++++++++++---- .../LatexEditor/Parser/Segments/LatexSpace.cs | 19 -- .../LatexEditor/Parser/Segments/LatexSuper.cs | 26 --- .../LatexEditor/Parser/Segments/LatexText.cs | 35 --- 13 files changed, 247 insertions(+), 273 deletions(-) create mode 100644 WpfTex/LatexEditor/Fonts/GlyphDescriptor.cs delete mode 100644 WpfTex/LatexEditor/Fonts/GlyphInfo.cs rename WpfTex/LatexEditor/Parser/{Parser.cs => LatexParser.cs} (85%) delete mode 100644 WpfTex/LatexEditor/Parser/Segments/LatexNull.cs delete mode 100644 WpfTex/LatexEditor/Parser/Segments/LatexReturn.cs delete mode 100644 WpfTex/LatexEditor/Parser/Segments/LatexRun.cs delete mode 100644 WpfTex/LatexEditor/Parser/Segments/LatexSpace.cs delete mode 100644 WpfTex/LatexEditor/Parser/Segments/LatexSuper.cs delete mode 100644 WpfTex/LatexEditor/Parser/Segments/LatexText.cs diff --git a/WpfTex/LatexEditor/Fonts/GlyphDescriptor.cs b/WpfTex/LatexEditor/Fonts/GlyphDescriptor.cs new file mode 100644 index 0000000..e783e5d --- /dev/null +++ b/WpfTex/LatexEditor/Fonts/GlyphDescriptor.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; + +namespace LatexEditor.Fonts +{ + public struct GlyphDescriptor + { + public GlyphTypeface Typeface; + public ushort Index; + public string CharacterName; + public double Size; + public Point Offset; + + public double AdvanceWidth => Typeface.AdvanceWidths[Index] * Size; + public double AdvanceHeight => Typeface.AdvanceHeights[Index] * Size; + public double Height => Typeface.Height * Size; + + public GlyphDescriptor(GlyphTypeface typeface, int character) + { + Typeface = typeface; + Index = Typeface.CharacterToGlyphMap[character]; + CharacterName = ((char) character).ToString(); + Offset = new Point(0, 0); + Size = 1; + } + + public override string ToString() + { + return $"'{CharacterName}' ({Index}) {Offset} {AdvanceWidth}x{AdvanceHeight} [{Height}]"; + } + } +} \ No newline at end of file diff --git a/WpfTex/LatexEditor/Fonts/GlyphInfo.cs b/WpfTex/LatexEditor/Fonts/GlyphInfo.cs deleted file mode 100644 index 05b6a32..0000000 --- a/WpfTex/LatexEditor/Fonts/GlyphInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Windows; -using System.Windows.Media; - -namespace LatexEditor.Fonts -{ - public class GlyphInfo - { - public CmFont Font; - public double RelativeSize; - public int Char; - public Point RelativeOffset; - public Point BaselineOrigin; - - public GlyphTypeface Gtf => Font.GlyphTypeface(); - - public ushort Index => Gtf.CharacterToGlyphMap[Char]; - - public double RelAdvWidth => Gtf.AdvanceWidths[Index]; - public double RelAdvHeight => Gtf.AdvanceHeights[Index]; - - public Point Offset => new Point( - BaselineOrigin.X + RelativeOffset.X * RelativeSize, - BaselineOrigin.Y + RelativeOffset.Y * RelativeSize); - - public GlyphInfo(CmFont font, int c, Point relativeOffset, Point baselineOrigin, double relativeSize = 1) - { - Font = font; - RelativeSize = relativeSize; - RelativeOffset = relativeOffset; - BaselineOrigin = baselineOrigin; - Char = c; - } - } -} \ No newline at end of file diff --git a/WpfTex/LatexEditor/LatexEditor.csproj b/WpfTex/LatexEditor/LatexEditor.csproj index e4f92c1..afcfec7 100644 --- a/WpfTex/LatexEditor/LatexEditor.csproj +++ b/WpfTex/LatexEditor/LatexEditor.csproj @@ -67,7 +67,7 @@ - + @@ -76,14 +76,8 @@ - - - - + - - - diff --git a/WpfTex/LatexEditor/LatexViewer.cs b/WpfTex/LatexEditor/LatexViewer.cs index ae42efe..c873cc7 100644 --- a/WpfTex/LatexEditor/LatexViewer.cs +++ b/WpfTex/LatexEditor/LatexViewer.cs @@ -4,6 +4,9 @@ using System.Linq; using System.Windows; using System.Windows.Markup; using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using LatexEditor.Parser; +using LatexEditor.Parser.Segments; namespace LatexEditor { @@ -36,30 +39,32 @@ namespace LatexEditor protected override void OnRender(DrawingContext dc) { - Debug.WriteLine(dc, nameof(LatexViewer)); - Debug.WriteLine(dc.GetType(), nameof(LatexViewer)); - if (string.IsNullOrEmpty(Content)) return; - var glyphInfoList = Parser.Parser.ToGlyphInfos(Content); + var seg = LatexSegment.ToLatexSegment(LatexParser.Tokenize(Content)); - var gtfGroups = glyphInfoList.GroupBy(gi => gi.Gtf); - foreach (var gtfGroup in gtfGroups) + var gds = seg.GlyphDescriptors.ToList(); + + var tfGroups = gds.GroupBy(gd => gd.Typeface); + foreach (var tfGroup in tfGroups) { - var gtf = gtfGroup.Key; + var tf = tfGroup.Key; - var sizeGroups = gtfGroup.GroupBy(gi => gi.RelativeSize); + var sizeGroups = tfGroup.GroupBy(gd => gd.Size); foreach (var sizeGroup in sizeGroups) { var size = sizeGroup.Key; - var glyphs = sizeGroup.Select(gi => gi.Index).ToList(); - var advanceWidths = new double[glyphs.Count]; // all zero - position based only on offset - var offsets = sizeGroup.Select(gi => new Point(gi.Offset.X * FontSize, gi.Offset.Y * FontSize)) + var glyphs = sizeGroup.Select(gd => gd.Index).ToList(); + var advWidths = new double[glyphs.Count]; // base only on offset + var offsets = sizeGroup.Select(gd => gd.Offset) + .Select(o => new Point(o.X * FontSize, o.Y * FontSize)) .ToList(); - var gr = new GlyphRun(gtf, 0, false, size * FontSize, - glyphs, new Point(0, FontSize), advanceWidths, - offsets, null, null, null, null, null); + var gr = new GlyphRun( + tf, 0, false, size * FontSize, + glyphs, new Point(0, FontSize), advWidths, + offsets, null, null, null, null, null + ); dc.DrawGlyphRun(Brushes.Black, gr); } diff --git a/WpfTex/LatexEditor/MainWindow.xaml b/WpfTex/LatexEditor/MainWindow.xaml index f570ad9..0b5c766 100644 --- a/WpfTex/LatexEditor/MainWindow.xaml +++ b/WpfTex/LatexEditor/MainWindow.xaml @@ -8,20 +8,20 @@ Title="MainWindow" Height="350" Width="525"> - - {2}{123} - - - - - - - - - - - - + e^{\pi i}=-1\{\textrm{in the complex plane} \\\\ + + A \ \alpha \quad N \ \nu \\ + B \ \beta \quad \Xi \ \xi \\ + \Gamma \ \gamma \quad O \ \omicron \\ + \Delta \ \delta \quad \Pi \ \pi \\ + E \ \epsilon \quad P \ \rho \\ + Z \ \zeta \quad \Sigma \ \sigma \\ + H \ \eta \quad T \ \tau \\ + \Theta \ \theta \quad Y \ \upsilon \\ + I \ \iota \quad \Phi \ \phi \\ + K \ \kappa \quad X \ \chi \\ + \Lambda \ \lambda \quad \Psi \ \psi \\ + M \ \mu \quad \Omega \ \omega diff --git a/WpfTex/LatexEditor/Parser/Parser.cs b/WpfTex/LatexEditor/Parser/LatexParser.cs similarity index 85% rename from WpfTex/LatexEditor/Parser/Parser.cs rename to WpfTex/LatexEditor/Parser/LatexParser.cs index 748423c..0cdf096 100644 --- a/WpfTex/LatexEditor/Parser/Parser.cs +++ b/WpfTex/LatexEditor/Parser/LatexParser.cs @@ -4,14 +4,14 @@ using LatexEditor.Parser.Segments; namespace LatexEditor.Parser { - public static class Parser + public static class LatexParser { // todo: move these tables to an environment class private static readonly Lexer LatexLexer = new Lexer() { new TokenDescriptor("whitespace", @"\s+"), new TokenDescriptor("escape", @"\\([\#\$\%\^\&_\{\}\~\\])", 1), - new TokenDescriptor("command", @"\\([^\d\s\\]+|[\ ])", 1), + new TokenDescriptor("command", @"\\([^\d\s\\\#\$\%\^\&_{}]+|[\ ])", 1), new TokenDescriptor("command", @"[\#\$\%\^\&_]"), new TokenDescriptor("number", @"\d"), new TokenDescriptor("open", @"\{"), @@ -73,12 +73,6 @@ namespace LatexEditor.Parser ["qquad"] = 4, }; - public static IEnumerable ToGlyphInfos(string latex) => ToGlyphInfos(LatexLexer.Tokenize(latex)); - - private static IEnumerable ToGlyphInfos(IEnumerable tokens) - { - var lss = LatexSegment.ToLatexSegment(tokens); - return lss.Glyphs; - } + public static IEnumerable Tokenize(string latex) => LatexLexer.Tokenize(latex); } } \ No newline at end of file diff --git a/WpfTex/LatexEditor/Parser/Segments/LatexNull.cs b/WpfTex/LatexEditor/Parser/Segments/LatexNull.cs deleted file mode 100644 index c0acfda..0000000 --- a/WpfTex/LatexEditor/Parser/Segments/LatexNull.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LatexEditor.Fonts; - -namespace LatexEditor.Parser.Segments -{ - public class LatexNull : LatexSegment - { - public override IEnumerable Glyphs => Enumerable.Empty(); - public override double RelAdvWidth => 0; - public override double RelAdvHeight => 0; - } -} \ No newline at end of file diff --git a/WpfTex/LatexEditor/Parser/Segments/LatexReturn.cs b/WpfTex/LatexEditor/Parser/Segments/LatexReturn.cs deleted file mode 100644 index d39ac7d..0000000 --- a/WpfTex/LatexEditor/Parser/Segments/LatexReturn.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LatexEditor.Fonts; - -namespace LatexEditor.Parser.Segments -{ - public class LatexReturn : LatexSegment - { - public override IEnumerable Glyphs => Enumerable.Empty(); - public override double RelAdvWidth => 0; - public override double RelAdvHeight { get; } - - public LatexReturn(double relAdvHeight) - { - RelAdvHeight = relAdvHeight; - } - } -} \ No newline at end of file diff --git a/WpfTex/LatexEditor/Parser/Segments/LatexRun.cs b/WpfTex/LatexEditor/Parser/Segments/LatexRun.cs deleted file mode 100644 index 3d7aaee..0000000 --- a/WpfTex/LatexEditor/Parser/Segments/LatexRun.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LatexEditor.Fonts; - -namespace LatexEditor.Parser.Segments -{ - public class LatexRun : LatexSegment - { - public override IEnumerable Glyphs => Segments.SelectMany(ls => ls.Glyphs); - - public override double RelAdvWidth { get; } - public override double RelAdvHeight { get; } - - public IEnumerable Segments { get; set; } - - public LatexRun(IEnumerable segments) - { - Segments = segments; - - // to shift characters into position - var x = 0d; - var y = 0d; - foreach (var segment in Segments) - { - if (segment is LatexReturn) - { - x = 0d; - y -= segment.RelAdvHeight; - continue; - } - foreach (var gi in segment.Glyphs) - { - gi.BaselineOrigin.X = x; - gi.BaselineOrigin.Y = y; - } - x += segment.RelAdvWidth; - } - RelAdvWidth = x; - } - } -} \ No newline at end of file diff --git a/WpfTex/LatexEditor/Parser/Segments/LatexSegment.cs b/WpfTex/LatexEditor/Parser/Segments/LatexSegment.cs index d00212c..0233a31 100644 --- a/WpfTex/LatexEditor/Parser/Segments/LatexSegment.cs +++ b/WpfTex/LatexEditor/Parser/Segments/LatexSegment.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using System.Windows; using JetBrains.Annotations; using LatexEditor.Fonts; @@ -6,8 +8,15 @@ namespace LatexEditor.Parser.Segments { public abstract class LatexSegment { - [NotNull] - public abstract IEnumerable Glyphs { get; } + public List Contents { get; } = new List(); + + public abstract double Width { get; } + public abstract double Height { get; } + + public double Size { get; protected set; } = 1; + public Point Offset { get; protected set; } = new Point(0, 0); + + public abstract IEnumerable Glyphs { get; } public static LatexSegment ToLatexSegment(IEnumerable tokens) { @@ -18,15 +27,12 @@ namespace LatexEditor.Parser.Segments segments.Add(ls); if (segments.Count == 0) - return new LatexNull(); + return null; if (segments.Count == 1) return segments[0]; - return new LatexRun(segments); + return new LatexNull(segments); } - public abstract double RelAdvWidth { get; } - public abstract double RelAdvHeight { get; } - private static bool PopLatexSegment(Queue tokens, out LatexSegment val) { val = null; @@ -37,48 +43,172 @@ namespace LatexEditor.Parser.Segments // todo: tabulate this var head = tokens.Dequeue(); - if (head.TokenName == "letter") - val = new LatexText(CmFont.SerifItalic, head.Value); - if (head.TokenName == "command") + switch (head.TokenName) { - if (Parser.GreekLetters.ContainsKey(head.Value)) - val = new LatexText(CmFont.SerifItalic, Parser.GreekLetters[head.Value]); - if (Parser.Spaces.ContainsKey(head.Value)) - val = new LatexSpace(Parser.Spaces[head.Value]); - if (head.Value == "^") - if (PopLatexSegment(tokens, out var content)) + case "letter": + val = new LatexGlyph(CmFont.SerifItalic, head.Value[0]); + break; + + case "number": + val = new LatexGlyph(CmFont.Serif, head.Value[0]); + break; + + case "open": // todo: implement proper lookahead + // currently crashes if no closing brace present + val = new LatexNull(); + while (tokens.Peek().TokenName != "close") + if (PopLatexSegment(tokens, out var content)) + val.Contents.Add(content); + tokens.Dequeue(); // pop close token + break; + + case "command": + if (LatexParser.GreekLetters.ContainsKey(head.Value)) + val = new LatexGlyph(CmFont.Serif, LatexParser.GreekLetters[head.Value]); + if (LatexParser.Spaces.ContainsKey(head.Value)) + val = new LatexSpace(LatexParser.Spaces[head.Value]); + if (head.Value == "^") { - val = new LatexSuper(content); + if (PopLatexSegment(tokens, out var content)) + { + val = content; + val.Size *= 0.7; + val.Offset = new Point(val.Offset.X, val.Offset.Y + 0.45); + } } - else // to prevent possible nullref, but still incorrect. - { - val = null; - return false; - } - if (val == null) // if no command matches - val = new LatexText(CmFont.SerifItalic, head.CapturedString); - } - if (head.TokenName == "number") - val = new LatexText(CmFont.Serif, head.Value); - if (head.TokenName == "escape") - if (head.Value == "\\") - val = new LatexReturn(1); - if (head.TokenName == "open") - { - // todo: fix incorrect glyph placement within {} - // Should create a LatexGlyph: LatexSegment, and change LatexSegment.Glyphs - // to be of LatexGlyphs.Then, ON ITERATION, not creation, compute the altered positions of - // the LatexGlyphs and create the GlyphRuns from that. - var segments = new List(); - while (tokens.Peek().TokenName != "close") - if (PopLatexSegment(tokens, out var content)) - segments.Add(content); - tokens.Dequeue(); // toss the close - val = new LatexRun(segments); + break; + + case "escape": + if (head.Value == "\\") + val = new LatexReturn(); + break; } return val != null || PopLatexSegment(tokens, out val); } + + public override string ToString() + { + return $"{GetType().Name}: [{string.Join(", ", Contents.Select(c => "{" + c + "}"))}]"; + } + + + public virtual IEnumerable GlyphDescriptors + { + get + { + var o_x = 0d; + var o_y = 0d; + foreach (var seg in Contents) + { + if (seg is LatexReturn) + { + o_x = 0; + o_y -= 1; + continue; + } + foreach (var gd in seg.GlyphDescriptors) + { + var cp = gd; + cp.Size *= Size; + cp.Offset.X += o_x + Offset.X; + cp.Offset.Y += o_y + Offset.Y; + yield return cp; + } + + o_x += seg.Width * Size; + } + } + } } + public class LatexGlyph : LatexSegment + { + private GlyphDescriptor _glyphDescriptor; + public override double Width => _glyphDescriptor.AdvanceWidth * Size; + public override double Height => _glyphDescriptor.AdvanceHeight * Size; + + public override IEnumerable Glyphs + { + get { yield return this; } + } + + public int CharId { get; } + + public char Char => (char) CharId; + + public LatexGlyph(CmFont font, int charId) + { + CharId = charId; + _glyphDescriptor = new GlyphDescriptor(font.GlyphTypeface(), CharId); + } + + public override string ToString() + { + return $"{base.ToString()}, '{Char}' ({CharId})"; + } + + public override IEnumerable GlyphDescriptors + { + get + { + var cp = _glyphDescriptor; + cp.Size *= Size; + cp.Offset.X += Offset.X; + cp.Offset.Y += Offset.Y; + yield return cp; + } + } + + // todo: essentially duplicate glyphinfo functionality here + // need to make sure that parent has control over offset and new relative size. + // Width and Height should account for this. + // easiest solution would be an "offset" method that returns a modified clone. + } + + public class LatexReturn : LatexSegment + { + public override double Width => 0; + public override double Height => 0; + public override IEnumerable Glyphs => Enumerable.Empty(); + } + + // todo: should be able to implement LatexReturn through latexspace. + public class LatexSpace : LatexSegment + { + public override double Width { get; } + public override double Height { get; } + public override IEnumerable Glyphs => Enumerable.Empty(); + + public LatexSpace(double width, double height = 0) + { + Width = width; + Height = height; + } + } + + public class LatexNull : LatexSegment + { + public override double Width => Contents.Sum(ls => ls.Width); + public override double Height => Contents.Max(ls => ls.Height); + + public override IEnumerable Glyphs + { + get + { + foreach (var ls in Contents) + foreach (var lg in ls.Glyphs) + yield return lg; + } + } + + public LatexNull() + { + } + + public LatexNull(IEnumerable contents) + { + Contents.AddRange(contents); + } + } } \ No newline at end of file diff --git a/WpfTex/LatexEditor/Parser/Segments/LatexSpace.cs b/WpfTex/LatexEditor/Parser/Segments/LatexSpace.cs deleted file mode 100644 index 1772bcd..0000000 --- a/WpfTex/LatexEditor/Parser/Segments/LatexSpace.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LatexEditor.Fonts; - -namespace LatexEditor.Parser.Segments -{ - public class LatexSpace : LatexSegment - { - public override IEnumerable Glyphs => Enumerable.Empty(); - public override double RelAdvWidth { get; } - public override double RelAdvHeight { get; } - - public LatexSpace(double relAdvWidth) - { - RelAdvWidth = relAdvWidth; - RelAdvHeight = 0; - } - } -} \ No newline at end of file diff --git a/WpfTex/LatexEditor/Parser/Segments/LatexSuper.cs b/WpfTex/LatexEditor/Parser/Segments/LatexSuper.cs deleted file mode 100644 index f5d36ae..0000000 --- a/WpfTex/LatexEditor/Parser/Segments/LatexSuper.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using LatexEditor.Fonts; - -namespace LatexEditor.Parser.Segments -{ - public class LatexSuper : LatexSegment - { - public override IEnumerable Glyphs => Content.Glyphs; - - public override double RelAdvWidth => Content.RelAdvWidth; - public override double RelAdvHeight => Content.RelAdvHeight; - - public LatexSegment Content { get; set; } - - public LatexSuper(LatexSegment content) - { - Content = content; - - foreach (var gi in Content.Glyphs) - { - gi.RelativeOffset.Y += 1; - gi.RelativeSize *= 0.5; - } - } - } -} \ No newline at end of file diff --git a/WpfTex/LatexEditor/Parser/Segments/LatexText.cs b/WpfTex/LatexEditor/Parser/Segments/LatexText.cs deleted file mode 100644 index f257220..0000000 --- a/WpfTex/LatexEditor/Parser/Segments/LatexText.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Windows; -using LatexEditor.Fonts; - -namespace LatexEditor.Parser.Segments -{ - public class LatexText : LatexSegment - { - private readonly List _glyphs = new List(); - - public override IEnumerable Glyphs => _glyphs; - public override double RelAdvWidth { get; } - public override double RelAdvHeight { get; } - - public LatexText(CmFont cmFont, int c) - { - var gi = new GlyphInfo(cmFont, c, new Point(0, 0), new Point(0, 0)); - _glyphs.Add(gi); - RelAdvWidth = gi.RelAdvWidth; - RelAdvHeight = gi.RelAdvHeight; - } - - public LatexText(CmFont cmFont, string text) - { - var x = 0d; - foreach (var c in text) - { - var gi = new GlyphInfo(cmFont, c, new Point(x, 0), new Point(0, 0)); - _glyphs.Add(gi); - x += gi.RelAdvWidth; - } - RelAdvWidth = x; - } - } -} \ No newline at end of file