Beispiel #1
0
        public static (Displays relative, Displays 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 Displays(Array.Empty <IDisplay <Fonts, Glyph> >()),
                     new Displays(Array.Empty <IDisplay <Fonts, Glyph> >()));
            }
            float accumulatedHeight = 0;
            bool  afterDisplayMaths = false; //indicator of the need to apply belowdisplay(short)skip when line break
            void BreakLine(TextDisplayLineBuilder 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,
                TextDisplayLineBuilder line,
                List <IDisplay <Fonts, Glyph> > displayList,
                List <IDisplay <Fonts, Glyph> > displayMathList,
                FontStyle style,
                Structures.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 <Fonts, Glyph> .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();
                    Warnings.AssertAll(typefaces,
                                       tf => Typography.OpenFont.Extensions.TypefaceExtensions.RecommendToUseTypoMetricsForLineSpacing(tf),
                                       "This font file is too old. Only font files that support standard typographical metrics are supported.");
                    display = new TextRunDisplay <Fonts, Glyph>(new Display.Text.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 <Fonts, Glyph> .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 Display.Text.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 TextDisplayLineBuilder();
                    AddDisplaysWithLineBreaks(a.Content, fonts, accentDisplayLine, accenteeDisplayList, invalidDisplayMaths, style, color);
                    float _ = default;
                    accentDisplayLine.Clear(0, 0, accenteeDisplayList, ref _, false, false, additionalLineSpacing);
                    Warnings.Assert(invalidDisplayMaths.Count == 0, "Display maths inside an accentee is unsupported -- ignoring display maths");
                    var   accentee            = new Displays(accenteeDisplayList);
                    var   accenteeCodepoint   = a.Content.SingleChar(style);
                    Glyph accenteeSingleGlyph = accenteeCodepoint.HasValue ? GlyphFinder.Instance.Lookup(fonts, accenteeCodepoint.Value) : GlyphFinder.Instance.EmptyGlyph;

                    var accentDisplay = new AccentDisplay <Fonts, Glyph>(
                        Typesetter <Fonts, Glyph> .CreateAccentGlyphDisplay(accentee, accenteeSingleGlyph, accentGlyph, TypesettingContext.Instance, fonts, 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 TextDisplayLineBuilder();
            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 Displays(relativePositionList),
                   new Displays(absolutePositionList));
        }