Skip to content

Gradient Text

A gradient in the creator is a Color, not a separate drawing operation. Creator.NewLinearGradientColor and Creator.NewRadialGradientColor return values that satisfy the same Color interface as creator.ColorBlack, so anywhere a flat color goes a gradient goes as well. Assign one to chunk.Style.Color and the glyphs are filled with a shading pattern instead of a solid ink.

The same values work as the fill color of a rectangle, ellipse, polygon, curve or border. Text is just the case where the result is most obviously useful.

Two stops and a direction

A gradient is a list of ColorPoint values: a color and its position from 0.0 at the start to 1.0 at the end.

gradient := c.NewLinearGradientColor([]*creator.ColorPoint{
    creator.NewColorPoint(creator.ColorRGBFrom8bit(0xE5, 0x3E, 0x3E), 0.0),
    creator.NewColorPoint(creator.ColorRGBFrom8bit(0x2B, 0x6C, 0xB0), 1.0),
})

p := c.NewStyledParagraph()
chunk := p.Append("Linear gradient heading")
chunk.Style.FontSize = 32
chunk.Style.Color = gradient

if err := c.Draw(p); err != nil {
    return err
}

That runs left to right. SetAngle rotates it, in degrees, and AddColorStop appends a stop after construction if you would rather build the list up.

With exactly two stops the positions are not read - the first color is placed at 0.0 and the last at 1.0 regardless of what you passed, so NewColorPoint(color, 0.3) in a two-stop list does nothing. Positions start mattering at three stops, where the intermediate ones become the bounds of a stitching function.

The radial constructor takes position and radii before the stops:

gradient := c.NewRadialGradientColor(0, 0, 0, -1, []*creator.ColorPoint{
    creator.NewColorPoint(creator.ColorRGBFrom8bit(0xF6, 0xC3, 0x43), 0.0),
    creator.NewColorPoint(creator.ColorRGBFrom8bit(0xC0, 0x39, 0x2B), 1.0),
})

The first two arguments are an offset from an anchor point, not page coordinates. The anchor defaults to the center of the area being filled, so 0, 0 centers the gradient on the text; SetAnchor moves it to a corner or an edge. The last two are the inner and outer radius. An outer radius of -1 sizes the gradient to the filled area. A fixed radius smaller than the text leaves the glyphs outside the disc unpainted, which is a legitimate effect but easy to hit by accident.

Extent follows the chunks that use the color

This is the part worth understanding before you start tuning numbers. A shading pattern is a field of color laid out in page space; the glyphs mask it. So what the gradient looks like depends on the box the pattern is given, and that box is derived from the paragraph after layout, not from anything you set.

Vertically the box is always the paragraph’s full height. Horizontally it is the union of the spans of the chunks that share that color value. The practical consequence:

Where the color is assignedResulting extent
One chunkThat chunk’s horizontal span. The gradient completes within the chunk.
Several chunks, same valueThe union of their spans, so the ramp continues across them.
SetFontColor on the paragraphThe whole paragraph.

Dedup is by value, so reusing one *LinearShading across chunks registers a single pattern and produces one continuous ramp. Calling NewLinearGradientColor twice with identical stops gives two patterns and two independent ramps. Mixed with flat chunks it composes as you would expect:

flat := p.Append("Flat color, then ")
flat.Style.Color = creator.ColorBlack

graded := p.Append("a gradient chunk")
graded.Style.Color = gradient

trailing := p.Append(", then flat again.")
trailing.Style.Color = creator.ColorBlack

Things that catch people out

The bundled image renderer does not paint pattern-filled text. It falls back to solid black, so converting a page to an image through the render package loses the gradient. The PDF itself is correct and viewers show it properly. If you are checking output in CI by rendering to PNG, this looks like a bug in your code and is not.

Anti-aliasing is off by default; SetAntiAlias(true) turns it on. Extends are off too, meaning the area beyond the first and last stop is left to the background rather than being filled with the end colors. SetExtends(true, true) is usually what you want for a radial gradient with a fixed outer radius, and SetBackgroundColor sets what shows through otherwise (white by default).

SetBoundingBox exists on both shading types but calling it on a gradient used for text has no effect. The paragraph overwrites the box during drawing.

Run the example

The example draws three paragraphs: a linear gradient heading, a radial one, and a paragraph mixing a gradient chunk between two flat ones.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/text
go run pdf_gradient_text.go

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source
Last updated on