corrected grouping behavior

This commit is contained in:
2017-05-25 16:16:28 -04:00
parent 7e5566c1cc
commit 7b17a53ceb
13 changed files with 247 additions and 273 deletions

View File

@@ -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}]";
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -67,7 +67,7 @@
</Compile>
<Compile Include="Fonts\CmManager.cs" />
<Compile Include="Fonts\CmFont.cs" />
<Compile Include="Fonts\GlyphInfo.cs" />
<Compile Include="Fonts\GlyphDescriptor.cs" />
<Compile Include="LatexDocument.cs" />
<Compile Include="LatexViewer.cs" />
<Compile Include="MainWindow.xaml.cs">
@@ -76,14 +76,8 @@
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Parser\Segments\LatexNull.cs" />
<Compile Include="Parser\Parser.cs" />
<Compile Include="Parser\Segments\LatexReturn.cs" />
<Compile Include="Parser\Segments\LatexRun.cs" />
<Compile Include="Parser\LatexParser.cs" />
<Compile Include="Parser\Segments\LatexSegment.cs" />
<Compile Include="Parser\Segments\LatexSpace.cs" />
<Compile Include="Parser\Segments\LatexSuper.cs" />
<Compile Include="Parser\Segments\LatexText.cs" />
<Compile Include="Parser\Lexer.cs" />
<Compile Include="Parser\Token.cs" />
<Compile Include="Parser\TokenDescriptor.cs" />

View File

@@ -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);
}

View File

@@ -8,20 +8,20 @@
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:LatexViewer>
<!-- e^{\pi i}=-1\{\textrm{in the complex plane}-->
{2}{123}
<!-- 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-->
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
</local:LatexViewer>
</Grid>

View File

@@ -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<GlyphInfo> ToGlyphInfos(string latex) => ToGlyphInfos(LatexLexer.Tokenize(latex));
private static IEnumerable<GlyphInfo> ToGlyphInfos(IEnumerable<Token> tokens)
{
var lss = LatexSegment.ToLatexSegment(tokens);
return lss.Glyphs;
}
public static IEnumerable<Token> Tokenize(string latex) => LatexLexer.Tokenize(latex);
}
}

View File

@@ -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<GlyphInfo> Glyphs => Enumerable.Empty<GlyphInfo>();
public override double RelAdvWidth => 0;
public override double RelAdvHeight => 0;
}
}

View File

@@ -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<GlyphInfo> Glyphs => Enumerable.Empty<GlyphInfo>();
public override double RelAdvWidth => 0;
public override double RelAdvHeight { get; }
public LatexReturn(double relAdvHeight)
{
RelAdvHeight = relAdvHeight;
}
}
}

View File

@@ -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<GlyphInfo> Glyphs => Segments.SelectMany(ls => ls.Glyphs);
public override double RelAdvWidth { get; }
public override double RelAdvHeight { get; }
public IEnumerable<LatexSegment> Segments { get; set; }
public LatexRun(IEnumerable<LatexSegment> 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;
}
}
}

View File

@@ -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<GlyphInfo> Glyphs { get; }
public List<LatexSegment> Contents { get; } = new List<LatexSegment>();
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<LatexGlyph> Glyphs { get; }
public static LatexSegment ToLatexSegment(IEnumerable<Token> 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<Token> 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))
{
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")
{
// 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<LatexSegment>();
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))
segments.Add(content);
tokens.Dequeue(); // toss the close
val = new LatexRun(segments);
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 == "^")
{
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 "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<GlyphDescriptor> 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<LatexGlyph> 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<GlyphDescriptor> 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<LatexGlyph> Glyphs => Enumerable.Empty<LatexGlyph>();
}
// 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<LatexGlyph> Glyphs => Enumerable.Empty<LatexGlyph>();
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<LatexGlyph> Glyphs
{
get
{
foreach (var ls in Contents)
foreach (var lg in ls.Glyphs)
yield return lg;
}
}
public LatexNull()
{
}
public LatexNull(IEnumerable<LatexSegment> contents)
{
Contents.AddRange(contents);
}
}
}

View File

@@ -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<GlyphInfo> Glyphs => Enumerable.Empty<GlyphInfo>();
public override double RelAdvWidth { get; }
public override double RelAdvHeight { get; }
public LatexSpace(double relAdvWidth)
{
RelAdvWidth = relAdvWidth;
RelAdvHeight = 0;
}
}
}

View File

@@ -1,26 +0,0 @@
using System.Collections.Generic;
using LatexEditor.Fonts;
namespace LatexEditor.Parser.Segments
{
public class LatexSuper : LatexSegment
{
public override IEnumerable<GlyphInfo> 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;
}
}
}
}

View File

@@ -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<GlyphInfo> _glyphs = new List<GlyphInfo>();
public override IEnumerable<GlyphInfo> 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;
}
}
}