TextFit 0.1.1

dotnet add package TextFit --version 0.1.1
                    
NuGet\Install-Package TextFit -Version 0.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TextFit" Version="0.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TextFit" Version="0.1.1" />
                    
Directory.Packages.props
<PackageReference Include="TextFit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TextFit --version 0.1.1
                    
#r "nuget: TextFit, 0.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TextFit@0.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TextFit&version=0.1.1
                    
Install as a Cake Addin
#tool nuget:?package=TextFit&version=0.1.1
                    
Install as a Cake Tool

TextFit

NuGet NuGet Downloads

A small, renderer-agnostic .NET library for fitting wrapped text into a fixed-size bounding box at the largest font size that still fits.

It does three things:

  1. Wraps your text by word.
  2. Picks the largest font size whose wrapped output fits — via binary search.
  3. Returns the chosen size and per-line positions, ready to hand to your renderer.

It is not tied to any specific drawing library. You provide a tiny ITextMeasurer implementation that knows how to measure text in your font; TextFit does the layout. A SkiaSharp-backed measurer ships in the optional TextFit.Skia package.


Why

Drawing wrapped, auto-fitting text into a fixed box (PDF signature widgets, image overlays, badges, fixed-cell labels…) is annoying: you have to wrap, measure, search for a font size that fits, and then translate line indices into your renderer's coordinate system. TextFit factors out the measure-search-wrap loop, so the only thing you write is the actual drawing call.


Install

dotnet add package TextFit          # core
dotnet add package TextFit.Skia     # optional, ITextMeasurer using SkiaSharp

Quick start

using TextFit;
using TextFit.Skia;

// 1. Build a measurer once. Reuse it across many fits — typeface load is cached.
using var measurer = new SkiaTextMeasurer("/path/to/Font-Regular.ttf");

// 2. Build the fitter.
var fitter = new TextFitter(measurer);

// 3. Fit text into a 200 x 100 box.
FitResult result = fitter.Fit(
    text:      "Hello, this is some text that may wrap and shrink to fit.",
    boxWidth:  200,
    boxHeight: 100);

// 4. Render. EnumerateTopLeft gives you (text, x, y-of-line-top) tuples in
//    a top-left-origin coordinate space.
foreach (var (line, x, y) in result.EnumerateTopLeft())
{
    myCanvas.DrawText(line, x, y, fontSize: result.FontSize);
}

Drawing into a PDF (bottom-left origin, baseline-positioned)

PDF coordinates start at the bottom-left of the page (or widget) with Y increasing upward, and text APIs typically positioned by baseline. Use EnumerateBaselineBottomLeft:

var result = fitter.Fit(text, boxWidth, boxHeight);

int fontSize = (int)Math.Truncate(result.FontSize);

foreach (var (line, x, y) in result.EnumerateBaselineBottomLeft(boxHeight))
{
    pdfWidget.AddText(
        line,
        (int)Math.Round(x),
        (int)Math.Round(y),
        fontSize.ToString(CultureInfo.InvariantCulture));
}

Custom measurer (no SkiaSharp dependency)

Implement ITextMeasurer in whatever way suits your stack:

public sealed class MyMeasurer : ITextMeasurer
{
    public float MeasureWidth(string text, float fontSize)
    {
        // …however your toolkit measures text. The result must be in the same
        // units you'll later pass as the box width to TextFitter.Fit().
    }
}

Common implementations:

  • SkiaSharpTextFit.Skia.SkiaTextMeasurer (provided).
  • System.Drawing — wrap Graphics.MeasureString.
  • PdfSharp / iText / QuestPDF — use the library's font metrics API.
  • Anything with a font file — parse glyph advance widths from the OpenType hmtx table.

Options

var options = new FitOptions
{
    MinFontSize                = 6,      // smallest font the search will return
    MaxFontSize                = 48,     // largest font the search will return
    Margin                     = 4,      // -1 = auto (default: 8% of min side, floor 2)
    LineSpacingFactor          = 1.2f,   // line height = font size * this
    BinarySearchIterations     = 20,     // precision of the size search
    MaxFontSizeBoxHeightFactor = 0.5f,   // additional cap: max(12, height * value); null disables
};

var result = fitter.Fit(text, w, h, options);

How it works

                ┌─────── boxWidth ────────┐
                ┌──────────────────────────┐ ─┐
                │ ░░ margin ░░░░░░░░░░░░░░ │  │
                │ ░░ Hello, this is some   │  │
                │ ░░ text that may wrap    │  │ boxHeight
                │ ░░ and shrink to fit.    │  │
                │ ░░░░░░░░░░░░░░░░░░░░░░░░ │  │
                └──────────────────────────┘ ─┘

TextFitter.Fit binary-searches the font size in [MinFontSize, MaxFontSize]. For each candidate size it word-wraps the text using your measurer; the size is accepted when:

  • every wrapped line fits within boxWidth - 2*margin, and
  • the total stack height (lines.Count * fontSize * LineSpacingFactor) fits within boxHeight - 2*margin.

The search probes higher after an accept, lower after a reject, and returns the largest accepted size after BinarySearchIterations rounds.

Long single words. A token wider than the available width can't be broken — when this happens at the current candidate size, the candidate is rejected and the search picks a smaller font. This guarantees no mid-word truncation.

Newlines. \n and \r\n are honored as paragraph breaks. Empty paragraphs render as a blank line (preserving the gap between paragraphs).

Coordinate system cheat sheet

Method Origin Y direction Position of (X, Y) per line
EnumerateTopLeft() Top-left Y goes down Top of the line's box (add FontSize for baseline)
EnumerateBaselineBottomLeft(h) Bottom-left Y goes up Baseline of the line

Building

git clone https://github.com/batuhanoztrk/TextFit
cd TextFit
dotnet build
dotnet pack -c Release          # produces .nupkg files in artifacts/

Versioning

This project follows SemVer. It is currently 0.x — the public API may still change between minor versions.

License

MIT.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on TextFit:

Package Downloads
TextFit.Skia

SkiaSharp-based ITextMeasurer for the TextFit library.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.1 152 5/9/2026
0.1.0 128 5/7/2026