Ejemplo n.º 1
0
        public static (Display relative, Display absolute) Layout
            (TextAtom input, Fonts inputFont, float canvasWidth, float additionalLineSpacing)
        {
#warning Multiply these constants by resolution
            const float abovedisplayskip = 12, abovedisplayshortskip = 0,
                        belowdisplayskip = 12, belowdisplayshortskip = 7;
            if (input == null)
            {
                return
                    (new Display(Array.Empty <IDisplay <Fonts, Glyph> >()),
                     new Display(Array.Empty <IDisplay <Fonts, Glyph> >()));
            }
            float accumulatedHeight = 0;
            //indicator of the need to apply belowdisplay(short)skip when line break
            bool afterDisplayMaths = false;
            void BreakLine(TextLayoutLineBuilder line, List <IDisplay <Fonts, Glyph> > displayList,
                           List <IDisplay <Fonts, Glyph> > displayMathList, bool appendLineGap = true)
            {
                if (afterDisplayMaths)
                {
                    accumulatedHeight +=
                        line.Width > displayMathList.Last().Position.X
                      ? belowdisplayskip
                      : belowdisplayshortskip;
                    afterDisplayMaths = false;
                }
                line.Clear(0, -accumulatedHeight, displayList,
                           ref accumulatedHeight, true, appendLineGap, additionalLineSpacing);
            }

            //variables captured by this method are currently unchangable by TextAtoms
            void AddDisplaysWithLineBreaks(
                TextAtom atom,
                Fonts fonts,
                TextLayoutLineBuilder line,
                List <IDisplay <Fonts, Glyph> > displayList,
                List <IDisplay <Fonts, Glyph> > displayMathList,
                FontStyle style,
                Color?color
                )
            {
                IDisplay <Fonts, Glyph> display;

                switch (atom)
                {
                case TextAtom.List list:
                    foreach (var a in list.Content)
                    {
                        AddDisplaysWithLineBreaks
                            (a, fonts, line, displayList, displayMathList, style, color);
                    }
                    break;

                case TextAtom.Style st:
                    AddDisplaysWithLineBreaks
                        (st.Content, fonts, line, displayList, displayMathList, st.FontStyle, color);
                    break;

                case TextAtom.Size sz:
                    AddDisplaysWithLineBreaks
                        (sz.Content, new Fonts(fonts, sz.PointSize), line, displayList,
                        displayMathList, style, color);
                    break;

                case TextAtom.Color c:
                    AddDisplaysWithLineBreaks
                        (c.Content, fonts, line, displayList, displayMathList, style, c.Colour);
                    break;

                case TextAtom.Space sp:
                    //Allow space at start of line since user explicitly specified its length
                    //Also \par generates this kind of spaces
                    line.AddSpace(sp.Content.ActualLength(MathTable.Instance, fonts));
                    break;

                case TextAtom.Newline n:
                    BreakLine(line, displayList, displayMathList);
                    break;

                case TextAtom.Math m when m.DisplayStyle:
                    var lastLineWidth = line.Width;
                    BreakLine(line, displayList, displayMathList, false);
                    display = Typesetter.CreateLine(m.Content, fonts, TypesettingContext.Instance, LineStyle.Display);
                    var displayX = IPainterExtensions.GetDisplayPosition
                                       (display.Width, display.Ascent, display.Descent,
                                       fonts.PointSize, false, canvasWidth, float.NaN,
                                       TextAlignment.Top, default, default, default).X;
                    //\because When displayList.LastOrDefault() is null,
                    //the false condition is selected
                    //\therefore Append abovedisplayshortskip which defaults
                    //to 0 when nothing is above the display-style maths
                    accumulatedHeight +=
                        lastLineWidth > displayX ? abovedisplayskip : abovedisplayshortskip;
                    accumulatedHeight += display.Ascent;
                    display.Position   = new System.Drawing.PointF(displayX, -accumulatedHeight);
                    accumulatedHeight += display.Descent;
                    afterDisplayMaths  = true;
                    if (color != null)
                    {
                        display.SetTextColorRecursive(color);
                    }
                    displayMathList.Add(display);
                    break;

                    void FinalizeInlineDisplay(float ascender, float rawDescender,
                                               float lineGap, bool forbidAtLineStart = false)
                    {
                        if (color != null)
                        {
                            display.SetTextColorRecursive(color);
                        }
                        if (line.Width + display.Width > canvasWidth && !forbidAtLineStart)
                        {
                            BreakLine(line, displayList, displayMathList);
                        }
                        //rawDescender is taken directly from font file and is negative,
                        //while IDisplay.Descender is positive
                        line.Add(display, ascender, -rawDescender, lineGap);
                    }

                case TextAtom.Text t:
                    var content = UnicodeFontChanger.Instance.ChangeFont(t.Content, style);
                    var glyphs  = GlyphFinder.Instance.FindGlyphs(fonts, content);
                    //Calling Select(g => g.Typeface).Distinct() speeds up query up to 10 times,
                    //Calling Max(Func<,>) instead of Select(Func<,>).Max() speeds up query 2 times
                    var typefaces = glyphs.Select(g => g.Typeface).Distinct().ToList();
                    display = new TextRunDisplay <Fonts, Glyph>(
                        new AttributedGlyphRun <Fonts, Glyph>(content, glyphs, fonts),
                        t.Range, TypesettingContext.Instance
                        );
                    FinalizeInlineDisplay(
                        typefaces.Max(tf => tf.Ascender * tf.CalculateScaleToPixelFromPointSize(fonts.PointSize)),
                        typefaces.Min(tf => tf.Descender * tf.CalculateScaleToPixelFromPointSize(fonts.PointSize)),
                        typefaces.Max(tf => tf.LineGap * tf.CalculateScaleToPixelFromPointSize(fonts.PointSize))
                        );
                    break;

                case TextAtom.Math m:
                    if (m.DisplayStyle)
                    {
                        throw new InvalidCodePathException
                                  ("Display style maths should have been handled above this switch.");
                    }
                    display = Typesetter.CreateLine(m.Content, fonts, TypesettingContext.Instance, LineStyle.Text);
                    var scale = fonts.MathTypeface.CalculateScaleToPixelFromPointSize(fonts.PointSize);
                    FinalizeInlineDisplay(fonts.MathTypeface.Ascender * scale,
                                          fonts.MathTypeface.Descender * scale, fonts.MathTypeface.LineGap * scale);
                    break;

                case TextAtom.ControlSpace cs:
                    var spaceGlyph = GlyphFinder.Instance.Lookup(fonts, ' ');
                    display = new TextRunDisplay <Fonts, Glyph>(
                        new AttributedGlyphRun <Fonts, Glyph>(" ", new[] { spaceGlyph }, fonts),
                        cs.Range, TypesettingContext.Instance
                        );
                    scale = spaceGlyph.Typeface.CalculateScaleToPixelFromPointSize(fonts.PointSize);
                    FinalizeInlineDisplay(spaceGlyph.Typeface.Ascender * scale,
                                          spaceGlyph.Typeface.Descender * scale,
                                          spaceGlyph.Typeface.LineGap * scale,
                                          forbidAtLineStart: true); //No spaces at start of line
                    break;

                case TextAtom.Accent a:
                    var accentGlyph = GlyphFinder.Instance.FindGlyphForCharacterAtIndex(
                        fonts, a.AccentChar.Length - 1, a.AccentChar
                        );
                    scale = accentGlyph.Typeface.CalculateScaleToPixelFromPointSize(fonts.PointSize);
                    var accenteeDisplayList = new List <IDisplay <Fonts, Glyph> >();
                    var invalidDisplayMaths = new List <IDisplay <Fonts, Glyph> >();
                    var accentDisplayLine   = new TextLayoutLineBuilder();
                    AddDisplaysWithLineBreaks(a.Content, fonts, accentDisplayLine,
                                              accenteeDisplayList, invalidDisplayMaths, style, color);
                    float _ = default;
                    accentDisplayLine.Clear
                        (0, 0, accenteeDisplayList, ref _, false, false, additionalLineSpacing);
                    System.Diagnostics.Debug.Assert(invalidDisplayMaths.Count == 0,
                                                    "Display maths inside an accentee is unsupported -- ignoring display maths");
                    var accentee            = new Display(accenteeDisplayList);
                    var accenteeCodepoint   = a.Content.SingleChar(style);
                    var accenteeSingleGlyph =
                        accenteeCodepoint.HasValue
                          ? GlyphFinder.Instance.Lookup(fonts, accenteeCodepoint.GetValueOrDefault())
                          : GlyphFinder.Instance.EmptyGlyph;

                    var accentDisplay = new AccentDisplay <Fonts, Glyph>(
                        Typesetter.CreateAccentGlyphDisplay(
                            accentee, accenteeSingleGlyph, accentGlyph,
                            TypesettingContext.Instance, fonts, a.Range
                            ), accentee);
                    display = accentDisplay;
                    //accentDisplay.Ascent does not take account of accent glyph's extra height
                    //-> accent will be out of bounds if it is on the first line
                    FinalizeInlineDisplay(
                        Math.Max(accentGlyph.Typeface.Ascender * scale,
                                 accentDisplay.Accent.Position.Y + accentDisplay.Ascent),
                        accentGlyph.Typeface.Descender * scale,
                        accentGlyph.Typeface.LineGap * scale);
                    break;

                case TextAtom.Comment _:
                    break;

                case null:
                    throw new InvalidOperationException
                              ("TextAtoms should never be null. You must have sneaked one in.");

                case var a:
                    throw new InvalidCodePathException
                              ($"There should not be an unknown type of TextAtom. However, one with type {a.GetType()} was encountered.");
                }
            }

            var relativePositionList = new List <IDisplay <Fonts, Glyph> >();
            var absolutePositionList = new List <IDisplay <Fonts, Glyph> >();
            var globalLine           = new TextLayoutLineBuilder();
            AddDisplaysWithLineBreaks(
                input,
                inputFont,
                globalLine,
                relativePositionList,
                absolutePositionList,
                FontStyle.Roman /*FontStyle.Default is FontStyle.Italic, FontStyle.Roman is no change to characters*/,
                null
                );
            BreakLine(globalLine, relativePositionList, absolutePositionList); //remember to finalize the last line
            return(new Display(relativePositionList), new Display(absolutePositionList));
        }
Ejemplo n.º 2
0
        public static (Displays relative, Displays absolute) Layout(TextAtom input, Fonts inputFont, float canvasWidth)
        {
            if (input == null)
            {
                return
                    (new Displays(Array.Empty <IDisplay <Fonts, Glyph> >()),
                     new Displays(Array.Empty <IDisplay <Fonts, Glyph> >()));
            }
            float accumulatedHeight     = 0;
            TextDisplayLineBuilder line = new TextDisplayLineBuilder();

            void BreakLine(List <IDisplay <Fonts, Glyph> > displayList)
            {
                accumulatedHeight += line.Ascent;
                line.Clear(0, -accumulatedHeight, displayList.Add, () => accumulatedHeight += line.Descent);
            }

            void AddDisplaysWithLineBreaks(TextAtom atom, Fonts fonts,
                                           List <IDisplay <Fonts, Glyph> > displayList,
                                           List <IDisplay <Fonts, Glyph> > displayMathList,
                                           FontStyle style        = FontStyle.Roman, /*FontStyle.Default is FontStyle.Italic, FontStyle.Roman is no change to characters*/
                                           Structures.Color?color = null)
            {
                IDisplay <Fonts, Glyph> display;

                switch (atom)
                {
                case TextAtom.List list:
                    foreach (var a in list.Content)
                    {
                        AddDisplaysWithLineBreaks(a, fonts, displayList, displayMathList, style, color);
                    }
                    break;

                case TextAtom.Style st:
                    AddDisplaysWithLineBreaks(st.Content, fonts, displayList, displayMathList, st.FontStyle, color);
                    break;

                case TextAtom.Size sz:
                    AddDisplaysWithLineBreaks(sz.Content, new Fonts(fonts, sz.PointSize), displayList, displayMathList, style, color);
                    break;

                case TextAtom.Color c:
                    AddDisplaysWithLineBreaks(c.Content, fonts, displayList, displayMathList, style, c.Colour);
                    break;

                case TextAtom.Space sp:
                    //Allow space at start of line since user explicitly specified its length
                    //Also \par generates this kind of spaces
                    line.AddSpace(sp.Content.ActualLength(MathTable.Instance, fonts));
                    break;

                case TextAtom.Newline n:
                    BreakLine(displayList);
                    break;

                case TextAtom.Math m when m.DisplayStyle:
                    BreakLine(displayList);
#warning Replace 12 with a more appropriate spacing
                    accumulatedHeight += 12;
                    display            = Typesetter <Fonts, Glyph> .CreateLine(m.Content, fonts, TypesettingContext.Instance, LineStyle.Display);

                    if (color != null)
                    {
                        display.SetTextColorRecursive(color);
                    }
                    accumulatedHeight += display.Ascent;
                    display.Position   = new System.Drawing.PointF(
                        IPainterExtensions.GetDisplayPosition(display.Width, display.Ascent, display.Descent, fonts.PointSize, false, canvasWidth, float.NaN, TextAlignment.Top, default, default, default).X,
                        -accumulatedHeight);
                    accumulatedHeight += display.Descent;
                    accumulatedHeight += 12;
                    if (color != null)
                    {
                        display.SetTextColorRecursive(color);
                    }
                    displayMathList.Add(display);
                    break;

                    void FinalizeInlineDisplay(float ascentMin, bool forbidAtLineStart = false)
                    {
                        if (color != null)
                        {
                            display.SetTextColorRecursive(color);
                        }
                        if (line.Width + display.Width > canvasWidth && !forbidAtLineStart)
                        {
                            BreakLine(displayList);
                        }
                        line.Add(display, ascentMin);
                    }

                case TextAtom.Text t:
                    var content = UnicodeFontChanger.Instance.ChangeFont(t.Content, style);
                    var glyphs  = GlyphFinder.Instance.FindGlyphs(fonts, content);
                    //Calling Select(g => g.Typeface).Distinct() speeds up query up to 10 times,
                    //Calling Max(Func<,>) instead of Select(Func<,>).Max() speeds up query 2 times
                    float maxLineSpacing = glyphs.Select(g => g.Typeface).Distinct().Max(tf =>
                                                                                         tf.CalculateRecommendLineSpacing() *
                                                                                         tf.CalculateScaleToPixelFromPointSize(fonts.PointSize)
                                                                                         );
                    display = new TextRunDisplay <Fonts, Glyph>(Display.Text.AttributedGlyphRuns.Create(content, glyphs, fonts, false), t.Range, TypesettingContext.Instance);
                    FinalizeInlineDisplay(maxLineSpacing);
                    break;

                case TextAtom.Math m:
                    if (m.DisplayStyle)
                    {
                        throw new InvalidCodePathException("Display style maths should have been handled above this switch.");
                    }
                    display = Typesetter <Fonts, Glyph> .CreateLine(m.Content, fonts, TypesettingContext.Instance, Enumerations.LineStyle.Text);

                    FinalizeInlineDisplay(fonts.MathTypeface.CalculateRecommendLineSpacing() *
                                          fonts.MathTypeface.CalculateScaleToPixelFromPointSize(fonts.PointSize));
                    break;

                case TextAtom.ControlSpace cs:
                    var spaceGlyph = GlyphFinder.Instance.Lookup(fonts, ' ');
                    display = new TextRunDisplay <Fonts, Glyph>(Display.Text.AttributedGlyphRuns.Create(" ", new[] { spaceGlyph }, fonts, false), cs.Range, TypesettingContext.Instance);
                    FinalizeInlineDisplay(spaceGlyph.Typeface.CalculateRecommendLineSpacing() *
                                          spaceGlyph.Typeface.CalculateScaleToPixelFromPointSize(fonts.PointSize),
                                          forbidAtLineStart: true); //No spaces at start of line
                    break;

                case null:
                    throw new InvalidOperationException("TextAtoms should never be null. You must have sneaked one in.");

                case var a:
                    throw new InvalidCodePathException($"There should not be an unknown type of TextAtom. However, one with type {a.GetType()} was encountered.");
                }
            }

            var relativePositionList = new List <IDisplay <Fonts, Glyph> >();
            var absolutePositionList = new List <IDisplay <Fonts, Glyph> >();
            AddDisplaysWithLineBreaks(input, inputFont, relativePositionList, absolutePositionList);
            BreakLine(relativePositionList); //remember to finalize the last line
            return(new Displays(relativePositionList),
                   new Displays(absolutePositionList));
        }