From 08eb58f5132d39e4e0be51e9baf77a01caa1faf9 Mon Sep 17 00:00:00 2001 From: David Allemang Date: Thu, 25 May 2017 22:16:36 -0400 Subject: [PATCH] Restructured super/subscript; uses indexed list rather than queue --- WpfTex/LatexEditor/LatexEditor.csproj | 2 +- WpfTex/LatexEditor/LatexViewer.cs | 4 +- WpfTex/LatexEditor/MainWindow.xaml | 17 +- .../Parser/Segments/LatexSegment.cs | 228 -------------- WpfTex/LatexEditor/Parser/Segments/Segment.cs | 296 ++++++++++++++++++ 5 files changed, 308 insertions(+), 239 deletions(-) delete mode 100644 WpfTex/LatexEditor/Parser/Segments/LatexSegment.cs create mode 100644 WpfTex/LatexEditor/Parser/Segments/Segment.cs diff --git a/WpfTex/LatexEditor/LatexEditor.csproj b/WpfTex/LatexEditor/LatexEditor.csproj index afcfec7..81cd367 100644 --- a/WpfTex/LatexEditor/LatexEditor.csproj +++ b/WpfTex/LatexEditor/LatexEditor.csproj @@ -77,7 +77,7 @@ - + diff --git a/WpfTex/LatexEditor/LatexViewer.cs b/WpfTex/LatexEditor/LatexViewer.cs index c873cc7..c787b21 100644 --- a/WpfTex/LatexEditor/LatexViewer.cs +++ b/WpfTex/LatexEditor/LatexViewer.cs @@ -41,7 +41,9 @@ namespace LatexEditor { if (string.IsNullOrEmpty(Content)) return; - var seg = LatexSegment.ToLatexSegment(LatexParser.Tokenize(Content)); +// var seg = Segment.ToLatexSegment(LatexParser.Tokenize(Content)); + + var seg = new NullSegment(new SegmentCollection(LatexParser.Tokenize(Content))); var gds = seg.GlyphDescriptors.ToList(); diff --git a/WpfTex/LatexEditor/MainWindow.xaml b/WpfTex/LatexEditor/MainWindow.xaml index 161dde3..0f63d7f 100644 --- a/WpfTex/LatexEditor/MainWindow.xaml +++ b/WpfTex/LatexEditor/MainWindow.xaml @@ -7,15 +7,14 @@ mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> - - e^{\pi i}=-1\quad\{\textrm{in the complex plane} \\\\ - - a^x+a^y=a^{x+y} \\ - log \sub b x \sup {n} = n \, log_b x \\ - log \sub b x \sup n = n \, log_{b} x \\ - - M^1_2 - + + + + + + a^b_c \quad a^{b}_{c} \\\\ + a^{b^c} + \ No newline at end of file diff --git a/WpfTex/LatexEditor/Parser/Segments/LatexSegment.cs b/WpfTex/LatexEditor/Parser/Segments/LatexSegment.cs deleted file mode 100644 index 6a951b3..0000000 --- a/WpfTex/LatexEditor/Parser/Segments/LatexSegment.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using JetBrains.Annotations; -using LatexEditor.Fonts; - -namespace LatexEditor.Parser.Segments -{ - public abstract class LatexSegment - { - 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) - { - var tokenQ = new Queue(tokens); - var segments = new List(); - while (tokenQ.Count > 0) - if (PopLatexSegment(tokenQ, out var ls)) - segments.Add(ls); - - if (segments.Count == 0) - return null; - if (segments.Count == 1) - return segments[0]; - return new LatexNull(segments); - } - - private static bool PopLatexSegment(Queue tokens, out LatexSegment val) - { - val = null; - - if (tokens.Count == 0) - return false; - - // todo: tabulate this - - var head = tokens.Dequeue(); - switch (head.TokenName) - { - 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]); - switch (head.Value) - { - case "^": - case "sup": - if (PopLatexSegment(tokens, out var content)) - { - val = content; - val.Size *= 0.7; - val.Offset = new Point(val.Offset.X, val.Offset.Y + 0.45); - } - break; - case "_": - case "sub": - if (PopLatexSegment(tokens, out content)) - { - val = content; - val.Size *= 0.7; - val.Offset = new Point(val.Offset.X, val.Offset.Y - 0.45); - } - break; - } - break; - - case "escape": - if (head.Value == "\\") - val = new LatexReturn(); - else - val = new LatexGlyph(CmFont.Serif, head.Value[0]); - 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/Segment.cs b/WpfTex/LatexEditor/Parser/Segments/Segment.cs new file mode 100644 index 0000000..44fda9f --- /dev/null +++ b/WpfTex/LatexEditor/Parser/Segments/Segment.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Windows; +using System.Windows.Documents.DocumentStructures; +using JetBrains.Annotations; +using LatexEditor.Fonts; + +namespace LatexEditor.Parser.Segments +{ + public class SegmentCollection : IEnumerable + { + private List _tokens; + + public SegmentCollection(IEnumerable tokens) + { + _tokens = tokens.ToList(); + } + + public IEnumerator GetEnumerator() + { + var tokenIndex = 0; + while (tokenIndex < _tokens.Count) + { + var seg = SegmentAt(tokenIndex, out int count); + + if (seg == null) yield break; + + yield return seg; + tokenIndex += count; + } + } + + private Segment SegmentAt(int index, out int numTokens) + { + numTokens = 1; + + if (index > _tokens.Count) + return null; + + // todo: tabulate this + + var tok = _tokens[index]; + if (tok.TokenName == "letter") + return new GlyphSegment(CmFont.SerifItalic, tok.Value[0]); + if (tok.TokenName == "number") + return new GlyphSegment(CmFont.Serif, tok.Value[0]); + if (tok.TokenName == "open") + { + var seg = new NullSegment(); + var i = index + 1; + var total = 1; + while (true) + { + if (i >= _tokens.Count) + return null; + + if (_tokens[i].TokenName == "close") + { + numTokens = total + 2; + return seg; + } + + var content = SegmentAt(i, out var n); + if (content == null) + return null; + seg.Contents.Add(content); + total += n; + i += n; + } + } + if (tok.TokenName == "command") + { + if (LatexParser.GreekLetters.ContainsKey(tok.Value)) + return new GlyphSegment(CmFont.Serif, LatexParser.GreekLetters[tok.Value]); + if (LatexParser.Spaces.ContainsKey(tok.Value)) + return new Space(LatexParser.Spaces[tok.Value]); + if (tok.Value == "^") + { + var sup = SegmentAt(index + 1, out var n); + if (sup == null) + return null; + numTokens = n + 1; + + Segment sub = new NullSegment(); + + if (index + numTokens < _tokens.Count) + { + var tok2 = _tokens[index + numTokens]; + Debug.WriteLine(tok2); + if (tok2.TokenName == "command" && tok2.Value == "_") + { + var s = SegmentAt(index + numTokens + 1, out n); + if (s != null) + { + sub = s; + numTokens += n + 1; + } + } + } + + return new SupSub(sup, sub); + } + if (tok.Value == "_") + { + var sub = SegmentAt(index + 1, out var n); + if (sub == null) + return null; + numTokens = n + 1; + + Segment sup = new NullSegment(); + + if (index + numTokens < _tokens.Count) + { + var tok2 = _tokens[index + numTokens]; + if (tok2.TokenName == "command" && tok2.Value == "^") + { + var s = SegmentAt(index + numTokens + 1, out n); + if (s != null) + { + sup = s; + numTokens += n + 1; + } + } + } + + return new SupSub(sup, sub); + } + } + if (tok.TokenName == "escape") + { + if (tok.Value == "\\") + return new Return(); + } + if (tok.TokenName == "whitespace") + return new NullSegment(); + + return new NullSegment(_tokens[index].Value.Select(c => new GlyphSegment(CmFont.Serif, c))); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + public abstract class Segment + { + public List Contents { get; } = new List(); + + public abstract double Width { get; } + public abstract double Height { get; } + + public double Size { get; set; } = 1; + public Point Offset { get; set; } = new Point(0, 0); + + 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 Return) + { + o_x = 0; + o_y -= Size; + 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 GlyphSegment : Segment + { + private GlyphDescriptor _glyphDescriptor; + public override double Width => _glyphDescriptor.AdvanceWidth * Size; + public override double Height => _glyphDescriptor.AdvanceHeight * Size; + + public int CharId { get; } + + public char Char => (char) CharId; + + public GlyphSegment(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; + } + } + } + + public class Return : Segment + { + public override double Width => 0; + public override double Height => 0; + } + + // todo: should be able to implement Return through latexspace. + public class Space : Segment + { + public override double Width { get; } + public override double Height { get; } + + public Space(double width, double height = 0) + { + Width = width; + Height = height; + } + } + + public class NullSegment : Segment + { + public override double Width => Contents.Sum(ls => ls.Width); + public override double Height => Contents.Max(ls => ls.Height); + + public NullSegment() + { + } + + public NullSegment(IEnumerable contents) + { + Contents.AddRange(contents); + } + } + + public class SupSub : Segment + { + public override double Width => Contents.Max(s => s.Width); + public override double Height => Contents.Sum(s => s.Height) + .1; + + public SupSub(Segment super, Segment sub) + { + Contents.Add(super); + Contents.Add(sub); + } + + public override IEnumerable GlyphDescriptors + { + get + { + foreach (var sup in Contents[0].GlyphDescriptors) + { + var cp = sup; + cp.Offset.X += Offset.X; + cp.Offset.Y += 0.45; + cp.Size *= 0.7; + yield return cp; + } + foreach (var sub in Contents[1].GlyphDescriptors) + { + var cp = sub; + cp.Offset.X += Offset.X; + cp.Offset.Y -= 0.2; + cp.Size *= 0.7; + yield return cp; + } + } + } + } +} \ No newline at end of file