diff --git a/WpfTex/LatexEditor/Fonts/GlyphInfo.cs b/WpfTex/LatexEditor/Fonts/GlyphInfo.cs
index d5bb0d4..b755ac4 100644
--- a/WpfTex/LatexEditor/Fonts/GlyphInfo.cs
+++ b/WpfTex/LatexEditor/Fonts/GlyphInfo.cs
@@ -1,30 +1,36 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
+using JetBrains.Annotations;
namespace LatexEditor.Fonts
{
public class GlyphInfo
{
public CmFont Font;
- public Point Offset;
- public double Size;
- private int Char;
+ public double RelativeSize;
+ public int Char;
+ public Point RelativeOffset;
+ public Point BaselineOrigin;
public GlyphTypeface Gtf => Font.GlyphTypeface();
- ///
- /// Index of this glyph within the GlyphTypeface.
- ///
- /// Thrown if does not have a glyph for char
public ushort Index => Gtf.CharacterToGlyphMap[Char];
- public GlyphInfo(CmFont font, int character, double size, Point offset)
+ 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;
- Char = character;
- Size = size;
- Offset = offset;
+ 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 6881c74..ab3dc79 100644
--- a/WpfTex/LatexEditor/LatexEditor.csproj
+++ b/WpfTex/LatexEditor/LatexEditor.csproj
@@ -76,7 +76,13 @@
+
+
+
+
+
+
diff --git a/WpfTex/LatexEditor/LatexViewer.cs b/WpfTex/LatexEditor/LatexViewer.cs
index 073fa6f..b6dce6a 100644
--- a/WpfTex/LatexEditor/LatexViewer.cs
+++ b/WpfTex/LatexEditor/LatexViewer.cs
@@ -41,83 +41,6 @@ namespace LatexEditor
set => SetValue(FontSizeProperty, value);
}
- protected List Parse(string latex)
- {
- var x = 0d;
- var y = 0d;
- var font = CmFont.SerifItalic;
- var size = FontSize;
-
- var glyphs = new List(latex.Length);
-
- var state = new Stack();
- state.Push("-");
-
- while (latex.Length > 0)
- {
- var s = state.Peek();
-
- if (s == "cmd")
- {
- state.Pop();
- var m = Regex.Match(latex, @"([a-z]+)(?=[^a-z]|$)", RegexOptions.IgnoreCase);
- if (m.Index == 0)
- {
- var cmd = latex.Substring(0, m.Groups[1].Length);
- switch (cmd)
- {
- case "it":
- font = CmFont.SerifItalic;
- break;
- case "sym":
- font = CmFont.Symbols;
- break;
- case "rm":
- font = CmFont.Serif;
- break;
- case "sup":
- size = FontSize * 0.4;
- y = FontSize * (1 - 0.4);
- break;
- case "sub":
- size = FontSize * 0.5;
- y = -FontSize * 0.5 * 0.5;
- break;
- default:
- continue;
- }
- latex = latex.Substring(m.Length).TrimStart(' ');
- }
- else
- {
- latex = latex.Substring(1);
- }
- }
- else
- {
- if (latex.StartsWith("\\"))
- {
- latex = latex.Substring(1);
-
- state.Push("cmd");
- }
- else
- {
- var chr = latex[0];
- latex = latex.Substring(1);
-
- var gtf = font.GlyphTypeface();
- var point = new Point(x, y);
- var gi = new GlyphInfo(font, chr, size, point);
- x += gtf.AdvanceWidths[gi.Index] * size;
- glyphs.Add(gi);
- }
- }
- }
-
- return glyphs;
- }
-
protected override void OnRender(DrawingContext dc)
{
Debug.WriteLine(dc, nameof(LatexViewer));
@@ -125,22 +48,23 @@ namespace LatexEditor
if (string.IsNullOrEmpty(Content)) return;
- var glyphInfoList = LatexParser.Parse(Content);
+ var glyphInfoList = LatexParser.ToGlyphInfos(Content);
var gtfGroups = glyphInfoList.GroupBy(gi => gi.Gtf);
foreach (var gtfGroup in gtfGroups)
{
var gtf = gtfGroup.Key;
- var sizeGroups = gtfGroup.GroupBy(gi => gi.Size);
+ var sizeGroups = gtfGroup.GroupBy(gi => gi.RelativeSize);
foreach (var sizeGroup in sizeGroups)
{
var size = sizeGroup.Key;
var glyphs = sizeGroup.Select(gi => gi.Index).ToList();
- var advanceWidths = new double[glyphs.Count];
- var offsets = sizeGroup.Select(gi => gi.Offset).ToList();
- var gr = new GlyphRun(gtf, 0, false, size,
+ 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))
+ .ToList();
+ var gr = new GlyphRun(gtf, 0, false, size * FontSize,
glyphs, new Point(0, FontSize), advanceWidths,
offsets, null, null, null, null, null);
diff --git a/WpfTex/LatexEditor/MainWindow.xaml b/WpfTex/LatexEditor/MainWindow.xaml
index 31004be..f570ad9 100644
--- a/WpfTex/LatexEditor/MainWindow.xaml
+++ b/WpfTex/LatexEditor/MainWindow.xaml
@@ -8,20 +8,20 @@
Title="MainWindow" Height="350" Width="525">
-
- 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
-
+
+ {2}{123}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WpfTex/LatexEditor/Parser/LatexNull.cs b/WpfTex/LatexEditor/Parser/LatexNull.cs
new file mode 100644
index 0000000..45b88c8
--- /dev/null
+++ b/WpfTex/LatexEditor/Parser/LatexNull.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using System.Linq;
+using LatexEditor.Fonts;
+
+namespace LatexEditor.Parser
+{
+ 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/LatexParser.cs b/WpfTex/LatexEditor/Parser/LatexParser.cs
index 8121c64..ab0a044 100644
--- a/WpfTex/LatexEditor/Parser/LatexParser.cs
+++ b/WpfTex/LatexEditor/Parser/LatexParser.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
-using System.Windows;
+using System.Runtime.CompilerServices;
+using System.Windows.Navigation;
using LatexEditor.Fonts;
namespace LatexEditor.Parser
@@ -18,7 +19,7 @@ namespace LatexEditor.Parser
new TokenDescriptor("letter", @"[\S]"), // nearly catch-all for uncaptured symbols
};
- private static readonly Dictionary greekLetters = new Dictionary
+ internal static readonly Dictionary GreekLetters = new Dictionary
{
["alpha"] = 0x03b1,
["beta"] = 0x03b2,
@@ -45,19 +46,22 @@ namespace LatexEditor.Parser
["psi"] = 0x03c8,
["omega"] = 0x03c9,
- ["Gamma"] = 0x0393,
- ["Delta"] = 0x0394,
- ["Theta"] = 0x0398,
- ["Lambda"] = 0x039b,
- ["Xi"] = 0x039e,
- ["Pi"] = 0x03a0,
- ["Sigma"] = 0x03a3,
- ["Phi"] = 0x03a6,
- ["Psi"] = 0x03a8,
- ["Omega"] = 0x03a9,
+ // Alpha Beta Epsilon Zeta Eta Iota Kappa Mu Nu
+ // Omicron Rho Tau and Upsilon all denoted by
+ // Latin symbols A B E Z H I K M N O P T and Y
+ ["Gamma"] = 0x0393,
+ ["Delta"] = 0x0394,
+ ["Theta"] = 0x0398,
+ ["Lambda"] = 0x039b,
+ ["Xi"] = 0x039e,
+ ["Pi"] = 0x03a0,
+ ["Sigma"] = 0x03a3,
+ ["Phi"] = 0x03a6,
+ ["Psi"] = 0x03a8,
+ ["Omega"] = 0x03a9,
};
- private static readonly Dictionary spaces = new Dictionary
+ internal static readonly Dictionary Spaces = new Dictionary
{
[","] = 0.16666,
["!"] = -0.16666,
@@ -69,57 +73,12 @@ namespace LatexEditor.Parser
["qquad"] = 4,
};
- public static IEnumerable Parse(string latex) => Parse(LatexLexer.Tokenize(latex));
+ public static IEnumerable ToGlyphInfos(string latex) => ToGlyphInfos(LatexLexer.Tokenize(latex));
- private static IEnumerable Parse(IEnumerable tokens)
+ private static IEnumerable ToGlyphInfos(IEnumerable tokens)
{
- var tokenQ = new Queue(tokens);
- var glyphs = new List();
-
- const double size = 20;
- var x = 0d;
- var y = 0d;
-
- void AddGlyph(CmFont font, int c)
- {
- glyphs.Add(new GlyphInfo(font, c, size, new Point(x * size, y * size)));
- var gtf = font.GlyphTypeface();
- var idx = gtf.CharacterToGlyphMap[c];
- x += gtf.AdvanceWidths[idx];
- }
-
- while (tokenQ.Count > 0)
- {
- var token = tokenQ.Dequeue();
- var val = token.Value;
- switch (token.TokenName)
- {
- case "letter":
- AddGlyph(CmFont.SerifItalic, val[0]);
- break;
- case "number":
- AddGlyph(CmFont.Serif, val[0]);
- break;
- case "command":
- if (greekLetters.ContainsKey(val))
- AddGlyph(CmFont.SerifItalic, greekLetters[val]);
- else if (spaces.ContainsKey(val))
- x += spaces[val];
- else
- foreach (var c in token.CapturedString)
- AddGlyph(CmFont.SerifItalic, c);
- break;
- case "escape":
- if (val == "\\")
- {
- x = 0;
- y -= 1;
- }
- break;
- }
- }
-
- return glyphs;
+ var lss = LatexSegment.ToLatexSegment(tokens);
+ return lss.Glyphs;
}
}
}
\ No newline at end of file
diff --git a/WpfTex/LatexEditor/Parser/LatexReturn.cs b/WpfTex/LatexEditor/Parser/LatexReturn.cs
new file mode 100644
index 0000000..bfa39ce
--- /dev/null
+++ b/WpfTex/LatexEditor/Parser/LatexReturn.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.Linq;
+using LatexEditor.Fonts;
+
+namespace LatexEditor.Parser
+{
+ 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/LatexRun.cs b/WpfTex/LatexEditor/Parser/LatexRun.cs
new file mode 100644
index 0000000..06b56ee
--- /dev/null
+++ b/WpfTex/LatexEditor/Parser/LatexRun.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using System.Linq;
+using LatexEditor.Fonts;
+
+namespace LatexEditor.Parser
+{
+ 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/LatexSegment.cs b/WpfTex/LatexEditor/Parser/LatexSegment.cs
new file mode 100644
index 0000000..6a8fd3e
--- /dev/null
+++ b/WpfTex/LatexEditor/Parser/LatexSegment.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using JetBrains.Annotations;
+using LatexEditor.Fonts;
+
+namespace LatexEditor.Parser
+{
+ public abstract class LatexSegment
+ {
+ [NotNull]
+ 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 new LatexNull();
+ if (segments.Count == 1)
+ return segments[0];
+ return new LatexRun(segments);
+ }
+
+ public abstract double RelAdvWidth { get; }
+ public abstract double RelAdvHeight { get; }
+
+ private static bool PopLatexSegment(Queue tokens, out LatexSegment val)
+ {
+ val = null;
+
+ if (tokens.Count == 0)
+ return false;
+
+ // todo: tabulate this
+
+ var head = tokens.Dequeue();
+ if (head.TokenName == "letter")
+ val = new LatexText(CmFont.SerifItalic, head.Value);
+ if (head.TokenName == "command")
+ {
+ if (LatexParser.GreekLetters.ContainsKey(head.Value))
+ val = new LatexText(CmFont.SerifItalic, LatexParser.GreekLetters[head.Value]);
+ if (LatexParser.Spaces.ContainsKey(head.Value))
+ val = new LatexSpace(LatexParser.Spaces[head.Value]);
+ if (head.Value == "^")
+ if (PopLatexSegment(tokens, out var content))
+ {
+ val = new LatexSuper(content);
+ }
+ 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")
+ {
+ 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);
+ }
+
+ return val != null || PopLatexSegment(tokens, out val);
+ }
+ }
+
+ 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/LatexSpace.cs b/WpfTex/LatexEditor/Parser/LatexSpace.cs
new file mode 100644
index 0000000..03275bf
--- /dev/null
+++ b/WpfTex/LatexEditor/Parser/LatexSpace.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Linq;
+using LatexEditor.Fonts;
+
+namespace LatexEditor.Parser
+{
+ 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/LatexText.cs b/WpfTex/LatexEditor/Parser/LatexText.cs
new file mode 100644
index 0000000..cf4f883
--- /dev/null
+++ b/WpfTex/LatexEditor/Parser/LatexText.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Windows;
+using LatexEditor.Fonts;
+
+namespace LatexEditor.Parser
+{
+ 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