Teilen über


Pfadinformationen und -enumeration

Abrufen von Informationen zu Pfaden und Aufzählen des Inhalts

Die SKPath Klasse definiert mehrere Eigenschaften und Methoden, mit denen Sie Informationen zum Pfad abrufen können. Die Bounds Eigenschaften TightBounds (und verwandte Methoden) rufen die Metrikabmessungen eines Pfads ab. Mit der Contains Methode können Sie ermitteln, ob sich ein bestimmter Punkt innerhalb eines Pfads befindet.

Es ist manchmal nützlich, die Gesamtlänge aller Linien und Kurven zu bestimmen, die einen Pfad bilden. Die Berechnung dieser Länge ist keine algorithmisch einfache Aufgabe, daher wird ihm eine gesamte benannte PathMeasure Klasse gewidmet.

Es ist auch manchmal nützlich, alle Zeichnungsvorgänge und Punkte abzurufen, die einen Pfad bilden. Zunächst mag diese Einrichtung unnötig erscheinen: Wenn Ihr Programm den Pfad erstellt hat, kennt das Programm bereits den Inhalt. Sie haben jedoch gesehen, dass Pfade auch durch Pfadeffekte erstellt werden können, und indem Sie Textzeichenfolgen in Pfade konvertieren. Sie können auch alle Zeichnungsvorgänge und Punkte abrufen, aus denen diese Pfade bestehen. Eine Möglichkeit besteht darin, eine algorithmische Transformation auf alle Punkte anzuwenden, z. B. um Text um eine Hemisphäre zu umbrechen:

Text, der in eine Hemisphäre eingeschlossen ist

Abrufen der Pfadlänge

Im Artikel "Pfade" und "Text " haben Sie gesehen, wie Sie mit der DrawTextOnPath Methode eine Textzeichenfolge zeichnen, deren Basisplan dem Verlauf eines Pfads folgt. Aber was ist, wenn Sie den Text so anpassen möchten, dass er genau in den Pfad passt? Das Zeichnen von Text um einen Kreis ist einfach, da der Umfang eines Kreises einfach berechnet werden kann. Aber der Umfang einer Ellipse oder die Länge einer Bézierkurve ist nicht so einfach.

Der SKPathMeasure Kurs kann ihnen helfen. Der Konstruktor akzeptiert ein SKPath Argument, und die Eigenschaft zeigt die Length Länge an.

Diese Klasse wird im Beispiel "Pfadlänge " veranschaulicht, das auf der Seite " Bézierkurve " basiert. Die Datei "PathLengthPage.xaml" wird von InteractivePage der Schnittstelle "PathLengthPage.xaml" abgeleitet und enthält eine Touchschnittstelle:

<local:InteractivePage xmlns="http://xamarin.com/schemas/2014/forms"
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       xmlns:local="clr-namespace:SkiaSharpFormsDemos"
                       xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
                       xmlns:tt="clr-namespace:TouchTracking"
                       x:Class="SkiaSharpFormsDemos.Curves.PathLengthPage"
                       Title="Path Length">
    <Grid BackgroundColor="White">
        <skia:SKCanvasView x:Name="canvasView"
                           PaintSurface="OnCanvasViewPaintSurface" />
        <Grid.Effects>
            <tt:TouchEffect Capture="True"
                            TouchAction="OnTouchEffectAction" />
        </Grid.Effects>
    </Grid>
</local:InteractivePage>

Mit der PathLengthPage.xaml.cs CodeBehind-Datei können Sie vier Berührungspunkte verschieben, um die Endpunkte und Kontrollpunkte einer kubischen Bézierkurve zu definieren. Drei Felder definieren eine Textzeichenfolge, ein SKPaint Objekt und eine berechnete Breite des Texts:

public partial class PathLengthPage : InteractivePage
{
    const string text = "Compute length of path";

    static SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Black,
        TextSize = 10,
    };

    static readonly float baseTextWidth = textPaint.MeasureText(text);
    ...
}

Das baseTextWidth Feld ist die Breite des Texts basierend auf einer TextSize Einstellung von 10.

Der PaintSurface Handler zeichnet die Bézierkurve und passt dann den Text an die gesamte Länge an:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    // Draw path with cubic Bezier curve
    using (SKPath path = new SKPath())
    {
        path.MoveTo(touchPoints[0].Center);
        path.CubicTo(touchPoints[1].Center,
                     touchPoints[2].Center,
                     touchPoints[3].Center);

        canvas.DrawPath(path, strokePaint);

        // Get path length
        SKPathMeasure pathMeasure = new SKPathMeasure(path, false, 1);

        // Find new text size
        textPaint.TextSize = pathMeasure.Length / baseTextWidth * 10;

        // Draw text on path
        canvas.DrawTextOnPath(text, path, 0, 0, textPaint);
    }
    ...
}

Die Length Eigenschaft des neu erstellten SKPathMeasure Objekts ruft die Länge des Pfads ab. Die Pfadlänge wird durch den baseTextWidth Wert (die Breite des Texts basierend auf einer Textgröße von 10) dividiert und dann mit der Basistextgröße von 10 multipliziert. Das Ergebnis ist eine neue Textgröße zum Anzeigen des Texts entlang dieses Pfads:

Dreifacher Screenshot der Seite

Wenn die Bézierkurve länger oder kürzer wird, können Sie sehen, dass sich die Textgröße ändert.

Durchlaufen des Pfads

SKPathMeasure kann mehr als nur die Länge des Pfads messen. Für einen beliebigen Wert zwischen Null und Pfadlänge kann ein SKPathMeasure Objekt die Position auf dem Pfad und den Tangens zur Pfadkurve an diesem Punkt abrufen. Der Tangens ist als Vektor in Form eines SKPoint Objekts oder als Drehung in einem SKMatrix Objekt verfügbar. Hier sind die Methoden, die SKPathMeasure diese Informationen auf vielfältige und flexible Weise abrufen:

Boolean GetPosition (Single distance, out SKPoint position)

Boolean GetTangent (Single distance, out SKPoint tangent)

Boolean GetPositionAndTangent (Single distance, out SKPoint position, out SKPoint tangent)

Boolean GetMatrix (Single distance, out SKMatrix matrix, SKPathMeasureMatrixFlags flag)

Die Member der SKPathMeasureMatrixFlags Aufzählung sind:

  • GetPosition
  • GetTangent
  • GetPositionAndTangent

Die Seite "Unicycle Half-Pipe " animiert eine Strichfigur auf einem Unirad, das auf einer kubischen Bézierkurve hin- und herfahren scheint:

Dreifacher Screenshot der Seite

Das SKPaint Objekt, das zum Streichen der Halbpipe und des Unicycles verwendet wird, wird als Feld in der UnicycleHalfPipePage Klasse definiert. Außerdem ist das SKPath Objekt für das Unicycle definiert:

public class UnicycleHalfPipePage : ContentPage
{
    ...
    SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        StrokeWidth = 3,
        Color = SKColors.Black
    };

    SKPath unicyclePath = SKPath.ParseSvgPathData(
        "M 0 0" +
        "A 25 25 0 0 0 0 -50" +
        "A 25 25 0 0 0 0 0 Z" +
        "M 0 -25 L 0 -100" +
        "A 15 15 0 0 0 0 -130" +
        "A 15 15 0 0 0 0 -100 Z" +
        "M -25 -85 L 25 -85");
    ...
}

Die Klasse enthält die Standardüberschreibungen der OnAppearing Animationen und OnDisappearing Methoden für Animationen. Der PaintSurface Handler erstellt den Pfad für die Halbpipeline und zeichnet ihn dann. Anschließend wird ein SKPathMeasure Objekt basierend auf diesem Pfad erstellt:

public class UnicycleHalfPipePage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPath pipePath = new SKPath())
        {
            pipePath.MoveTo(50, 50);
            pipePath.CubicTo(0, 1.25f * info.Height,
                             info.Width - 0, 1.25f * info.Height,
                             info.Width - 50, 50);

            canvas.DrawPath(pipePath, strokePaint);

            using (SKPathMeasure pathMeasure = new SKPathMeasure(pipePath))
            {
                float length = pathMeasure.Length;

                // Animate t from 0 to 1 every three seconds
                TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
                float t = (float)(timeSpan.TotalSeconds % 5 / 5);

                // t from 0 to 1 to 0 but slower at beginning and end
                t = (float)((1 - Math.Cos(t * 2 * Math.PI)) / 2);

                SKMatrix matrix;
                pathMeasure.GetMatrix(t * length, out matrix,
                                      SKPathMeasureMatrixFlags.GetPositionAndTangent);

                canvas.SetMatrix(matrix);
                canvas.DrawPath(unicyclePath, strokePaint);
            }
        }
    }
}

Der PaintSurface Handler berechnet einen Wert von t 0 bis 1 alle fünf Sekunden. Anschließend wird die Math.Cos Funktion verwendet, um die Funktion in einen Wert von t 0 bis 1 und zurück zu 0 zu konvertieren, wobei 0 dem Unicycle am Anfang oben links entspricht, während 1 dem Unicycle oben rechts entspricht. Die Kosinusfunktion bewirkt, dass die Geschwindigkeit am oberen Rand des Rohrs langsam und am schnellsten am unteren Rand ist.

Beachten Sie, dass dieser Wert t mit der Pfadlänge für das erste Argument GetMatrixmultipliziert werden muss. Die Matrix wird dann auf das SKCanvas Objekt angewendet, um den Unizyklischen Pfad zu zeichnen.

Aufzählen des Pfads

Zwei eingebettete Klassen SKPath , mit denen Sie den Inhalt des Pfads aufzählen können. Diese Klassen sind SKPath.Iterator und SKPath.RawIterator. Die beiden Klassen sind sehr ähnlich, können jedoch SKPath.Iterator Elemente im Pfad mit einer Länge null oder nahe einer Länge null beseitigen. Dies RawIterator wird im folgenden Beispiel verwendet.

Sie können ein Objekt vom Typ SKPath.RawIterator abrufen, indem Sie die CreateRawIterator Methode von SKPath. Das Aufzählen durch den Pfad wird durch wiederholtes Aufrufen der Next Methode erreicht. Übergeben Sie an dieses Array mit vier SKPoint Werten:

SKPoint[] points = new SKPoint[4];
...
SKPathVerb pathVerb = rawIterator.Next(points);

Die Next Methode gibt ein Element des Enumerationstyps SKPathVerb zurück. Diese Werte geben den bestimmten Zeichnungsbefehl im Pfad an. Die Anzahl der in das Array eingefügten gültigen Punkte hängt von diesem Verb ab:

  • Move mit einem einzelnen Punkt
  • Line mit zwei Punkten
  • Cubic mit vier Punkten
  • Quad mit drei Punkten
  • Conic mit drei Punkten (und rufen Sie auch die ConicWeight Methode für die Gewichtung auf)
  • Close mit einem Punkt
  • Done

Das Done Verb gibt an, dass die Pfadenumeration abgeschlossen ist.

Beachten Sie, dass keine Arc Verben vorhanden sind. Dies weist darauf hin, dass alle Bögen beim Hinzufügen zum Pfad in Bézierkurven konvertiert werden.

Einige der Informationen im SKPoint Array sind redundant. Wenn auf ein Move Verb beispielsweise ein Line Verb folgt, ist der erste der beiden zugehörigen Line Punkte identisch mit dem Move Punkt. In der Praxis ist diese Redundanz sehr hilfreich. Wenn Sie ein Cubic Verb erhalten, wird es von allen vier Punkten begleitet, die die kubische Bézierkurve definieren. Sie müssen die aktuelle Position, die durch das vorherige Verb festgelegt wurde, nicht beibehalten.

Das problematische Verb ist Closejedoch . Dieser Befehl zeichnet eine gerade Linie von der aktuellen Position bis zum Anfang der kontur, die zuvor durch den Move Befehl festgelegt wurde. Im Idealfall sollte das Close Verb diese beiden Punkte statt nur einen Punkt angeben. Schlimmer noch: Der Punkt, der das Close Verb begleitet, ist immer (0, 0). Wenn Sie einen Pfad durchlaufen, müssen Sie wahrscheinlich den Move Punkt und die aktuelle Position beibehalten.

Aufzählen, Abschärfung und Fehlforming

Es ist manchmal wünschenswert, eine algorithmische Transformation auf einen Pfad anzuwenden, um ihn in irgendeiner Weise falsch zu gestalten:

Text, der in eine Hemisphäre eingeschlossen ist

Die meisten dieser Buchstaben bestehen aus geraden Linien, aber diese geraden Linien wurden offenbar in Kurven verdreht. Wie ist das möglich?

Der Schlüssel besteht darin, dass die ursprünglichen geraden Linien in eine Reihe kleinerer gerader Linien unterteilt werden. Diese einzelnen kleineren geraden Linien können dann auf unterschiedliche Weise bearbeitet werden, um eine Kurve zu bilden.

Zur Unterstützung dieses Prozesses enthält das Beispiel eine statische PathExtensions Klasse mit einer Interpolate Methode, die eine gerade Linie in zahlreiche kurze Zeilen aufbricht, die nur eine Einheit lang sind. Darüber hinaus enthält die Klasse mehrere Methoden, mit denen die drei Arten von Bézierkurven in eine Reihe von winzigen geraden Linien konvertiert werden, die der Kurve nähern. (Die parametrischen Formeln wurden im Artikel vorgestelltDrei Arten von Bézierkurven.) Dieser Prozess wird als Flachung der Kurve bezeichnet:

static class PathExtensions
{
    ...
    static SKPoint[] Interpolate(SKPoint pt0, SKPoint pt1)
    {
        int count = (int)Math.Max(1, Length(pt0, pt1));
        SKPoint[] points = new SKPoint[count];

        for (int i = 0; i < count; i++)
        {
            float t = (i + 1f) / count;
            float x = (1 - t) * pt0.X + t * pt1.X;
            float y = (1 - t) * pt0.Y + t * pt1.Y;
            points[i] = new SKPoint(x, y);
        }

        return points;
    }

    static SKPoint[] FlattenCubic(SKPoint pt0, SKPoint pt1, SKPoint pt2, SKPoint pt3)
    {
        int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2) + Length(pt2, pt3));
        SKPoint[] points = new SKPoint[count];

        for (int i = 0; i < count; i++)
        {
            float t = (i + 1f) / count;
            float x = (1 - t) * (1 - t) * (1 - t) * pt0.X +
                        3 * t * (1 - t) * (1 - t) * pt1.X +
                        3 * t * t * (1 - t) * pt2.X +
                        t * t * t * pt3.X;
            float y = (1 - t) * (1 - t) * (1 - t) * pt0.Y +
                        3 * t * (1 - t) * (1 - t) * pt1.Y +
                        3 * t * t * (1 - t) * pt2.Y +
                        t * t * t * pt3.Y;
            points[i] = new SKPoint(x, y);
        }

        return points;
    }

    static SKPoint[] FlattenQuadratic(SKPoint pt0, SKPoint pt1, SKPoint pt2)
    {
        int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2));
        SKPoint[] points = new SKPoint[count];

        for (int i = 0; i < count; i++)
        {
            float t = (i + 1f) / count;
            float x = (1 - t) * (1 - t) * pt0.X + 2 * t * (1 - t) * pt1.X + t * t * pt2.X;
            float y = (1 - t) * (1 - t) * pt0.Y + 2 * t * (1 - t) * pt1.Y + t * t * pt2.Y;
            points[i] = new SKPoint(x, y);
        }

        return points;
    }

    static SKPoint[] FlattenConic(SKPoint pt0, SKPoint pt1, SKPoint pt2, float weight)
    {
        int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2));
        SKPoint[] points = new SKPoint[count];

        for (int i = 0; i < count; i++)
        {
            float t = (i + 1f) / count;
            float denominator = (1 - t) * (1 - t) + 2 * weight * t * (1 - t) + t * t;
            float x = (1 - t) * (1 - t) * pt0.X + 2 * weight * t * (1 - t) * pt1.X + t * t * pt2.X;
            float y = (1 - t) * (1 - t) * pt0.Y + 2 * weight * t * (1 - t) * pt1.Y + t * t * pt2.Y;
            x /= denominator;
            y /= denominator;
            points[i] = new SKPoint(x, y);
        }

        return points;
    }

    static double Length(SKPoint pt0, SKPoint pt1)
    {
        return Math.Sqrt(Math.Pow(pt1.X - pt0.X, 2) + Math.Pow(pt1.Y - pt0.Y, 2));
    }
}

Auf alle diese Methoden wird aus der Erweiterungsmethode CloneWithTransform verwiesen, die auch in dieser Klasse enthalten ist und unten dargestellt wird. Mit dieser Methode wird ein Pfad geklont, indem die Pfadbefehle aufgelistet und ein neuer Pfad basierend auf den Daten erstellt wird. Der neue Pfad besteht jedoch nur MoveTo aus und LineTo Aufrufen. Alle Kurven und geraden Linien werden auf eine Reihe von winzigen Linien reduziert.

Beim Aufrufen CloneWithTransformübergeben Sie die Methode a Func<SKPoint, SKPoint>, eine Funktion mit einem SKPaint Parameter, der einen SKPoint Wert zurückgibt. Diese Funktion wird für jeden Punkt aufgerufen, um eine benutzerdefinierte algorithmische Transformation anzuwenden:

static class PathExtensions
{
    public static SKPath CloneWithTransform(this SKPath pathIn, Func<SKPoint, SKPoint> transform)
    {
        SKPath pathOut = new SKPath();

        using (SKPath.RawIterator iterator = pathIn.CreateRawIterator())
        {
            SKPoint[] points = new SKPoint[4];
            SKPathVerb pathVerb = SKPathVerb.Move;
            SKPoint firstPoint = new SKPoint();
            SKPoint lastPoint = new SKPoint();

            while ((pathVerb = iterator.Next(points)) != SKPathVerb.Done)
            {
                switch (pathVerb)
                {
                    case SKPathVerb.Move:
                        pathOut.MoveTo(transform(points[0]));
                        firstPoint = lastPoint = points[0];
                        break;

                    case SKPathVerb.Line:
                        SKPoint[] linePoints = Interpolate(points[0], points[1]);

                        foreach (SKPoint pt in linePoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        lastPoint = points[1];
                        break;

                    case SKPathVerb.Cubic:
                        SKPoint[] cubicPoints = FlattenCubic(points[0], points[1], points[2], points[3]);

                        foreach (SKPoint pt in cubicPoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        lastPoint = points[3];
                        break;

                    case SKPathVerb.Quad:
                        SKPoint[] quadPoints = FlattenQuadratic(points[0], points[1], points[2]);

                        foreach (SKPoint pt in quadPoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        lastPoint = points[2];
                        break;

                    case SKPathVerb.Conic:
                        SKPoint[] conicPoints = FlattenConic(points[0], points[1], points[2], iterator.ConicWeight());

                        foreach (SKPoint pt in conicPoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        lastPoint = points[2];
                        break;

                    case SKPathVerb.Close:
                        SKPoint[] closePoints = Interpolate(lastPoint, firstPoint);

                        foreach (SKPoint pt in closePoints)
                        {
                            pathOut.LineTo(transform(pt));
                        }

                        firstPoint = lastPoint = new SKPoint(0, 0);
                        pathOut.Close();
                        break;
                }
            }
        }
        return pathOut;
    }
    ...
}

Da der geklonte Pfad auf winzige gerade Linien reduziert wird, kann die Transformationsfunktion gerade Linien in Kurven konvertieren.

Beachten Sie, dass die Methode den ersten Punkt jeder Kontur in der aufgerufenen Variablen firstPoint und die aktuelle Position nach jedem Zeichnungsbefehl in der Variablen lastPointbeibehält. Diese Variablen sind erforderlich, um die endgültige Endlinie zu erstellen, wenn ein Close Verb gefunden wird.

Das GlobularText-Beispiel verwendet diese Erweiterungsmethode, um Text scheinbar um eine Hemisphäre in einem 3D-Effekt umzuschließen:

Dreifacher Screenshot der Globular-Textseite

Der GlobularTextPage Klassenkonstruktor führt diese Transformation aus. Es erstellt ein SKPaint Objekt für den Text und ruft dann ein SKPath Objekt aus der GetTextPath Methode ab. Dies ist der Pfad, der zusammen mit einer Transformationsfunktion an die CloneWithTransform Erweiterungsmethode übergeben wird:

public class GlobularTextPage : ContentPage
{
    SKPath globePath;

    public GlobularTextPage()
    {
        Title = "Globular Text";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;

        using (SKPaint textPaint = new SKPaint())
        {
            textPaint.Typeface = SKTypeface.FromFamilyName("Times New Roman");
            textPaint.TextSize = 100;

            using (SKPath textPath = textPaint.GetTextPath("HELLO", 0, 0))
            {
                SKRect textPathBounds;
                textPath.GetBounds(out textPathBounds);

                globePath = textPath.CloneWithTransform((SKPoint pt) =>
                {
                    double longitude = (Math.PI / textPathBounds.Width) *
                                            (pt.X - textPathBounds.Left) - Math.PI / 2;
                    double latitude = (Math.PI / textPathBounds.Height) *
                                            (pt.Y - textPathBounds.Top) - Math.PI / 2;

                    longitude *= 0.75;
                    latitude *= 0.75;

                    float x = (float)(Math.Cos(latitude) * Math.Sin(longitude));
                    float y = (float)Math.Sin(latitude);

                    return new SKPoint(x, y);
                });
            }
        }
    }
    ...
}

Die Transformationsfunktion berechnet zunächst zwei Benannte longitude Werte und latitude diesen Bereich von –π/2 oben und links neben dem Text bis π/2 am rechten und unteren Rand des Texts. Der Bereich dieser Werte ist nicht visuell zufriedenstellend, sodass sie durch Multiplizieren mit 0,75 reduziert werden. (Versuchen Sie den Code ohne diese Anpassungen. Der Text wird zu undurchsichtig an den Nord- und Südpolen und zu dünn an den Seiten.) Diese dreidimensionalen sphärischen Koordinaten werden nach Standardformeln in zweidimensionale x Und y Koordinaten konvertiert.

Der neue Pfad wird als Feld gespeichert. Der PaintSurface Handler muss dann lediglich den Pfad zentrieren und skalieren, um ihn auf dem Bildschirm anzuzeigen:

public class GlobularTextPage : ContentPage
{
    SKPath globePath;
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint pathPaint = new SKPaint())
        {
            pathPaint.Style = SKPaintStyle.Fill;
            pathPaint.Color = SKColors.Blue;
            pathPaint.StrokeWidth = 3;
            pathPaint.IsAntialias = true;

            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.Scale(0.45f * Math.Min(info.Width, info.Height));     // radius
            canvas.DrawPath(globePath, pathPaint);
        }
    }
}

Dies ist eine sehr vielseitige Technik. Wenn das Im Artikel "Pfadeffekte" beschriebene Array von Pfadeffekten nicht ganz etwas umfasst, das Sie sehen sollten, ist dies eine Möglichkeit, die Lücken auszufüllen.