Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Positioned PNG/JPEG images on `VectorCanvas` from file paths, byte arrays,
and streams, with `Stretch`, `Contain`, centred or top-left `Cover`, and
natural-size top-left crop modes. Image clipping is isolated from subsequent
canvas commands.
- Optional clockwise text rotation around the canvas text baseline point for
standard and registered custom fonts.
- Elliptical arcs and closed sectors through `PathDescriptor`, plus
`FillPie`, `StrokePie`, and `DrawPie` canvas conveniences.
- Native dash patterns and phases for canvas lines and stroked rectangles.

### Fixed
- PNG decoding now supports 8-bit grayscale images with alpha (colour type 4).

### Changed
- Updated `Microsoft.SourceLink.GitHub` to `10.0.401` to remove the vulnerable
transitive `Microsoft.Build.Tasks.Git` 8.0.0 dependency.

---

## [2.1.0] - 2026-09-03
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Devanagari-aware rendering (conjunct ligatures, reph, and below-base 'ra' forms,
shaping engine) and automatic glyph subsetting, so only the glyphs a document actually uses are
embedded. See [Custom Fonts](https://github.com/sahebansari/TerraPDF/blob/master/docs/custom-fonts.md).
>
> Also: Code128 barcodes and QR codes (ISO/IEC 18004, versions 1-40, all four error correction levels) via `container.Barcode(...)` and `container.QrCode(...)` — rendered as vector-filled rectangles, no raster image pipeline, placeable anywhere a `Column`, `Row`, or `Table` cell can go. The vector canvas (`container.Canvas(...)`) can now place text labels and draw with constant-alpha transparency. TerraPDF also multi-targets **.NET 10 (LTS)** alongside .NET 8 and 9.
> Also: Code128 barcodes and QR codes (ISO/IEC 18004, versions 1-40, all four error correction levels) via `container.Barcode(...)` and `container.QrCode(...)` — rendered as vector-filled rectangles, no raster image pipeline, placeable anywhere a `Column`, `Row`, or `Table` cell can go. The vector canvas (`container.Canvas(...)`) supports positioned and clipped images, rotated text, arcs and pie sectors, dashed strokes, and constant-alpha transparency. TerraPDF also multi-targets **.NET 10 (LTS)** alongside .NET 8 and 9.

TerraPDF is a lightweight, zero-dependency, pure C# PDF 1.7 writer for programmatic document generation.
Its fluent API covers the full document-authoring lifecycle: page layout, rich text, tables, images,
Expand Down Expand Up @@ -82,7 +82,7 @@ runtime packages.
- **Images from bytes and streams** — `Image(byte[])` / `Image(Stream)` with transparency and deduplication
- **Anchor-based bookmarks** — bookmark content directly to rendered elements and keep destinations accurate
- Full **WinAnsiEncoding** character coverage
- **Vector graphics canvas** - lines, rectangles, rounded rectangles, circles, ellipses, arbitrary Bezier paths, polygons, text labels, and grid helpers via `container.Canvas()`
- **Vector graphics canvas** - lines, dashed strokes, rectangles, circles, ellipses, arbitrary Bezier paths, arcs, pie sectors, rotated text, positioned images with fit/crop modes, and grid helpers via `container.Canvas()`
- **Constant-alpha transparency** - translucent fills, strokes, and text on the vector canvas via `/ExtGState`, with `opacity` on every canvas primitive
- **Code128 barcodes** - `container.Barcode(...)`, with optional human-readable caption, custom colours, and quiet zone
- **QR codes** - `container.QrCode(...)`, full ISO/IEC 18004 generator (versions 1-40, error correction levels L/M/Q/H), rendered as vector rectangles
Expand Down Expand Up @@ -156,12 +156,12 @@ For complete API reference and detailed guides, visit the [docs](https://github.
- **[Layout](https://github.com/sahebansari/TerraPDF/blob/master/docs/layout.md)** — Column, Row, and Table layouts with alignment, spacing, and cell spans
- **[Row and Column Layout](https://github.com/sahebansari/TerraPDF/blob/master/docs/row-and-column-layout.md)** — Detailed Row and Column layout examples
- **[Decorators](https://github.com/sahebansari/TerraPDF/blob/master/docs/decorators.md)** — Padding, margin, backgrounds, borders, and styling
- **[Images](https://github.com/sahebansari/TerraPDF/blob/master/docs/images.md)** — PNG and JPEG embedding with sizing and alignment
- **[Images](https://github.com/sahebansari/TerraPDF/blob/master/docs/images.md)** — PNG and JPEG embedding, positioned canvas images, fit modes, clipping, sizing, and alignment
- **[Page Sizes & Units](https://github.com/sahebansari/TerraPDF/blob/master/docs/page-sizes-and-units.md)** — Built-in page sizes and unit conversions
- **[Colors](https://github.com/sahebansari/TerraPDF/blob/master/docs/colors.md)** — Material Design color palette with shades
- **[Encryption & Security](https://github.com/sahebansari/TerraPDF/blob/master/docs/encryption.md)** — AES-256 by default, with AES-128 compatibility mode and permission flags
- **[Custom Fonts](https://github.com/sahebansari/TerraPDF/blob/master/docs/custom-fonts.md)** — embed TrueType fonts for brand typefaces and full Unicode (Cyrillic, Greek, and beyond)
- **[Vector Graphics](https://github.com/sahebansari/TerraPDF/blob/master/docs/vector-graphics.md)** — Canvas API, shapes, paths, text labels, transparency, grids, and charts
- **[Vector Graphics](https://github.com/sahebansari/TerraPDF/blob/master/docs/vector-graphics.md)** — Canvas API, images, rotated text, dashed strokes, arcs, sectors, transparency, grids, and charts
- **[Table of Contents](https://github.com/sahebansari/TerraPDF/blob/master/docs/table-of-contents.md)** — Automatic TOC generation from headings
- **[Bookmarks](https://github.com/sahebansari/TerraPDF/blob/master/docs/bookmarks.md)** — PDF bookmarks and outlines
- **[Components & Templates](https://github.com/sahebansari/TerraPDF/blob/master/docs/components-and-templates.md)** — Reusable components and document templates
Expand Down
47 changes: 47 additions & 0 deletions docs/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,53 @@ container.Image("icon.png", 32);

---

## Positioned Images on a Vector Canvas

`VectorCanvas.Image(...)` places an image in an absolute target rectangle. All
coordinates and dimensions are PDF points relative to the canvas's top-left
origin. File paths, raw bytes, and streams are supported:

```csharp
container.Canvas(220, canvas =>
{
canvas.Image("photo.jpg", 0, 0, 180, 120, ImageFit.Contain);
canvas.Image(logoBytes, 200, 0, 180, 120, ImageFit.Cover);

using Stream source = OpenImage();
canvas.Image(source, 400, 0, 80, 80, ImageFit.Stretch);
});
```

The format is detected from PNG or JPEG magic bytes, not the file extension.
Byte arrays and remaining stream data are copied when `Image` is called because
the canvas is rendered later. A supplied stream is read from its current
position and remains owned by the caller; TerraPDF never disposes it.

Use `VectorCanvas.GetImageSizeInPoints(imageData)` when a target rectangle
should match the natural image size. Pixel dimensions are converted at 96 DPI:

```csharp
var (width, height) = VectorCanvas.GetImageSizeInPoints(logoBytes);
canvas.Image(logoBytes, 20, 20, width, height);
```

### Canvas image fit modes

| Mode | Aspect ratio | Position | Clipping |
|------|--------------|----------|----------|
| `Stretch` | May distort | Fills the target | No |
| `Contain` | Preserved | Centred inside the target | No |
| `Cover` | Preserved | Centred over the target | Yes |
| `CoverTopLeft` | Preserved | Anchored at the target's top-left | Yes |
| `CropTopLeft` | Natural 96-DPI size | Anchored at the target's top-left | Yes |

`Cover`, `CoverTopLeft`, and `CropTopLeft` isolate clipping with PDF graphics
state save/restore operators. A shape or image drawn afterward is therefore not
affected. The clipping rectangle may extend beyond the canvas; it is not
automatically intersected with the canvas bounds.

---

## Combining with Other Decorators

Images participate in the full decorator chain:
Expand Down
79 changes: 71 additions & 8 deletions docs/vector-graphics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

TerraPDF provides a fluent **Canvas API** for drawing vector graphics directly inside
any layout container. You can render lines, rectangles, circles, ellipses, rounded
rectangles, arbitrary Bézier paths, polygons, text labels, and grids — all without
any external dependencies.
rectangles, arbitrary Bézier paths, polygons, arcs, pie sectors, positioned images,
rotated text labels, and grids — all without any external dependencies.

---

Expand Down Expand Up @@ -52,16 +52,23 @@ Every method returns `this` so calls can be chained.
### Lines

```csharp
canvas.Line(x1, y1, x2, y2, hexColor = "#000000", lineWidth = 1, opacity = 1);
canvas.Line(x1, y1, x2, y2, hexColor = "#000000", lineWidth = 1, opacity = 1,
dashPattern = null, dashPhase = 0);
```

Draws a straight line from `(x1, y1)` to `(x2, y2)`.

```csharp
c.Line(0, 20, 300, 20, "#CCCCCC", 0.5); // thin grey rule
c.Line(0, 0, 150, 80, Color.Red.Medium, 2);
c.Line(0, 60, 300, 60, dashPattern: [8, 4], dashPhase: 2);
```

Dash arrays alternate painted and skipped lengths in points. They must contain
at least one positive finite value and cannot contain negative values. The
pattern is copied when the command is added. Each dashed command restores the
PDF graphics state, so a following line remains solid.

---

### Rectangles
Expand All @@ -80,6 +87,9 @@ canvas.DrawRect(x, y, width, height,
fillHex = "#FFFFFF", strokeHex = "#000000", lineWidth = 1, opacity = 1);
```

`StrokeRect` and `DrawRect` also accept trailing `dashPattern` and `dashPhase`
arguments with the same semantics as `Line`.

```csharp
c.FillRect ( 0, 0, 80, 50, Color.Blue.Lighten3);
c.StrokeRect(100, 0, 80, 50, Color.Blue.Darken2, 1.5);
Expand Down Expand Up @@ -150,7 +160,7 @@ c.FillEllipse(100, 40, 80, 30, Color.Purple.Lighten3); // wide, flat ellipse
```csharp
canvas.Text(text, x, y,
hexColor = "#000000", fontSize = 12,
fontFamily = null, bold = false, italic = false, opacity = 1);
fontFamily = null, bold = false, italic = false, opacity = 1, angle = 0);
```

Places one line of text with its **baseline** at `(x, y)` — not the top-left
Expand All @@ -162,9 +172,14 @@ matches one, otherwise through the standard-14 family it resolves to
exactly the same resolution every other TerraPDF text API uses. `Text` draws
one line only: no wrapping, no automatic fitting.

`angle` rotates clockwise in degrees around the baseline point `(x, y)` in the
top-left canvas coordinate system. Negative angles rotate counter-clockwise;
values outside one revolution are accepted unchanged.

```csharp
c.Text("Q1", 10, 100, Color.Grey.Darken2, 9); // axis label
c.Text("Revenue", 10, 20, Color.Blue.Darken2, 16, bold: true); // title
c.Text("Vertical", 220, 80, angle: 90); // rotated around its baseline
c.Text("वित्तीय रिपोर्ट", 10, 140, fontFamily: "NotoSansDevanagari"); // via a registered custom font
```

Expand All @@ -180,6 +195,46 @@ c.Text(label, (canvasWidth - w) / 2, 20, bold: true); // centred

---

### Positioned images

```csharp
canvas.Image(path, x, y, width, height, ImageFit.Contain);
canvas.Image(bytes, x, y, width, height, ImageFit.Cover);
canvas.Image(stream, x, y, width, height, ImageFit.Stretch);
```

Coordinates and dimensions are points. `Contain` centres the complete image;
`Cover` centres and clips it; `CoverTopLeft` clips from the top-left;
`CropTopLeft` keeps the natural 96-DPI size and clips right/bottom overflow.
Internally, TerraPDF computes the image transformation matrix and scopes any
clip with `q`/`Q`, so later canvas commands are unaffected. See
[Images](images.md#positioned-images-on-a-vector-canvas) for source ownership
and the complete fit-mode table.

---

### Arcs and pie sectors

`PathDescriptor.Arc` appends an elliptical arc, while `Sector` connects the arc
to its centre and closes the path. Convenience methods render sectors directly:

```csharp
canvas.FillPie(x, y, width, height, startAngle, sweepAngle, fillHex);
canvas.StrokePie(x, y, width, height, startAngle, sweepAngle, strokeHex, lineWidth);
canvas.DrawPie(x, y, width, height, startAngle, sweepAngle, fillHex, strokeHex, lineWidth);

canvas.Path(path => path
.Arc(100, 60, 80, 40, startAngle: 15, sweepAngle: 220)
.Stroke(Color.Blue.Darken2, 2));
```

Angles start at the ellipse's right-hand point and increase clockwise. Negative
sweeps run counter-clockwise. A zero sweep adds no path; sweeps whose absolute
value exceeds 360 degrees retain every revolution. Arcs are split into cubic
Bézier segments of at most 90 degrees.

---

### Grid helper

Draws a full-canvas grid of evenly spaced vertical and horizontal lines:
Expand Down Expand Up @@ -243,6 +298,8 @@ These helpers append subpaths to the current descriptor:
| `Rect(x, y, width, height)` | Rectangular subpath |
| `Ellipse(cx, cy, rx, ry)` | Ellipse subpath (cubic Bézier approximation) |
| `Circle(cx, cy, radius)` | Circle subpath |
| `Arc(cx,cy,rx,ry,start,sweep)` | Append an elliptical arc |
| `Sector(cx,cy,rx,ry,start,sweep)` | Append a closed elliptical sector |
| `Polyline((x,y)[] points)` | Open polyline through 2+ points |
| `Polygon((x,y)[] points)` | Closed polygon through 3+ points |

Expand Down Expand Up @@ -315,10 +372,10 @@ content.

| Method | Description |
|--------|-------------|
| `Line(x1,y1, x2,y2, color, lw, opacity)` | Straight line |
| `Line(x1,y1, x2,y2, color, lw, opacity, dash?, phase)` | Solid or dashed straight line |
| `FillRect(x,y,w,h, color, opacity)` | Filled rectangle |
| `StrokeRect(x,y,w,h, color, lw, opacity)` | Stroked rectangle |
| `DrawRect(x,y,w,h, fill, stroke, lw, opacity)` | Filled + stroked rectangle |
| `StrokeRect(x,y,w,h, color, lw, opacity, dash?, phase)` | Solid or dashed stroked rectangle |
| `DrawRect(x,y,w,h, fill, stroke, lw, opacity, dash?, phase)` | Filled + solid or dashed rectangle |
| `FillRoundedRect(x,y,w,h, r, color, opacity)` | Filled rounded rectangle |
| `StrokeRoundedRect(x,y,w,h, r, color, lw, opacity)` | Stroked rounded rectangle |
| `DrawRoundedRect(x,y,w,h, r, fill, stroke, lw, opacity)` | Filled + stroked rounded rect |
Expand All @@ -329,7 +386,11 @@ content.
| `StrokeEllipse(cx,cy, rx,ry, color, lw, opacity)` | Stroked ellipse |
| `DrawEllipse(cx,cy, rx,ry, fill, stroke, lw, opacity)` | Filled + stroked ellipse |
| `Path(Action<PathDescriptor>)` | Arbitrary path with full Bézier support |
| `Text(text, x,y, color, size, family, bold, italic, opacity)` | Text label, baseline at (x, y) |
| `Image(source, x,y,w,h, fit)` | Positioned PNG/JPEG from a file, bytes, or stream |
| `FillPie(x,y,w,h,start,sweep,fill,opacity)` | Filled elliptical sector |
| `StrokePie(x,y,w,h,start,sweep,stroke,lw,opacity)` | Stroked elliptical sector |
| `DrawPie(x,y,w,h,start,sweep,fill,stroke,lw,opacity)` | Filled and stroked elliptical sector |
| `Text(text, x,y, color, size, family, bold, italic, opacity, angle)` | Rotatable text label, baseline at (x, y) |
| `MeasureTextWidth(text, size, family, bold, italic)` (static) | Advance width for aligning/centring a label |
| `Grid(cw, ch?, color, lw)` | Full-canvas rectangular grid |

Expand All @@ -346,6 +407,8 @@ content.
| `Rect(x,y,w,h)` | Append rectangular subpath |
| `Ellipse(cx,cy,rx,ry)` | Append ellipse subpath |
| `Circle(cx,cy,r)` | Append circle subpath |
| `Arc(cx,cy,rx,ry,start,sweep)` | Append elliptical arc |
| `Sector(cx,cy,rx,ry,start,sweep)` | Append closed elliptical sector |
| `Polyline(points[])` | Append open polyline (≥ 2 points) |
| `Polygon(points[])` | Append closed polygon (≥ 3 points) |
| `Fill(hexColor)` | Set fill paint |
Expand Down
7 changes: 4 additions & 3 deletions samples/TerraPDF.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
string sampleDir = args.Length > 0 ? args[0] : Path.Combine(desktop, "SamplePDF");
Directory.CreateDirectory(sampleDir);

string imgDir = AppContext.BaseDirectory; // images live next to the exe
string imgDir = AppContext.BaseDirectory; // images live next to the exe
string headerImg = Path.Combine(imgDir, "header_logo.png");
string smallImg = Path.Combine(imgDir, "small_logo.jpg");
string smallImg = Path.Combine(imgDir, "small_logo.jpg");

Console.WriteLine("TerraPDF Samples - generating seventeen PDFs to Desktop...");
Console.WriteLine("TerraPDF Samples - generating eighteen PDFs to Desktop...");
Console.WriteLine();

SimpleReport.Generate(Path.Combine(sampleDir, "01_simple_report.pdf"));
Expand All @@ -36,6 +36,7 @@
TableSpansShowcase.Generate(Path.Combine(sampleDir, "16_table_spans_showcase.pdf"));
FontSubsettingShowcase.Generate(Path.Combine(sampleDir, "17_font_subsetting_showcase.pdf"),
Path.Combine(imgDir, "Fonts", "Lato-Regular.ttf"), Path.Combine(imgDir, "Fonts", "NotoSansDevanagari-Regular.ttf"));
CanvasMediaShowcase.Generate(Path.Combine(sampleDir, "18_canvas_media_showcase.pdf"), smallImg);

Console.WriteLine();
Console.WriteLine("Done.");
45 changes: 45 additions & 0 deletions samples/TerraPDF.Sample/Samples/18_CanvasMediaShowcase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace TerraPDF.Sample.Samples;

using TerraPDF.Core;
using TerraPDF.Helpers;

internal static class CanvasMediaShowcase
{
internal static void Generate(string path, string imagePath)
{
Document.Create(document => document.Page(page =>
{
page.Size(PageSize.A4);
page.Margin(2, Unit.Centimetre);
page.DefaultTextStyle(style => style.FontSize(10));

page.Header().Text("Canvas Media Showcase")
.Bold().FontSize(20).FontColor(Color.Blue.Darken2);

page.Content().PaddingTop(16).Canvas(520, canvas =>
{
canvas.Text("Contain", 12, 18, Color.Blue.Darken2, 11, bold: true);
canvas.DrawRect(10, 28, 200, 120, Color.Grey.Lighten5, Color.Grey.Medium);
canvas.Image(imagePath, 10, 28, 200, 120, ImageFit.Contain);

canvas.Text("Cover", 242, 18, Color.Blue.Darken2, 11, bold: true);
canvas.DrawRect(240, 28, 200, 120, Color.Grey.Lighten5, Color.Grey.Medium);
canvas.Image(imagePath, 240, 28, 200, 120, ImageFit.Cover);

canvas.Line(10, 180, 440, 180, Color.Grey.Medium, 1,
dashPattern: [8, 4]);
canvas.Text("Rotated text", 80, 285, Color.Orange.Darken2, 22,
bold: true, angle: -35);

canvas.FillPie(260, 215, 150, 110, 20, 230, Color.Blue.Lighten2, 0.75);
canvas.StrokePie(260, 215, 150, 110, 20, 230, Color.Blue.Darken2, 2);
canvas.Text("Elliptical sector", 270, 350, Color.Blue.Darken2, 11);

canvas.Path(shape => shape
.Arc(125, 420, 95, 45, 195, 250)
.Stroke(Color.Green.Darken2, 3));
canvas.Text("PathDescriptor.Arc", 55, 490, Color.Green.Darken2, 11);
});
})).PublishPdf(path);
}
}
Loading
Loading