# Installation

Learn how to install and render your first HTML content.

### Installation

To install typeset.sh in your PHP project, you have two options: using the PHAR file or Composer. We generally recommend using Composer.

{% hint style="info" %}
Please note that an active subscription license is required for your project in order to install typeset.sh as described below.
{% endhint %}

#### Using phar file

1. Sign up and purchase a license.
2. Download the PHAR file from your project page.
3. Include the PHAR file in your PHP script by adding it to your code.
4. You can then access any typeset.sh functions and classes.

```php
<?php

require_once 'typeset.sh.lib.phar';
```

#### Using composer access

To add the repository to your project, run the following command in your Composer project, this will add `packages.typeset.sh` to your `composer.json` file.

```bash
composer config repositories.typesetsh composer https://packages.typeset.sh
```

Next, you need to configure Composer to authenticate using your project ID and a token. You can create multiple tokens for different developers or deployment setups if needed. To generate a new token, go to your project page and find the "Create new tokens" option.

<figure><img src="/files/DwkRQsjrjlA7y8orNnBG" alt=""><figcaption></figcaption></figure>

Add the token to your global Composer configuration using the command bellow:

```bash
composer config -g http-basic.packages.typeset.sh "{PUBLIC_ID}" "{TOKEN}"
```

{% hint style="info" %}
Please note that the project *API secret* is not the same as the *composer token*. To create composer tokens, an active subscription license is required.
{% endhint %}

Require the typesetsh library by running the following command:

```bash
composer require typesetsh/typesetsh
```

Alternatively, you can use the Symfony bundle or Laravel wrapper, which come with helper functions and services for rendering templates. To use these instead of the typesetsh library, require the Laravel wrapper or Symfony bundle instead:

```bash
composer require typesetsh/laravel-wrapper
```

```bash
composer require typesetsh/pdf-bundle
```

### Minimum requirements&#x20;

Make sure your target environment is running PHP 7.4 or higher and has the PHAR extension installed. The following extensions are also required:

* ext-curl
* ext-dom
* ext-exif
* ext-fileinfo
* ext-gd
* ext-iconv
* ext-libxml
* ext-simplexml
* ext-zlib

{% hint style="info" %}
PHP 7.4 has reached the end of its lifecycle and we strongly recommend that all users upgrade to the latest version of PHP as soon as they can. However, we understand that this may not always be feasible and will continue to offer support for PHP 7.4 in the meantime.
{% endhint %}

{% hint style="info" %}
Large PNG files can significantly slow down rendering in typeset.sh due to the slow image byte level decoding involved. To overcome this, consider installing the PHP extension [Imagick](https://www.php.net/manual/de/book.imagick.php) or opting for file formats like JPEG or PDF.
{% endhint %}

{% hint style="success" %}
The test pipeline runs on **`PHP`** **`7.4`**, **`8.0`**, **`8.1, 8.2, 8.3, 8.4 and 8.5`**
{% endhint %}


# Using typeset.sh

Before rendering your first page, take a moment to familiarize yourself with some basic usage concepts.

### Allowing external resources&#x20;

It is important to understand that typeset.sh has strict restrictions on including external resources (such as CSS, fonts, and images) by default. A `resolveUri` function must be provided to resolve the URL of each external resource. This function receives the requested URL (e.g. `./my-logo.png`) and returns the actual path to the file, or an empty string if the resource is not found or not allowed.&#x20;

To simplify this process, you can use the `\Typesetsh\UriResolver` class to define resolvers for different schemes (e.g. "http\://", "data://", "file://").

```php
$content = "Hello <strong>World</strong>";

$base = getcwd();

$cachePath = __DIR__.'/cache';
$resolveUri = \Typesetsh\UriResolver::all($cachePath, $base);

$pdf = \Typesetsh\createPdf($content, $resolveUri);
$pdf->toFile('test.pdf');
```

{% hint style="info" %}
Note that the URI resolver receives a base path and cache path.
{% endhint %}

The *base path* is used to resolve relative URIs. For example, in the above example, `./my-logo.png` would be resolved to the path of the current working directory.

The *cache path* is used for HTTP(S) or data URIs. When an external HTTP(S) resource is used, the URI resolver will attempt to download and cache the resource to prevent the need for repeated downloads. If no cache path is provided (`null`), the URI resolver will try to use the system's default temporary file system as the cache path.

The example above uses the "`all()`" preset, which allows all paths to be included. Other presets include:

```php
// Only http(s) urls are allowed, no loca files.
\Typesetsh\UriResolver::httpOnly($cachePath);

// http(s) and the current working dir of you application.
\Typesetsh\UriResolver::httpAndCurrentDir($cachePath, $base);

// Local files only within the list of given allowed directories 
\Typesetsh\UriResolver::localOnly($allowedDirectories, $base);
```

You can create your own presets to further restrict access or even implement your own `resolveUri` method. To do this, your function must have the following signature: `function(string $uri, string $base): string`

```php
$base = __DIR__.'/public_html';
$cachePath = __DIR__.'/cache';
$allowedDirectories = [
    __DIR__.'/public_html'
];

// e.g. https://example.org/test.css
$http = new \Typesetsh\UriResolver\Http($cachePath);

// e.g. data:image/png;base64,iVBORw0KGgoAA...
$data = new \Typesetsh\UriResolver\Data($cachePath);

// e.g. file:./logo.png
$file = new \Typesetsh\UriResolver\File($allowedDirectories);

$resolveUri = new \Typesetsh\UriResolver(
    [
        'file' => $file,
        'http' => $http,
        'https' => $http,
        'data' => $data,
    ],
    $base
);

$pdf = \Typesetsh\createPdf($content, $resolveUri);
$pdf->toFile('test.pdf');
```

### Using typeset.sh

Now that you understand the concept of the $resolveUri method, let's take a quick look at how to render a PDF.&#x20;

The easiest way is to use the `\Typesetsh\createPdf` method, which returns a `\Typesetsh\Result` object. This object allows you to save the PDF to a file or retrieve it as a binary string, specify the version to save the document as, get the number of pages that were created, and retrieve a list of warnings.

```php
$pdf = \Typesetsh\createPdf("Hello World", $resolveUri);

// The version of the PDF file (default 1.6)
$pdf->version = '1.6';

// [Readonly] Number of pages that have been created.
$pdf->pageCount;

// [Readonly] List of \RuntimeException that raised durring rendering.
$pdf->issues;

// Write PDF to a file
$pdf->toFile('test.pdf');

// Return PDF as string
$data = $pdf->asString();

```

To display a PDF in the browser without saving it, you can use the `asString()` method and set the appropriate headers.

```php
$data = $pdf->asString();
header('Content-Type: application/pdf');
header('Content-Length: ' . strlen($data));
header("Content-Disposition:inline;filename=hello.pdf");

echo $data;
```

### Technical Documentation

```php
function createPdf(string $html, ?callable $resolveUri = null, int $pageLimit = 100): Result
{
    $service = new HtmlToPdf();
    return $service->render($html, $resolveUri, $pageLimit);
}

```

Create PDF function is only a simple wrapper around the HtmlToPdf service.

#### HtmlToPdf Service

A service can be configured with a **save handler** to further manipulate the final PDF (see *Save Handlers* for more details).

There are two entry points available:

1. **Single Document Rendering** – Render a single PDF from a single HTML document (standard approach).
2. **Multi-Document Rendering** – Render multiple HTML documents into a single PDF file.

#### Method Parameters

All rendering methods accept the following parameters:

* **`resolverUri` callback** – A function that takes a URI string and a base URI string, returning a local file path. This allows you to manage caching, allowlists, or other path resolution logic.\
  The class **`\Typesetsh\UriResolver`** provides several useful default implementations that can be used as-is or extended for custom behavior.
* **`pageLimit`** – Defines the maximum number of pages to process. This prevents long runtimes or excessive PDF sizes caused by malformed or corrupted HTML layouts.
  * The default value is **100 pages**.
  * If your output regularly exceeds this, consider increasing the limit.

<pre class="language-php"><code class="lang-php"><strong>/**
</strong> * Render a single html document as pdf.
 *
 * @param callable(string, string|null):string|null $resolveUri
 */
<strong>public function render(string $html, ?callable $resolveUri = null, int $pageLimit = 100): Result
</strong>
<strong>/**
</strong> * Render multiple html documents at once into a single pdf document.
 *
 * @param non-empty-list&#x3C;string> $htmls
 * @param callable(string, string|null):string|null $resolveUri
 */
public function renderMultiple(array $htmls, ?callable $resolveUri = null, int $pageLimit = 100): Result

</code></pre>

For more advanced customization, including post-processing and handling logic, refer to the [**Advanced Guide to Save Handlers**](/advanced-guides/save-handlers).


# First template

All layout configurations are done using CSS.

If you are accustomed to configuring page size and other document aspects (such as fonts) using PHP with other PDF renderers, you will appreciate the ability to do so using CSS and the official Print-CSS syntax with typeset.sh.

The following example shows how to create a simple A5 page using a Google font:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My first template</title>
<style>
    @import url('https://fonts.googleapis.com/css2?family=PT+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap');
    @page {
        size: A5;
        margin: 10mm;
    }
</style>
</head>
<body>
    Hello World
</body>
</html>
```

All layout-related configurations are done using CSS inside your document. The official CSS standard includes special properties and rules for printed and paged media such as PDF.

For more information on paged media in CSS, refer to the official W3.org reference at <https://www.w3.org/TR/2018/WD-css-page-3-20181018/>.


# CSS and paged media

CSS can do more than styling websites!

Unlike websites, which are not constrained by any physical size, paged media (such as documents or books) are limited by the size of the target page (e.g. an A4 page). Fortunately, CSS (Cascading Style Sheets) [supports paged media](https://www.w3.org/TR/2018/WD-css-page-3-20181018/) and provides a range of properties and concepts that can be used to control the flow of your document on a paged document.

### @page at rule

The `@page` at-rule is used to specify the dimensions, margins, and other layout properties of a page.

```css
@page  {
    /* Some samples of different page sizes */
    size: A4 landscape;
    margin: 25mm;
    background: red;
}
```

### Page break control

`break-before` and `break-after`: These properties can be used to specify where a page break should or should never occur before or after an element.

`break-inside`: This property can be used to prevent a page break from occurring within an element.

`orphans` and `widows`: These properties can be used to specify the minimum number of lines that must appear at the top or bottom of a page before a page break is allowed.

### Page Size

The @page rule lets you configure your page. You can set things like size, margins and backgrounds.

#### Auto page height

While not considered an official standard, you have the option to set the page height to 'auto' in which case the PDF will adjust its height to accommodate the content. You can also refine the result by using the min-height and max-height properties for further customization.

```css
@page  {
    /* Auto page height */
    size: 400mm auto;
    min-height: 200mm;
    margin: 20mm;
}
```

### Page Margins

When you define a page with margins, the space between the edge of the page and the inner margin border is called the page margins. Each side of the page is split into three margin areas: left, center, and right (for horizontal margins) or top, middle, and bottom (for vertical margins). Additionally, there are four corner areas, making a total of 16 margin areas.

Each area can be access using a @rule inside the @page rule.

<pre class="language-css"><code class="lang-css">@page {
    size: A4;
    margin: 2cm;

    /* TOP 3 MARGIN AREAS */
    @top-left {
        content: 'left';
        background: yellow;
    }
    @top-right {
        content: 'right';
        background: red;
    }
    @top-center {
        content: 'center';
        background: green;
    }
    
    /* BOTTOM 3 MARGIN AREAS */
    @bottom-left {
        content: 'left';
        background: yellow;
    }
    @bottom-right {
        content: 'right';
        background: red;
    }
    @bottom-center {
        content: 'center';
        background: green;
    }
    
    /* LEFT 3 MARGIN AREAS */
    @left-top {
        content: 'top';
        background: yellow;
    }
    @left-bottom {
        content: 'bottom';
        background: red;
    }
    @left-middle {
        content: 'middle';
        background: green;
    }
    
    /* RIGHT 3 MARGIN AREAS */
    @right-top {
        content: 'top';
        background: yellow;
    }
    @right-bottom {
        content: 'bottom';
        background: red;
    }
    @right-middle {
        content: 'middle';
        background: green;
    }
    
<strong>    /* 4 CORNER MARGIN AREAS */
</strong>    @bottom-left-corner {
        content: 'bl';
        background: #587b80;
    }
    @bottom-right-corner {
        content: 'br';
        background: #587b80;
    }
    @top-left-corner {
        content: 'tl';
        background: #587b80;
    }
    @top-right-corner {
        content: 'tr';
        background: #587b80;
    }
}
</code></pre>

{% embed url="<https://typeset.sh/en/live-demo/margin-boxes>" %}

### Page Areas

Sometimes, the page margins may not provide enough flexibility for your layout needs. In these cases, you can use page areas to create additional layout spaces on the page. Page areas are essentially absolute positioned areas on the page, but they are rendered like the margin areas in the context of the page.

```css
@page {
    size: 200px 300px;
    margin: 50px 10px 30px 10px;

    @area foobar {
        content: 'Position me like asboulte elements';
        text-align: center;
        left: 10mm;
        right: 10mm
        bottom: 10mm;
        height: 40mm;
    }
}
```

### Header and Footers with running elements

When playing around with [#page-margins](#page-margins "mention") and [#page-areas](#page-areas "mention"), you will quickly realize that placing simple content text is not enough. How about adding actual HTML content in your page margins that repeat for each page. This is done using running elements.

Running elements are basically HTML elements inside your document that are moved out of the content flow into the page context.&#x20;

This involves two steps, you first need to define which element should be running, this is done by using the `position` property and the function `running(<identifier>)`. The identifier can be any valid identifier  and will be used as id.

```html
<body>
    <div id="header">
        Any header content can go here
    </div>
```

```css

#header {
    /* Position element as running with the id "my-header" */
    position: running(my-header);
}
```

The second step is to tell the margin area to use a running element as content using the `content` property and `element(<identifier>)` function with the same identifier ID.

```css
@page {
    size: A4;
    margin: 5cm 2cm 2cm 2cm;
    
    @top-center {
        content: element(my-header);
    }
}
```

That's it, now you the element `@header` gets repeated on each page. Of cause you can also have another element for other page elements such as footer.

{% hint style="warning" %}
If you plan on adding a footer on each page, make sure the footer element is at the beginning of your document flow not at the end.
{% endhint %}


# Page counters

Print-CSS defines two special counters, `page` and `pages`, which can be used to display the current page number and the total number of pages, respectively. For example, the following code will display the current and total page numbers at the bottom right of each page:

```css
@page {
    size: A4;
    margin: 20mm;

    @bottom-right {
        content: 'Page: ' counter(page) ' of ' counter(pages);
        font-size: 0.7em;
    }
}
```

{% embed url="<https://typeset.sh/en/live-demo/page-counter>" %}

### Manipulating the page counter

The `page` and `pages` counters can both be manipulated, but only within the `@page` at-rule context. For instance, in the following example, the first page is a cover page, so it is not included in the page numbering. To achieve this, we reset the `page` and `pages` counters to -1 so that they will start counting from the beginning on the next page:

```css
@page:first {
    size: A4;
    counter-reset: page -1 pages -1;
    @bottom-right {
        content: none;
    }
}
```

Keep in mind that these counters can only be manipulated within the `@page` at-rule context.

### Target page counter

You can use the `target-counter` function to retrieve the page number of an element and display it as part of your content. A common use case for this is creating a table of contents (TOC).

Here is an example of how you can use the `target-counter` function to retrieve the page number for the element that a link (specified by the `href` attribute) points to:

```css
#toc a::after {
    content: ' (' target-counter(attr(href url), page) ')';
}
```

{% code title="HTML table of content code" %}

```html
<ol id="toc">
    <li><a href="#topic-1">Lorem ipsum dolor</a></li>
    <li><a href="#topic2-">Stet clita kasd</a></li>
</ol>
```

{% endcode %}

<figure><img src="/files/sdg24jmb8HT3CiWrKyff" alt=""><figcaption></figcaption></figure>

{% embed url="<https://typeset.sh/en/live-demo/toc>" %}


# Page selectors

In CSS, the @page rule allows you to specify styles for individual pages or groups of pages in a document. You can use various page selectors to select specific pages or groups of pages to apply your styles to.

Some common page selectors include:

* `:first`: Selects the first page of a document.
* `:left` and `:right`: Select pages that appear on the left or right side of a spread in a printed document.
* `:blank`: Selects pages that do not hold any content (due to left, right insertion break)
* `:nth(x of y)`: Selects every xth page of a document, where x is a positive integer.
* Additionally named pages are also possible.

### First page

Selector for the first page pf a document. Useful for cover-pages etc.

```css
@page {
    size: A4;
    margin: 1cm;
}
@page:first {
    background: red;
}
```

### Left and right selector

In a book-like document, printed documents can have left and right pages. You can specify different layouts for each type using the `:left` and `:right` selector. A common example is to display the current page number on the outside or inside of the book.

```css
@page:right {
    @bottom-right {
        content: 'Page: ' counter(page);
    }
}
@page:left {
    @bottom-left {
        content: 'Page: ' counter(page);
    }
}
```

### Blank pages

If a force page break (e.g. `break-before: left;`) is present in the document flow, a blank page must be inserted if the current page is already a left page to ensure that the element begins on a new left page. The blank page selector can be used to add a custom design for these pages.

```css
@page:blank {
    background: lightgrey;
}
```

{% embed url="<https://typeset.sh/en/live-demo/blank-page-selector>" %}

### Nth selector

Select a page by its number or every N pages.

* `@page:nth(1)` Select the first page of a document
* `@page:nth(2)` Select the second page of a document
* `@page:nth(2n)` Select all even document pages

### Named pages

The document flow can also influence the page layout. For instance, it is possible to specify that all tables should be printed on pages with a landscape orientation. This can be achieved by using named pages.

```css
@page wide-table {
    size: A4 landscape;
}

/* Print any table with the class 'wide' on a landscape orientated page */
table.wide {
    page: wide-table;
}
```


# Page groups

Using named pages and page selectors you can create so called page groups.&#x20;

If you have a document with multiple chapters that span several pages and you want to select only the first page of each chapter, you can do so by enclosing each chapter in its own element and forcing a named page (e.g. chapter). You can then use the `nth()` selector to select the first page of all chapters.

```css
@page :nth(1 of chapter) {
    background: lightgrey;
}

section.chapter {
    page: chapter;
}
```

{% embed url="<https://typeset.sh/en/live-demo/page-groups>" %}

See <https://www.w3.org/TR/css-gcpm-3/#document-sequence-selectors>


# Running elements

Create custom header and footer for your pages.

When working with [CSS and paged media](/setup/css-and-paged-media#page-margins) and [CSS and paged media](/setup/css-and-paged-media#page-areas), you may want to add more complex content, such as repeating HTML elements, to your layout. This can be achieved using running elements.&#x20;

Running elements are HTML elements that are moved out of the content flow and into the page context.

To create a running element, you will need to perform two steps:

First, define which element should be a running element. This is done by using the `position` property and the `running()` function. The `running()` function takes an identifier as an argument, which can be any valid identifier and will be used as the element's `id`.

```html
<body>
    <div id="header">
        Any header content can go here
    </div>
```

```css

#header {
    /* Position element as running with the id "my-header" */
    position: running(my-header);
}
```

Then place a running element in a page margin or page area, you will need to use the `content` property and the `element()` function. The `element()` function takes an identifier as an argument, which should be the same identifier used when defining the running element.

```css
@page {
    size: A4;
    margin: 5cm 2cm 2cm 2cm;
    
    @top-center {
        content: element(my-header);
    }
}
```

That's all there is to it! Now, the element with the `header` identifier will be repeated on each page. You can also create additional running elements for other page elements, such as a footer, using the same process.

{% hint style="warning" %}
If you plan on adding a footer element to each page of your paged media using running elements, it is important to make sure that the footer element appears at the beginning of your document flow, rather than at the end. This is because running elements are moved out of the normal content flow and into the page context, and if the content is not present at page generation time, it will not be displayed.
{% endhint %}


# Bleed area

In printing, the bleed area is the portion of the printed document that extends beyond the trim edge. It is added to the document to ensure that there are no white margins or unfinished edges when the document is trimmed to its final size.

When a document has a bleed, it means that the ink or color extends beyond the trim edge of the page and into the bleed area. This allows the printer to trim the document to its final size without leaving any unprinted areas on the edges.

The bleed can be added using the bleed property inside a `@page` at-rule.

```css
@page {
    size: A4;
    margin: 10mm;
    bleed: 10mm;
    background: red;
}
```

####

### Marks

```css
@page {
    size: A4;
    margin: 10mm;
    bleed: 10mm;
    /* Add one or more marks */
    marks: cross crop colors;
}
```

#### Crop Marks

Crop marks, also known as trim marks, are lines printed on a document that indicate where the document should be trimmed.&#x20;

They are usually located outside the trim area (inside the bleed area) and are used as a guide for cutting the document to its final size. Crop marks are typically placed at the corners of the document and at any other points where the document should be trimmed. They are usually thin lines that are printed in a color that is different from the rest of the document, such as black or blue. When a document is printed with crop marks, it is typically printed on a larger sheet of paper and then trimmed down to its final size using the crop marks as a guide.

<figure><img src="/files/CaTJG4pazKI0w16B3wSs" alt=""><figcaption></figcaption></figure>

{% embed url="<https://typeset.sh/en/live-demo/bleed>" %}

####


# Convert from URL

Typeset.sh is unable to convert an URL into HTML, but it can be easily accomplished using PHP. When using relative paths in your HTML, ensure to set the appropriate base path for resolving those URLs.

```php
// Make sure this is a trusted path!
$url = 'https://typeset.sh/samples/invoice.html';
$base = dirname($url);
$urlResolver = \Typesetsh\UriResolver::all(null, $base, [__DIR__.'/public']);

// Alternativly you could use \Typesetsh\Resource\Cache class as well for this.
$html = file_get_contents($url);

$result = \Typesetsh\createPdf($html, $urlResolver);
$result->toFile(__DIR__.'/invoice.pdf');
```


# Document Metadata

In addition to defining the document title using the element, you can also specify the author, keywords, and description of the document using the meta HTML elements.

```html
<head>
    <title>Hello World</title>    
    <meta name="author" content="John">
    <meta name="description" content="typeset.sh is great">
    <meta name="keywords" content="pdf php typeset.sh">
    <meta name="view.fitWindow" content="yes">
    <meta name="view.displayTitle" content="false">
</head>
```


# Changelog

### 0.27.7

2026-06-04

#### Fixed

* Reset garbage collector state on unhappy path.

### 0.27.6

2026-05-12

#### Fixed

* Text decoration on justified text not aligned properly.

### 0.27.5

2026-03-26

#### Fixed

* Fixed border-width keyword not working as intended.

### 0.27.4

2026-03-09

#### Fixed

* Fixed issue in Open Font Layout engine for the Mark Glyph Sets table.

### 0.27.3

2026-02-10

#### Fixed

* Fixed Arabic shaping engine.

### 0.27.2

2026-01-20

#### Added

* Support PHP 8.5
* Added Date/DateTime Form fields

#### Fixed

* Fixed text-align-last on forced line breaks

### 0.27.1

2025-06-01

#### Fixed

* Use intl ext for thai line breaking

### 0.27.0

2025-05-29

#### Added

* Support for hyphenation for Thai.

**Breaking Changes**

If you use Thai script, check your results. Consider enabling [hyphenation](https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens) if you have long text.&#x20;

### 0.26.28

2025-05-13

#### Added

* Support for Thai Script.

### 0.26.27

2025-04-28

#### Fixed

* Fix UTF16-String encoding issue that could cause corrupted PDF files .

### 0.26.26

2025-03-20

#### Fixed

* Fix xmp record for ZUGFeRD&#x20;

### 0.26.24

2025-03-05

#### Fixed

* Certain Glyph IDs may be encoded incorrectly in specific use cases, resulting in disappearing text.&#x20;

### 0.26.23

2025-02-21

#### Fixed

* Support fallback font for Thai script.
* Fix deprecation warnings in php84.

### 0.26.22

2024-09-17

#### Fixed

* Collapse margin dimension failed on some edge cases.

### 0.26.21

2024-09-12

#### Fixed

* Collapse margin dimension failed on some edge cases.

### 0.26.20

2024-09-06

#### Fixed

* Table Layout - Work around for [PHP/OPcache](https://github.com/php/php-src/issues/15773)[ issue](https://github.com/php/php-src/issues/15773).

### 0.26.18

2024-09-03

#### Fixed

* Table Layout - Updated Auto Column Distribution Algorithm.&#x20;
* Fix bonding box issue preventing InDesign import.

### 0.26.7

2024-03-18

#### Fixed

* SVG - Don't fail on missing offset entry on \<stop> element.
* CSS - Fix a number of minor parsing errors when working with invalid css.

### 0.26.6

2024-03-15

#### Fixed

* PDF - Incorrect Size value for cross reference streams.

### 0.26.5

2024-03-14

#### Fixed

* CSS - Allow usage of first, start and last for multiple running elements.

### 0.26.4

2024-03-13

#### Fixed

* PDF - Add missing mark info to catalog dictionary for accessibility validation.

### 0.26.3

2024-03-07

#### Fixed

* SVG - Rending multiple text elements is super slow
* SVG - Text vertical alignment is not quite correct

### 0.26.2

2024-02-28

#### Fixed

* CSS - Text align with no value crashes rendering

### 0.26.0

2024-02-19&#x20;

#### Added

* SVG Support for text and tspan elements

#### Fixed

* SVG - Stroke Width of 1px should be default  :warning:
* SVG - Render anchor groups

### 0.25.0

2024-01-30

#### Added

* New save handler for ZUGFeRD XML attachment
* Basic support for object-fit and object-position

#### Fixed

* Incorrect ISO un/coated ICC profiles names
* Use Coated FOGRA51 as default for X4 :warning:
* Possible div-by-zero issue in table layout
* Percentage height not computed correctly for flex items

### 0.24.10

2023-08-09

#### Fixed

* Allow color-space --ts-seperate in SVG context.

### 0.24.9

2023-05-22

#### Fixed

* Form checkbox/radio remove widget border.
* CSS `rgb()` func allow without comma and add alpha support `rgb(r g b / a)`.

### 0.24.8

2023-05-20

#### Fixed

* Do not escape binary data when using STDOUT in the phar file.

### 0.24.7

2023-05-17

#### Added

* CSS property to controll the layout of radio and checkboxes.

  <pre class="language-css" data-full-width="false"><code class="lang-css">-typesetsh-button-style: checkbox|radio;
  </code></pre>
* Support PDF JavaScript. (WIP)

### 0.24.6

2023-05-15

#### Fixed

* Log and propagate exceptions from computing CSS properties.

### 0.24.5

2023-05-12

#### Fixed

* Flex Layout - Avoid inside control not respected. :warning:
* Flex Layout - Not all items rendered between pages breaks in some scenarios.

#### Added

* Support for signature field. (\<input type="signature" />)

### 0.24.4

2023-04-24

#### Fixed

* Checkbox always checked by default.

#### Added

* Support for signature field. (\<input type="signature" />)

### 0.24.3

2023-04-17

#### Fixed

* Radio buttons not working correctly.
* Form field values not proper encoded to support UTF8.

### 0.24.2

2023-04-14

#### Added

* Support for select and data-list form fields.

### 0.24.1

2023-04-07

#### Fixed

* Table Layout - RTL column sorting for header and footer rows.

### 0.24.0    &#x20;

*2023-04-06*

#### Breaking Changes

* Check your tables if you use RTL writing mode.

#### Fixed

* Table Layout - Column sorting in right to left writing mode was wrong.

### 0.23.12&#x20;

*2023-02-23*

#### Added

* CSS - Parsing support for :host selector that caused issues with font-awesome.
* CSS - Support additional CSS page sizes.

### 0.23.11

*2023-02-13*

#### Fixed

* Inline - Line-breaks inside nested inline element

### 0.23.10

*2023-02-11*

#### Fixed

* OpenType - Pair adjustment format 2 reader.
* Snft - Recursive read composite glyph.

### 0.23.9&#x20;

*2023-02-09*

#### Fixed

* SVG - Zero size cause division by zero crash.
* SVG - CSS dimensions for size caused error.

### 0.23.8

*2023-01-31*

#### Fixed

* PHP 7.4 issue.
* Fix selector specificity for attribute and pseudo classes.
* Add tokenizer packing and unpacking functions.

#### Added

* CSS custom variables support.
* :root CSS selector.

### 0.23.7

*2023-01-24*

#### Fixed

* Kerning and xAdvanced adjustment issue

### 0.23.6

*2023-01-19*

#### Fixed

* Text-decoration missing marked content

### 0.23.5

*2023-01-17*

#### Fixed

* Auto assign form name if none is set.
* Tailing offset issue on glyph renderer.
* OS2MetricsWriter version type.

### 0.23.4

*2023-01-03*

#### Added

* Support font variant CSS properties

### 0.23.3

*2022-12-22*

#### Fixed

* File scheme uri resolver.

#### Added

* Add page iteration and fix page() method.

### 0.23.2

*2022-12-21*

#### Fixed

* Possible endless loop on malformed tables.

#### Added

* PdfMerger helper class.

### 0.23.1

*2023-12-15*

#### Fixed

* Font selector issue when using phar.

### 0.23.0

*2022-12-15*

#### Changed

* Extract CJK Noto font to seprate package so it can be repaced.

#### Added

* Support for profile-color space.
* Support for lab color space.

### 0.22.5

*2022-12-11*

#### Added

* Support for color -ts-separation
* Allow modifying page and pages counter in at-page rule

### 0.22.4

*2022-11-30*

#### Added

* CFF - Support Koren by default

#### Fixed

* CFF - Charset list may be string or int due to array-key

### 0.22.3

*2022-11-22*

#### Fixed

* Cleanup FormattingContext

#### Changed

* SVG - Refactor parser
* SVG - Refactor rendering
* SVG use static cache for linear gradient sample functions

#### Added

* Allow PDF import page number (`src="..pdf#2"`)
* Allow nested baseline shif

### 0.22.2

*2022-10-27*

#### Fixed

* Layout Table run prepare cycle
* &#x20;twice

### 0.22.1

*2022-10-23*

#### Fixed

* Merge conflict debugger output


# QR codes

A QR code (Quick Response code) is a two-dimensional barcode that can be scanned using a smartphone camera or QR code reader. QR codes are similar to traditional barcodes, but they can store much more information and can be scanned much more quickly.

They are often used to store website URLs, contact information, or other types of data that can be easily accessed by scanning the code.&#x20;

Codes can be placed on business cards, brochures, advertisements, or other marketing materials. They can also be used in other ways such as for mobile payments, ticketing, and much more. They can also be used to track inventory and assets in industries such as manufacturing and logistics.

CSS and HTML do not include built-in support for QR codes, therefore, it must be obtained from a separate package that needs to be imported specifically if you wish to use it.

To install it, use composer and require `typesetsh/qr-code-element`.

```
composer require typesetsh/qr-code-element
```

After installation, you can include the `qr-code` element in your HTML. This element supports the `alt` attribute as well as the `icon-src` and `icon-size` attributes. These can be used to place and adjust the size of an icon in the center of your QR code. The size is represented as an integer, where 1 equals one block size (a single block-bit, so to speak). The content within the `qr-code` element represents the data that will be encoded in the QR code.

```html
<qr-code icon-src="icon.png" icon-size="3">DATE_GOES-HERE</qr-code>
```

{% hint style="warning" %}
Please be aware that white spaces are not automatically removed. If you are adding multi-line content for encoding, make sure to avoid using tabs or white spaces for code indentation, as these will also be encoded in the QR code.
{% endhint %}

{% embed url="<https://typeset.sh/en/live-demo/qr-code>" %}

### Attributes

| Attribute                   | Default | Description                                                                                                         |
| --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| data-error-correction-level | Q       | <p>Possible values: <br>L: \~7% correction<br>M: \~15% correction<br>Q: \~25% correction<br>H: \~30% correction</p> |
| data-encoding               | UTF-8   | Valid encoding, default UTF-8                                                                                       |
| icon-src                    |         | Any image url                                                                                                       |
| icon-size                   | 0       | The size of the icon in bit unit. 1 equals the size of 1 square.                                                    |


# Save handlers

Manipulate your PDF documents after they have been rendered.

*Save handlers* are powerful tools that allow you to make additional changes to a PDF document after the layout process is complete, but before it is saved. *Save handlers* are simple callable functions that can be easily implemented, they require the following signature:

```php
callable(\Typesetsh\Pdf\Document $document): void
```

To use a *save handler*, it must be registered at `Typesetsh\HtmlToPdf::$saveHandler` with an arbitrary key, along with the save handler.&#x20;

When the document is being saved, each registered handler will be called in the order in which they were registered, passing in the PDF document as a parameter. The following example demonstrates how to initialize an HTML to PDF service and register two *save handlers*.

```php
$html = 'Hello <strong>World</string>!';

$service = new \Typesetsh\HtmlToPdf();
$service->saveHandler['pdf_a'] = new \Typesetsh\HtmlToPdf\A_1B_Web();
$service->saveHandler['producer'] = function(\Typesetsh\Pdf\Document $document): void {
    $document->Info->Producer = 'My awesome application';
};

$resolveUri = \Typesetsh\UriResolver::all();

$result = $service->render($html, $resolveUri);
$result->toFile(__DIR__.'/hello.pdf');

```

Typeset.sh comes with a variety of predefined save handlers that offer additional functionality for manipulating your PDF documents. These save handlers are thoroughly described in their corresponding documentation pages, providing detailed explanations on how to use them and their specific capabilities.


# Error handling

Catch them all!

Typeset.sh attempts to render your document even when errors occur, such as incorrect use of CSS properties or unsupported features. These errors are caught and stored within the result object for further handling, e.g. logging.

Similar, the standard UriResolver also catches any exceptions, such as file not found or access denied, and stores them in the UriResolver instance.

However, some errors may still prevent the document from rendering, in which case a try-catch block can be utilized. The example provided demonstrates how to dump all errors as additional headers.

```php
try {
    $html = <<<HTML
        <p>Hello,</p>
        <p>This is an simple example.</p>
    HTML;
    
    $resolveUri = \Typesetsh\UriResolver::all();
    $result = \Typesetsh\createPdf($html, $resolveUri);

    $data = $result->asString();
    header('Content-Type: application/pdf');
    header('Content-Length: ' . strlen($data));
    header("Content-Disposition:inline;filename=hello.pdf");
    
    /* Merge PDF errors and resolver errors */
    foreach ([...$result->issues, ...$resolveUri->errors] as $issue) {
        header("X-PDF-Warning: ".$issue->getMessage());
    }

    echo $data;

} catch (Exception $exception) {
    // Snap!
    http_response_code(500);
    echo "Error!";
}

```


# Signing a PDF

Signing PDFs digitally can be easily accomplished by providing a certificate and, if necessary, an accompanying private key.

You can easily create a self-signed certificate for testing.

```
openssl req -x509 -nodes -days 365000 -newkey rsa:2048 -keyout my-certificate.crt -out my-certificate.crt
```

Then all you need to do, is adding the signature to your HtmlToPdf service as [Save handlers](/advanced-guides/save-handlers).

```php
<?php
$html = "Hello World!";
$cert = 'file://'.__DIR__.'/my-certificate.crt';

$signature = new \Typesetsh\HtmlToPdf\Signature($cert);
$signature->ContactInfo = 'contact@typeset.sh';
$signature->Location = 'DE';
$signature->Name = 'FooBar';
$signature->Reason = 'Testing';


$service = new \Typesetsh\HtmlToPdf();
$service->saveHandler['signature'] = $signature;
$service->saveHandler['pdf_a'] = new \Typesetsh\HtmlToPdf\A_1B_Web();

$result = $service->render($html, \Typesetsh\UriResolver::all());
$result->toFile(__DIR__.'/hello.signed.pdf');
```


# PDF Standards

PDFs can be used for various purposes, such as web display, printing, or archiving. Over time, different standards have been developed for these use-cases. Typeset.sh enables you to save your PDF as different standards by properly tagging it. However, it is your responsibility to ensure that the PDF does not include any features that are not allowed for the chosen standard.


# ZUGFeRD

Zentraler User Guide des Forums elektronische Rechnung Deutschland

ZUGFeRD, is a standardized format for electronic invoices. Developed collaboratively by businesses and government entities, ZUGFeRD aims to facilitate seamless and efficient invoicing processes. This standard combines both a human-readable PDF document and structured XML data within a single file, allowing for easy interpretation by both humans and automated systems. ZUGFeRD promotes interoperability and simplifies the exchange of invoices across diverse business systems, fostering a more streamlined and cost-effective invoicing ecosystem. As a widely accepted standard, ZUGFeRD enhances transparency, reduces errors, and contributes to the digital transformation of invoicing practices in Germany and beyond.

While Typeset.sh itself does not offer XML generation functionality, there are alternative packages available to fulfill this need. One such example is <https://github.com/easybill/zugferd-php>.\
\
To attach your XML to the PDF file, setup a save handler designed for this purpose. Refer to the example below for details:

```php
$html = "...";
$xml = "...";

$service = new \Typesetsh\HtmlToPdf();
$service->saveHandler['ZUGFeRD'] = new \Typesetsh\HtmlToPdf\ZUGFeRD($xml);

$result = $service->render($html, \Typesetsh\UriResolver::all());
$result->toFile(__DIR__.'/invoice.pdf');
```

Additional parameters are available if required:

```php
\Typesetsh\HtmlToPdf\ZUGFeRD(
    string $xml,
    string $name = 'factur-x.xml',
    string $description = 'Factur-X/ZUGFeRD Invoice',
    string $type = 'INVOICE',
    string $conformanceLevel = 'BASIC',
    Pdf\Date $modTime = null,
    string $version = '1.0',
)
```


# PDF/A

PDF/A-1B (ISO 32000-1)

PDF/A is an ISO standard specifically designed for long-term archiving. Because of this, certain features are not supported and must be avoided. For example, the use of color-alpha values or opacity properties is not allowed as transparency is not supported in PDF/A.

Currently we only have a save handler for the PDF/A-1A/B standard.&#x20;

<pre class="language-php"><code class="lang-php">&#x3C;?php
$html = "Hello World, I am a pdf/a-1b conform pdf!";

$service = new \Typesetsh\HtmlToPdf();
<strong>//$service->saveHandler['pdf_a'] = new \Typesetsh\HtmlToPdf\A_1A_Web();
</strong>$service->saveHandler['pdf_a'] = new \Typesetsh\HtmlToPdf\A_1B_Web();

$result = $service->render($html, \Typesetsh\UriResolver::all());
$result->toFile(__DIR__.'/hello.pdf');
</code></pre>


# PDF/UA

PDF/Universal Accessibility (ISO 14289)

This standard ensures accessibility for people with disabilities who use assistive technology like screen reader software.

In order to make your PDF accessibility friendly, you must follow some standards as you would do when producing accessibility friendly HTML documents.

A good starting point for that are the [accessibility guidelines](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML) provided by mozilla.org.

We highly recommend running your PDF using a tool like [Adobe Acrobat](https://acrobat.adobe.com/de/de/acrobat/pricing.html) or [PDF Accessibility Checker (Free)](https://www.access-for-all.ch/ch/pdf-werkstatt/pdf-accessibility-checker-pac.html) to verify your PDFs are fine.

Also check out the [Web Accessibility Evaluation Tools List](https://www.w3.org/WAI/ER/tools/) from W3C to verify your HTML.

Feel free to get in touch with us if you are having trouble with your PDF, we are happy to help.

#### Add meta tag to identify your document to support PDF/UA standard.

By default, Typeset.sh does not automatically add the XMP identification that marks your PDF as user agent-friendly, even if it is. This is because it requires manual effort from the author to validate the PDF and ensure everything is in order. By including the following meta tag in the head of your document, Typeset.sh will add the appropriate XMP identifier.

```html
<meta name="WCAG" content="2.1">
```

Then make sure to save the PDF as [PDF/A](/advanced-guides/pdf-standards/pdf-a).

```php
<?php
$html = <<<HTML
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="WCAG" content="2.1">
        <title>Hello World</title>
    </head>
    <body>
        Hello World, I am a <strong>UA conform</strong> pdf!
    </body>
</html>
HTML;

$service = new \Typesetsh\HtmlToPdf();
$service->saveHandler['pdf_a'] = new \Typesetsh\HtmlToPdf\A_1B_Web();

$result = $service->render($html, \Typesetsh\UriResolver::all());
$result->toFile(__DIR__.'/ua-tagged.pdf');
```


# PDF/X-4

Create print ready documents

When working with printers, they typically require PDFs to be PDF/X-4 compliant. This can be achieved using the save-handler `\Typesetsh\HtmlToPdf\X4`.&#x20;

However, there are certain considerations to keep in mind when creating documents for print. All colors must be defined in CMYK, and Typeset.sh does not automatically convert colors. Therefore, it is important to ensure that colors are defined correctly in your document and CSS.

```css
html, body {
   color: cmyk(0 0 0 100%);
}
```

In PHP you can then simply use the save-handler to create X4 conform PDF documents.

```php
<?php
$html = "Hello World, I am a pdf/x-4 conform pdf!";

$service = new \Typesetsh\HtmlToPdf();
$service->saveHandler['pdf_x4'] = new \Typesetsh\HtmlToPdf\X4();

$result = $service->render($html, \Typesetsh\UriResolver::all());
$result->toFile(__DIR__.'/hello.x4.pdf');
```

### Advanced configuration

When generating a PDF, Typeset.sh keeps track of the images included in the document and their dimensions. The images are saved into the PDF only at the final step of saving the document. This allows a save-handler to alter the images before they are embedded in the PDF.&#x20;

The X4 save-handler uses the `\Typesetsh\HtmlToPdf\ImagePreflight` save-handler, which allows for resizing images based on DPI configuration and converting RGB images to CMYK. However, this process requires the [Imagick PHP extension](https://www.php.net/manual/de/book.imagick.php). Additionally, a cache path can be provided to ensure that image resizing and conversion only occurs once.

```php
<?php
$html = "Hello World, I am a pdf/x-4 conform pdf!";

$service = new \Typesetsh\HtmlToPdf();
$service->saveHandler['pdf_x4'] = new \Typesetsh\HtmlToPdf\X4(
    outputIntent: new \Typesetsh\Pdf\OutputIntent\PdfX\ECI\PSO_Coated_v3(),
    preflight: true,
    dpi: 350,
    cachePath: __DIR__.'/cache/'
);

$result = $service->render($html, \Typesetsh\UriResolver::all());
$result->toFile(__DIR__.'/hello.x4.pdf');
```


# Color profiles

Separation / DeviceN color support

You can create custom color profiles with your own components and their corresponding CMYK values for mapping. However, keep in mind that the official CSS specification for this feature is currently in draft form and may change in the future.

In the example below, we demonstrate how to create a 5-component color space by adding an additional ink to the traditional CMYK color profile. We then use the `color()` function to reference to it.

```css
@color-profile --cmykb {
    src: device-cmyk;
    components: "Cyan" 1 0 0 0,
                "Magenta" 0 1 0 0,
                "Yellow" 0 0 1 0,
                "Black" 0 0 0 1,
                "PANTONE Reflex Blue C" 1 0.723 0 0.02;
}

h1 {
    /* Provide a value for all 5 components */
    color: color(--cmykb { 0 0 0 0 1);
}
h2 {
    color: color(--cmykb { 25% 0 10% 0 80%);
}

```

Bellow, we create a duotone colorspace using a CMYK mapping. We can then use this colorspace to create a gradient transitioning from one color to the other.

```css
@color-profile --duotone {
    src: device-cmyk;
    components: "PANTONE Reflex Blue C" 1 0.723 0 0.02,
                "PANTONE Warm Red C" 0 0.75 0.9 0;
}

#gradient {
    background: linear-gradient(to right, color(--duotone 1 0), color(--duotone 0 1));
    height: 1cm;
}
```

<figure><img src="/files/6V8pod3ExsH05bdU1xW9" alt=""><figcaption></figcaption></figure>

{% embed url="<https://typeset.sh/en/live-demo/color-profile>" %}

You can also reference to an ICC profile using the `src` property.

```css
@color-profile --fogra52 {
    src: url('https://www.color.org/registry/profiles/PSOuncoated_v3_FOGRA52.icc');
}

h1 {
    color: color(--fogra52 0 100% 0 0);
}
```


# JavaScript

This is an experimental feature and script tags may change in the future. However, please give it a try and feel free to provide feedback.

PDF supports JavaScript for various purposes, such as form validation. However, please note that typeset.sh itself does not parse JavaScript like a browser would. Instead, scripts are attached to the PDF and executed by the client's PDF viewer application.

Not all viewer support all futures and events.

To include `<script>` tags, the `data-pdf` attribute must be present. This precaution is taken to avoid including any scripts by default.

PDF supports document event handlers that can be subscribed to by adding the attribute `data-on="EventType"`. The following event types are available:

```
WillClose
WillSave
DidSave
WillPrint
DidPrint
```

Ohter ineraction events (onclick, onmousedown, etc.) only work on form fields.

#### Simple JavaScript Example

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>javascript.html</title>
    <style>
        @page {
            size: A4;
            margin: 10mm;
        }
    </style>
    <script type="text/javascript" data-pdf>
        var counter = 0;
        function test(event) {
            app.alert((++counter) + " Hello from "+app.viewerType+" by clicking "+event.target.name);
        }

        function validate() {
            app.alert("Email: " + this.getField("email").value);
        }
    </script>

    <script type="text/javascript" data-pdf data-on="didSave">
        app.alert("Saving complete");
    </script>

</head>
<body>
    <div>
        <input type="checkbox" name="foo" value="1" onclick="test(event)" />

        <input type="radio" name="bar" value="1" onclick="test(event)" checked />
        <input type="radio" name="bar" value="2" onclick="test(event)" />

        <input type="text" 
               name="email" 
               onmouseenter="console.println('email onmouseenter')"
               onmousedown="console.println('email onmousedown')"
               onmouseleave="console.println('email onmouseleave')"
               onmouseover="console.println('email onmouseover')"
               onmouseup="console.println('email onmouseup')"
               onfocus="console.println('email onfocus')"
               onblur="console.println('email onblur')"
               onkeystroke="console.println('email onkeystroke')"
               onvalidate="console.println('email onvalidate')"
               oncalculate="console.println('email oncalculate')"
               value="contact@typeset.sh" />
    </div>
    <div>
        <button onclick="validate()">Validate</button>
    </div>
</body>
</html>

```

More information can be found here: <https://opensource.adobe.com/dc-acrobat-sdk-docs/acrobatsdk/pdfs/acrobatsdk_jsapiref.pdf>


# Common Object/Classes

* [createPdf function](/advanced-guides/common-object-classes/createpdf-function)
* [HtmlToPdf](/advanced-guides/common-object-classes/htmltopdf)
* [Result Object](/advanced-guides/common-object-classes/result-object)


# createPdf function

Simple function that lets you easily convert a HTML string to a PDF.

```php
\Typesetsh\createPdf($html, \Typesetsh\UriResolver::all())->toFile('test.pdf');
```


# HtmlToPdf

The entry point when creating a PDF from a HTML file. The [createPdf function](/advanced-guides/common-object-classes/createpdf-function) does the same by initiating this class and invoke it. By initiating your own service class you have more control over the output using [Save handlers](/advanced-guides/save-handlers)


# Result Object

The result object is a simple class that gets returned by the [createPdf function](/advanced-guides/common-object-classes/createpdf-function) or the [HtmlToPdf](/advanced-guides/common-object-classes/htmltopdf)service class.

```php
$pdf = \Typesetsh\createPdf("Hello World", $resolveUri);

// The version of the PDF file (default 1.6)
$pdf->version = '1.6';

// [Readonly] Number of pages that have been created.
$pdf->pageCount;

// [Readonly] List of \RuntimeException that raised durring rendering.
$pdf->issues;

// Write PDF to a file
$pdf->toFile('test.pdf');

// Return PDF as string
$data = $pdf->asString();

```


