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