Skip to content

speedata/boxesandglue

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go reference documentation Fund the development Homepage

Boxes and Glue

This is a PDF typesetting library/backend in the spirit of TeX's algorithms. TeX is a typesetting system which is well known for its superb output quality.

TeX packs each unit (glyph, image, heading, ...) in a rectangular box which can be packed into other rectangular boxes. A variable length called “glue” can be between each of these rectangles. This is why this repository is called “boxes and glue”.

Features

  • High speed (“Ludicrous mode”). A simple document is created within 10ms on a macBook. This includes loading an OpenType font, typesetting text and writing the PDF file.
  • High output quality. Boxes and glue uses TeX's line breaking algorithm to create the optimal line breaks.
  • Extensibility: See the API section below. Boxes and glue is split into a high level frontend and a low level backend to provide the API you need.
  • OpenType features and font shaping with harfbuzz. Harfbuzz is well known for its awesome language support.

API

The API has two layers, a high level frontend and a low level backend. Each layer is useful when using the library. I suggest to start using the high level API and switch to the backend when you need more control over the typesetting output.

bagstructure

Frontend

The frontend has high level methods to create a PDF document, load fonts, insert text which can be broken into lines and output objects at exact positions.

Backend

The backend has the small building blocks that are used to create documents. These building blocks are called “nodes” which can be chained together in linked lists, a node list.

See the architecture overview for a more detailed description.

Status

This library is still under development. Expect API changes.

Contact

Patrick Gundlach, gundlach@speedata.de
mastodon: @speedata, @boxesandglue

Sample code

See the bagme library and boxesandglue-examples for code using boxes and glue.

For a starter here is a simple main.go to play with. The result is

typeset text from the frog king

package main

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/speedata/boxesandglue/backend/bag"
	"github.com/speedata/boxesandglue/frontend"
)

var (
	str = `In olden times when wishing still helped one, there lived a king whose daughters
	were all beautiful; and the youngest was so beautiful that the sun itself, which
	has seen so much, was astonished whenever it shone in her face.
	Close by the king's castle lay a great dark forest, and under an old lime-tree in the forest
	was a well, and when the day was very warm, the king's child went out into the
	forest and sat down by the side of the cool fountain; and when she was bored she
	took a golden ball, and threw it up on high and caught it; and this ball was her
	favorite plaything.`
)

func typesetSample() error {
	f, err := frontend.New("sample.pdf")
	if err != nil {
		return err
	}

	f.Doc.Title = "The frog king"

	if f.Doc.DefaultLanguage, err = frontend.GetLanguage("en"); err != nil {
		return err
	}

	// Load a font, define a font family, and add this font to the family.
	ff := f.NewFontFamily("text")
	ff.AddMember(
		&frontend.FontSource{Location: "fonts/CrimsonPro-Regular.ttf"},
		frontend.FontWeight400,
		frontend.FontStyleNormal,
	)

	// Create a recursive data structure for typesetting initialized with the
	// text from the top (but with space normalized).
	para := frontend.NewText()
	para.Items = []any{strings.Join(strings.Fields(str), " ")}

	// Format the text into a paragraph. Some of these settings (font family and
	// font size) can be part of the typesetting element.
	vlist, _, err := f.FormatParagraph(para, bag.MustSp("125pt"),
		frontend.Leading(bag.MustSp("14pt")),
		frontend.FontSize(bag.MustSp("12pt")),
		frontend.Family(ff),
	)
	if err != nil {
		return err
	}

	// Output the text and finish the page and the PDF file.
	p := f.Doc.NewPage()
	p.OutputAt(bag.MustSp("1cm"), bag.MustSp("26cm"), vlist)
	p.Shipout()
	if err = f.Doc.Finish(); err != nil {
		return err
	}
	return nil
}

func main() {
	starttime := time.Now()
	err := typesetSample()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("finished in ", time.Now().Sub(starttime))
}

To get a PDF/UA (universal accessibility) document, insert the following lines before .OutputAt... and add "github.com/speedata/boxesandglue/backend/document" to the import section.

	f.Doc.RootStructureElement = &document.StructureElement{
		Role: "Document",
	}

	para := &document.StructureElement{
		Role:       "P",
		ActualText: str,
	}
	f.Doc.RootStructureElement.AddChild(para)

	vlist.Attributes = node.H{
		"tag": para,
	}