Gutenberg

Class Document<T>

Represents an immutable textual document which can be laid out in a variety of ways. Once laid out, the document can be rendered by an IDocumentRenderer<T>.

A Document<T>'s layout is determined by the available page width, the locations of line breaks within the document, and the locations of groups within the document.

The page has a certain maximum width (determined by PageWidth), which the layout algorithm tries not to exceed, by inserting line breaks where possible. The possible line breaks are indicated by the presence of LineBreak values within the document.

A document may contain groups, introduced by the Grouped() method. A group indicates a "flattenable" region within a document. When the layout algorithm encounters a group, it will attempt to flatten the group into a single line. If the result does not fit within the page width, the group is rendered without changes. Groups give the layout algorithm flexibility to choose the "best" way to display a document, by using the available horizontal space efficiently.

The document may contain annotations - values of type T - which can be interpreted by the IDocumentRenderer<T>.

This class is intended to be imported under an alias, since typically the type of annotations won't change within your code: using Doc = Gutenberg.Document<MyAnnotation>;

Inheritance
Declaration
public abstract class Document<T> : Object
Type Parameters
Name Description

T

The type of annotations in the document.

Remarks

The examples in the documentation for this class are assumed to be preceded by using Doc = Gutenberg.Document<object>;

Properties

Empty

Represents a Document<T> containing no text.

Declaration
public static Document<T> Empty { get; }
Property Value
Type Description

Document<T>

A Document<T> containing no text.

Examples

Empty is equivalent to Doc.FromString(""). This means that the document still occupies vertical space:

var doc = new Doc[] { "abc", Doc.Empty, "def" }
    .Separated(Doc.LineBreak);
Console.WriteLine(doc);
// Output:
// abc
// 
// def

HardLineBreak

A Document<T> which always creates a line break, even when Grouped(). This document cannot be flattened. You should probably use LineBreak.

Declaration
public static Document<T> HardLineBreak { get; }
Property Value
Type Description

Document<T>

Examples

HardLineBreak negates the effect of Grouped(), even when there are other line breaks in the group.

var doc = Doc.Concat(
    "abc", Doc.LineBreak,
    "def", Doc.HardLineBreak,
    "ghi"
).Grouped();
Console.WriteLine(doc);
// Output:
// abc
// def
// ghi

LineBreak

A Document<T> which advances to the next line and indents to the current nesting level.

When flattened, LineBreak is displayed as a single space.

Declaration
public static Document<T> LineBreak { get; }
Property Value
Type Description

Document<T>

Remarks

By default, LineBreak starts a new line. However, if the line break is undone by Grouped(), the line break is rendered as a single space.

Examples

The default behaviour of LineBreak is to start a new line.

var doc = Doc.FromString("abc")
    .Append(Doc.LineBreak)
    .Append("def");
Console.WriteLine(doc);
// Output:
// abc
// def

When the LineBreak is Grouped(), the layout algorithm tries to compress the line break to a single space.

var doc = Doc.FromString("abc")
    .Append(Doc.LineBreak)
    .Append("def")
    .Grouped();
Console.WriteLine(doc);
// Output:
// abc def

When the LineBreak is Grouped(), but the page width is too narrow to fit the text into a single line, the text is broken at the LineBreak.

var doc = Doc.FromString("abc")
    .Append(Doc.LineBreak)
    .Append("def")
    .Grouped();
Console.WriteLine(doc.ToString(4));
// Output:
// abc
// def
See Also
LineBreakOr(Document<T>)

LineBreakHint

A "soft line break". Behaves like FromString(" ") if the resulting output fits the page; otherwise, this behaves like LineBreak.

Declaration
public static Document<T> LineBreakHint { get; }
Property Value
Type Description

Document<T>

Examples
Console.WriteLine(Doc.Concat("abc", Doc.LineBreakHint, "def"));
// Output:
// abc def
var doc = Doc.Concat("abc", Doc.LineBreakHint, "def");
Console.WriteLine(doc.ToString(5));
// Output:
// abc
// def

ZeroWidthLineBreak

A Document<T> which advances to the next line and indents to the current nesting level.

When flattened, ZeroWidthLineBreak behaves like Empty.

Declaration
public static Document<T> ZeroWidthLineBreak { get; }
Property Value
Type Description

Document<T>

Remarks

ZeroWidthLineBreak is like LineBreak, except it behaves like Empty if the line break is undone by Grouped().

Examples

The default behaviour of ZeroWidthLineBreak is to start a new line.

var doc = Doc.FromString("abc")
    .Append(Doc.ZeroWidthLineBreak)
    .Append("def");
Console.WriteLine(doc);
// Output:
// abc
// def

When the ZeroWidthLineBreak is Grouped(), the layout algorithm tries to compress the line break to nothing.

var doc = Doc.FromString("abc")
    .Append(Doc.ZeroWidthLineBreak)
    .Append("def")
    .Grouped();
Console.WriteLine(doc);
// Output:
// abcdef

When the ZeroWidthLineBreak is Grouped(), but the page width is too narrow to fit the text into a single line, the text is broken at the ZeroWidthLineBreak.

var doc = Doc.FromString("abc")
    .Append(Doc.ZeroWidthLineBreak)
    .Append("def")
    .Grouped();
Console.WriteLine(doc.ToString(4));
// Output:
// abc
// def
See Also
LineBreak
LineBreakOr(Document<T>)

ZeroWidthLineBreakHint

A "soft line break". Behaves like Empty if the resulting output fits the page; otherwise, this behaves like LineBreak.

Declaration
public static Document<T> ZeroWidthLineBreakHint { get; }
Property Value
Type Description

Document<T>

Examples
Console.WriteLine(Doc.Concat("abc", Doc.ZeroWidthLineBreakHint, "def"));
// Output:
// abcdef
var doc = Doc.Concat("abc", Doc.ZeroWidthLineBreakHint, "def");
Console.WriteLine(doc.ToString(5));
// Output:
// abc
// def

Methods

Aligned()

Sets the nesting level of the document to the current column.

Declaration
public Document<T> Aligned()
Returns
Type Description

Document<T>

Remarks

For many purposes, Nested(Int32) is both simpler and more efficient than Aligned().

Examples
var doc = Doc.Concat(
    "leading text ",
    new Doc[]
    {
        "first line",
        "second line",
        "third line"
    }.Separated(Doc.LineBreak).Aligned()
);
Console.WriteLine(doc);
// Output:
// leading text first line
//              second line
//              third line

Aligned() sets the nesting level to the current column, but the aligned document may have some further nesting inside it.

var doc = Doc.Concat(
    "leading text ",
    Doc.Concat(
        "first line", 
        Doc.LineBreak, "second line",
        Doc.Concat (
            Doc.LineBreak, "nested line 1",
            Doc.LineBreak, "nested line 2"
        ).Nested(4),  // nested inside aligned
        Doc.LineBreak, "last line"
    ).Aligned()
);
Console.WriteLine(doc);
// Output:
// leading text first line
//              second line
//                  nested line 1
//                  nested line 2
//              last line

But if an aligned document appears inside a Nested(Int32) document, it overrides the nesting level.

var doc = Doc.Concat(
    "leading text ",
    new Doc[]
    {
        "first line",
        "second line",
        "third line"
    }.Separated(Doc.LineBreak)
        .Aligned()  // aligned inside nested
        .Nested(4)
);
Console.WriteLine(doc);
// Output:
// leading text first line
//              second line
//              third line

Annotated(T)

Apply an annotation to the current Document<T>. The annotation will be passed to the IDocumentRenderer<T>.

Declaration
public Document<T> Annotated(T value)
Parameters
Type Name Description

T

value

The annotation

Returns
Type Description

Document<T>

A copy of the current Document<T> with an annotation applied.

Append(Document<T>)

Returns a new Document<T> representing the content of this document concatenated with the content of other.

Declaration
public Document<T> Append(Document<T> other)
Parameters
Type Name Description

Document<T>

other

The document to append to this document

Returns
Type Description

Document<T>

A new Document<T> representing the content of this document concatenated with the content of other.

Remarks

x.Append(y) is equivalent to Document<T>.Concat(x, y).

Examples
Console.WriteLine(Doc.FromString("abc").Append("def"));
// Output:
// abcdef
See Also
Concat(Document<T>[])

Between(Document<T>, Document<T>)

Returns a new Document<T> representing the content of the current document surrounded by the contents of before and after.

Declaration
public Document<T> Between(Document<T> before, Document<T> after)
Parameters
Type Name Description

Document<T>

before

The document to place before the current document

Document<T>

after

The document to place after the current document

Returns
Type Description

Document<T>

A new Document<T> representing the content of the current document surrounded by the contents of before and after.

Examples
Console.WriteLine(Doc.FromString("abc").Between("(", ")"));
// Output:
// (abc)

Concat(Document<T>[])

Returns a new Document<T> representing the contents of all of documents concatenated together.

Declaration
public static Document<T> Concat(params Document<T>[] documents)
Parameters
Type Name Description

Document<T>[]

documents

The documents to concatenate

Returns
Type Description

Document<T>

A new Document<T> representing the contents of all of documents concatenated together.

Examples
Console.WriteLine(Doc.Concat("abc", "def", "ghi"));
// Output:
// abcdefghi

Concat(IEnumerable<Document<T>>)

See Concat(Document<T>[]).

Declaration
public static Document<T> Concat(IEnumerable<Document<T>> documents)
Parameters
Type Name Description

IEnumerable<Document<T>>

documents

Returns
Type Description

Document<T>

FromBox(Box<T>)

Creates a document containing a Box<T>.

Declaration
public static Document<T> FromBox(Box<T> box)
Parameters
Type Name Description

Box<T>

box

The Box<T> from which to create the Document<T>.

Returns
Type Description

Document<T>

A Document<T> representing the text containted in box.

FromString(String)

Creates a Document<T> containing the specified text.

Any line breaks ('\n' characters) in the value will be converted to LineBreaks.

Declaration
public static Document<T> FromString(string value)
Parameters
Type Name Description

String

value

The text from which to create the Document<T>.

Returns
Type Description

Document<T>

A Document<T> representing the text containted in value.

Examples
Console.WriteLine(Doc.FromString("abc"));
// Output:
// abc

If the value contains line breaks, they will be converted to LineBreaks. If the Document<T> is Grouped() then the line breaks are liable to be flattened.

Console.WriteLine(Doc.FromString("abc\ndef").Grouped());
// Output:
// abc def

Grouped()

Signals that the current Document<T> is eligible to be flattened.

When the layout algorithm encounters a Grouped() region of a document, it attempts to flatten that region by removing any line breaks within it. If the flattened version of the document does not fit within the page width, the original document will be rendered without any changes.

Declaration
public Document<T> Grouped()
Returns
Type Description

Document<T>

Remarks

The appearance of the flattened version of the document depends on the variety of line breaks within it: LineBreak flattens to a single space; ZeroWidthLineBreak flattens to nothing.

Examples

Here is an example of a document containing LineBreaks, which is rendered on a single line because it has been grouped.

var doc = Doc.Concat(
    "abc", Doc.LineBreak,
    "def", Doc.LineBreak,
    "ghi"
).Grouped();
Console.WriteLine(doc);
// Output:
// abc def ghi

Here, the document from the previous example does not fit on a single line due to the narrow page width. The original LineBreaks are rendered unchanged.

var doc = Doc.Concat(
    "abc", Doc.LineBreak,
    "def", Doc.LineBreak,
    "ghi"
).Grouped();
Console.WriteLine(doc.ToString(3));
// Output:
// abc
// def
// ghi

The group is flattened atomically - either all of the line breaks within the group are flattened or the document is rendered unchanged. In this example, the page is wide enough for the first two line breaks to be flattened, but not the third. However, all three line breaks are rendered as line breaks.

var doc = Doc.Concat(
    "abc", Doc.LineBreak,
    "def", Doc.LineBreak,
    "ghi"
).Grouped();
Console.WriteLine(doc.ToString(7));
// Output:
// abc
// def
// ghi

Groups may be nested within each other arbitrarily. If a group fits within a line, the entire group will be flattened, including any groups nested within it.

var doc = Doc.Concat(
    Doc.Concat("abc", Doc.LineBreak, "def").Grouped(),
    Doc.LineBreak,
    Doc.Concat("ghi", Doc.LineBreak, "jkl").Grouped()
).Grouped();
Console.WriteLine(doc);
// Output:
// abc def ghi jkl

If a parent group doesn't fit within the page width, the child groups are still eligibe to be flattened.

var doc = Doc.Concat(
    Doc.Concat("abc", Doc.LineBreak, "def").Grouped(),
    Doc.LineBreak,
    Doc.Concat("ghi", Doc.LineBreak, "jkl").Grouped()
).Grouped();
Console.WriteLine(doc.ToString(7));
// Output:
// abc def
// ghi jkl

Hanging(Int32)

Sets the nesting level to the current column plus amount.

Declaration
public Document<T> Hanging(int amount)
Parameters
Type Name Description

Int32

amount

Returns
Type Description

Document<T>

Remarks

doc.Hanging(amt) is equivalent to doc.Nested(amount).Aligned().

Examples
var doc = Doc.Concat(
    "leading text ",
    new Doc[]
    {
        "first line",
        "second line",
        "third line"
    }.Separated(Doc.LineBreak).Hanging(4)
);
Console.WriteLine(doc);
// Output:
// leading text first line
//                  second line
//                  third line

Indented(Int32)

Aligns the current document and indents it by indentation, starting at the current column.

Declaration
public Document<T> Indented(int indentation)
Parameters
Type Name Description

Int32

indentation

The amount of

Returns
Type Description

Document<T>

Remarks

doc.Indented(amt) is equivalent to Doc.FromString(new string(' ', amt)) + doc.Aligned().

Examples
var doc = Doc.Concat(
    "leading text ",
    new Doc[]
    {
        "first line",
        "second line",
        "third line"
    }.Separated(Doc.LineBreak).Indented(4)
);
Console.WriteLine(doc);
// Output:
// leading text     first line
//                  second line
//                  third line

LineBreakHintOr(Document<T>)

A "soft line break". Behaves like ifFits if the resulting output fits the page; otherwise, this behaves like LineBreak.

Declaration
public static Document<T> LineBreakHintOr(Document<T> ifFits)
Parameters
Type Name Description

Document<T>

ifFits

The document to render if there's enough space

Returns
Type Description

Document<T>

A Document<T> which behaves like ifFits if the resulting output fits the page but allows the layout engine to break the line if it doesn't.

Examples
Console.WriteLine(Doc.Concat("abc", Doc.LineBreakHintOr("|"), "def"));
// Output:
// abc|def
var doc = Doc.Concat("abc", Doc.LineBreakHintOr("|"), "def");
Console.WriteLine(doc.ToString(5));
// Output:
// abc
// def

LineBreakOr(Document<T>)

Creates a Document<T> which advances to the next line and indents to the current nesting level.

When flattened, the ifFlattened text is displayed instead.

Declaration
public static Document<T> LineBreakOr(Document<T> ifFlattened)
Parameters
Type Name Description

Document<T>

ifFlattened

Returns
Type Description

Document<T>

Remarks

By default, LineBreakOr(ifFlattened) starts a new line. However, if the line break is undone by Grouped(), the line break is rendered as ifFlattened.

Examples

The default behaviour of LineBreakOr(Document<T>) is to start a new line.

var doc = Doc.FromString("abc")
    .Append(Doc.LineBreakOr("|"))
    .Append("def");
Console.WriteLine(doc);
// Output:
// abc
// def

When the document is Grouped(), the layout algorithm tries to render the line break as ifFlattened.

var doc = Doc.FromString("abc")
    .Append(Doc.LineBreakOr("|"))
    .Append("def")
    .Grouped();
Console.WriteLine(doc);
// Output:
// abc|def

When the LineBreakOr(Document<T>) is Grouped(), but the page width is too narrow to fit the text into a single line, the text is broken at the line break.

var doc = Doc.FromString("abc")
    .Append(Doc.LineBreakOr("|"))
    .Append("def")
    .Grouped();
Console.WriteLine(doc.ToString(4));
// Output:
// abc
// def
See Also
LineBreak

MapAnnotations<U>(Func<T, U>)

Apply a function to all the annotations in the current document.

Declaration
public Document<U> MapAnnotations<U>(Func<T, U> selector)
Parameters
Type Name Description

Func<T, U>

selector

The function to apply to the annotations.

Returns
Type Description

Document<U>

A document with all of the annotations replaced with the return value of selector

Type Parameters
Name Description

U

Remarks

Typically this method is less efficient than MapAnnotations<T, U>(IDocumentRenderer<U>, Func<T, U>), because this method has to consider all the possible ways the document could be laid out, including layouts which would be discarded.

MapAnnotations<U>(Func<T, IEnumerable<U>>)

Apply a function to all the annotations in the current document. If the function returns multiple annotations, the annotations are added in a left-to-right fashion.

Declaration
public Document<U> MapAnnotations<U>(Func<T, IEnumerable<U>> selector)
Parameters
Type Name Description

Func<T, IEnumerable<U>>

selector

The function to apply to the annotations.

Returns
Type Description

Document<U>

A document with all of the annotations replaced with the return values of selector

Type Parameters
Name Description

U

Nested()

Increase the nesting level of the current Document<T> by DefaultNesting.

A document's nesting level indicates the amount of indentation which should be applied to line breaks within the document.

Declaration
public Document<T> Nested()
Returns
Type Description

Document<T>

A new Document<T> representing the current document with its nesting level increased by DefaultNesting.

Examples
var doc = new Doc[]
{
    "first line",
    "second line",
    "third line"
}.Separated(Doc.LineBreak).Nested();
var options = LayoutOptions.Default with { DefaultNesting = 2 };
Console.WriteLine(doc.ToString(options));
// Output:
// first line
//   second line
//   third line
See Also
Nested(Int32)

Nested(Int32)

Increase the nesting level of the current Document<T> by amount.

A document's nesting level indicates the amount of indentation which should be applied to line breaks within the document.

Declaration
public Document<T> Nested(int amount)
Parameters
Type Name Description

Int32

amount

The amount by which to increase the document's nesting level

Returns
Type Description

Document<T>

A new Document<T> representing the current document with its nesting level increased by amount.

Remarks

An amount of 0 is equivalent to not nesting the document at all.

Examples

A nested document is not "immediately" indented. Rather, the given amount of nesting is applied to any line breaks within the document. If you nest a document which has some text on its first line, that text is displayed unaltered. The following lines in the document will have the nesting applied to them.

var doc = new Doc[]
{
    "first line",
    "second line",
    "third line"
}.Separated(Doc.LineBreak).Nested(4);
Console.WriteLine(doc);
// Output:
// first line
//     second line
//     third line

This also applies when there is already text on the current line:

var doc = Doc.Concat(
    "leading text ",
    new Doc[]
    {
        "first line",
        "second line",
        "third line"
    }.Separated(Doc.LineBreak).Nested(4)
);
Console.WriteLine(doc);
// Output:
// leading text first line
//     second line
//     third line
See Also
Nested()

Reflow(String)

Inserts LineBreakHints between words, so that the text is broken into multiple lines when it is too wide for the page.

Declaration
public static Document<T> Reflow(string text)
Parameters
Type Name Description

String

text

Returns
Type Description

Document<T>

Render(IDocumentRenderer<T>, CancellationToken)

Lay out the Document<T>, using the default LayoutOptions, and write its text to the renderer.

Declaration
public ValueTask Render(IDocumentRenderer<T> renderer, CancellationToken cancellationToken = null)
Parameters
Type Name Description

IDocumentRenderer<T>

renderer

The IDocumentRenderer<T>.

CancellationToken

cancellationToken

A CancellationToken.

Returns
Type Description

ValueTask

A ValueTask which will complete when all of the Document<T>'s text has been written to the renderer.

Render(LayoutOptions, IDocumentRenderer<T>, CancellationToken)

Lay out the Document<T> and write its text to the renderer.

Declaration
public ValueTask Render(LayoutOptions options, IDocumentRenderer<T> renderer, CancellationToken cancellationToken = null)
Parameters
Type Name Description

LayoutOptions

options

Options for rendering the document

IDocumentRenderer<T>

renderer

The IDocumentRenderer<T>.

CancellationToken

cancellationToken

A CancellationToken.

Returns
Type Description

ValueTask

A ValueTask which will complete when all of the Document<T>'s text has been written to the renderer.

Render(Int32, IDocumentRenderer<T>, CancellationToken)

Lay out the Document<T>, with the given pageWidth, and write its text to the renderer.

Declaration
public ValueTask Render(int pageWidth, IDocumentRenderer<T> renderer, CancellationToken cancellationToken = null)
Parameters
Type Name Description

Int32

pageWidth

The page width

IDocumentRenderer<T>

renderer

The IDocumentRenderer<T>.

CancellationToken

cancellationToken

A CancellationToken.

Returns
Type Description

ValueTask

A ValueTask which will complete when all of the Document<T>'s text has been written to the renderer.

Select<U>(Func<T, U>)

Apply a function to all the annotations in the current document.

Declaration
public Document<U> Select<U>(Func<T, U> selector)
Parameters
Type Name Description

Func<T, U>

selector

The function to apply to the annotations.

Returns
Type Description

Document<U>

A document with all of the annotations replaced with the return value of selector

Type Parameters
Name Description

U

Remarks

Typically this method is less efficient than MapAnnotations<T, U>(IDocumentRenderer<U>, Func<T, U>), because this method has to consider all the possible ways the document could be laid out, including layouts which would be discarded.

ToString()

Lay out the Document<T>, using the default LayoutOptions, and render it as a string.

Declaration
public override string ToString()
Returns
Type Description

String

ToString(LayoutOptions)

Lay out the Document<T> and render it as a string.

Declaration
public string ToString(LayoutOptions options)
Parameters
Type Name Description

LayoutOptions

options

Returns
Type Description

String

ToString(Int32)

Lay out the Document<T>, with the given pageWidth, and render it as a string.

Declaration
public string ToString(int pageWidth)
Parameters
Type Name Description

Int32

pageWidth

Returns
Type Description

String

UnsafeFromString(String)

Creates a Document<T> containing the specified text.

The value MUST NOT contain newline characters. This precondition is not checked. It is your responsibility to ensure that there are no '\n's in the input.

You should probably use FromString(String) instead.

Declaration
public static Document<T> UnsafeFromString(string value)
Parameters
Type Name Description

String

value

The text from which to create the Document<T>. This value MUST NOT contain line breaks.

Returns
Type Description

Document<T>

A Document<T> representing the text containted in value.

See Also
FromString(String)

Write(LayoutOptions, TextWriter, CancellationToken)

Lay out the Document<T> and write it into a TextWriter.

Declaration
public ValueTask Write(LayoutOptions options, TextWriter writer, CancellationToken cancellationToken = null)
Parameters
Type Name Description

LayoutOptions

options

The LayoutOptions

TextWriter

writer

The TextWriter

CancellationToken

cancellationToken

A CancellationToken

Returns
Type Description

ValueTask

A ValueTask which will complete when all of the Document<T>'s text has been written to the writer.

Write(TextWriter, CancellationToken)

Lay out the Document<T> and write it into a TextWriter.

Declaration
public ValueTask Write(TextWriter writer, CancellationToken cancellationToken = null)
Parameters
Type Name Description

TextWriter

writer

The TextWriter

CancellationToken

cancellationToken

A CancellationToken

Returns
Type Description

ValueTask

A ValueTask which will complete when all of the Document<T>'s text has been written to the writer.

Operators

Addition(Document<T>, Document<T>)

Returns a new Document<T> representing the content of left concatenated with the content of right.

Declaration
public static Document<T> operator +(Document<T> left, Document<T> right)
Parameters
Type Name Description

Document<T>

left

The document to append right to

Document<T>

right

The document to append to left

Returns
Type Description

Document<T>

A new Document<T> representing the content of left concatenated with the content of right.

Remarks

x + y is equivalent to x.Append(y).

Examples
Console.WriteLine(Doc.FromString("abc") + Doc.FromString("def"));
// Output:
// abcdef
See Also
Append(Document<T>)

Implicit(Box<T> to Document<T>)

Implicitly convert a Box<T> to a Document<T>.

Declaration
public static implicit operator Document<T>(Box<T> value)
Parameters
Type Name Description

Box<T>

value

The Box<T> from which to create the Document<T>.

Returns
Type Description

Document<T>

A Document<T> representing the text containted in value.

Remarks

This conversion is equivalent to FromBox(Box<T>).

Implicit(String to Document<T>)

Implicitly convert a String to a Document<T>.

Declaration
public static implicit operator Document<T>(string value)
Parameters
Type Name Description

String

value

The text from which to create the Document<T>.

Returns
Type Description

Document<T>

A Document<T> representing the text containted in value.

Remarks

This conversion is equivalent to FromString(String).

Examples

This conversion is especially useful when used with methods which accept a Document<T>, such as Concat(Document<T>[]).

Console.WriteLine(Doc.Concat("abc", "def"));
// Output:
// abcdef