docspec

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2021 License: MIT Imports: 12 Imported by: 0

README

Docspec

MIT licensed Go Reference

A work-in-progress PDF renderer, used to provide a declarative API similar to CSS for laying out simple PDF documents.

This port of FPDF for Go provides all the basic primitives. I want to stress that all the interesting PDF-specific stuff happens in that library thanks to its authors.

Nevertheless, I wanted to create a layout engine where I could specify layouts more declaratively (similar to CSS), rather than calculating all the rects and x and y values myself for each page. The layout engine represents the document as a tree, and walks the tree to calculate widths and heights of each node on the tree in terms of defined layout contraints.

Used internally at my job but open sourced here on my profile for more general use. For the record, I have no intention as of yet of expanding the project beyond what I require for work, but forks or contributions are welcome if desired.

Installation

  • go get github.com/michaelhelvey/docspec

Documentation && Usage

See example/main.go for a usage example.

TODO:

  • Write a good unit test suite. Shouldn't be too difficult as all the heavy lifting happens in the creation of the document tree, which is much easier to assert things about than a PDF document.

  • Improve the automated API documentation enough to be able to create a good godoc site if desired.

Authors

License

MIT

Documentation

Index

Constants

View Source
const (
	// FlowVertical represents a list of children that should flow from top to
	// bottom within the parent.
	FlowVertical = iota
	// FlowHorizontal represents a list of children that should flow from left
	// to right within the parent.
	FlowHorizontal
)
View Source
const (
	// Start is equal to justify-content: start in CSS
	Start childAlignment = iota
	// End is equal to justify-content: end in CSS
	End
	// Center is equal to justify-content: center in CSS
	Center
)
View Source
const (
	// FontRegular describes a regular font in a TextNode
	FontRegular fontStyle = iota
	// FontItalic describes an instalic font in a TextNode
	FontItalic
	// FontBold describes a bold font in a TextNode
	FontBold
	// FontUnderscore describes a underscored font in a TextNode
	FontUnderscore
	// FontStrikeOut describes a strikethrough font in a TextNode
	FontStrikeOut
)
View Source
const (
	// fill draw rect of parent with image, do not preserve aspect ratio
	ImageStretch imageFit = iota
	// preserve aspect ratio of original image
	ImagePreserve
)

Note: we're specifying both aspect ratio and alignment in the same enum here. We may want to split these out.

View Source
const (
	// ImageCenter places the image in the center of the parent
	ImageCenter imageAlignment = iota
	// ImageStart preserve aspect ratio, and aligns longest edge of image along
	// the start of the parent draw rect.  if the image is taller than it is
	// wide, this will be the left edge of the draw rect.  If the image is
	// wider than is is tall, this will be the bottom of the parent draw rect.
	ImageStart
	// ImageEnd preserves aspect ratio and is the inverse of `ImageStart`
	ImageEnd
)
View Source
const (
	// DocumentSizeLetter represents a page with the size U.S. Letter
	DocumentSizeLetter documentSize = iota
)

Variables

This section is empty.

Functions

func NoChildren

func NoChildren(parent *LayoutNode)

NoChildren is the NoOp callback for creating node children.

Types

type BooleanQuad

type BooleanQuad struct {
	// contains filtered or unexported fields
}

BooleanQuad represents on/off state for each side of a rectangle

func NewBooleanQuad

func NewBooleanQuad(top, right, bottom, left bool) BooleanQuad

NewBooleanQuad creates a quadrilateral of boolean values

func NewSingletonBooleanQuad

func NewSingletonBooleanQuad(value bool) BooleanQuad

NewSingletonBooleanQuad creates a quadrilateral of boolean values where each value is equal to every other value.

type Color

type Color struct {
	// contains filtered or unexported fields
}

Color represents an RGB color

func NewColor

func NewColor(red, green, blue int) Color

NewColor constructs an RGB color from its arguments

type Document

type Document struct {
	Children []*Page
}

Document is the fundamental unit of layout. It encapsulates global settings and a list of pages. The list of pages is manipulated by the layout engine to create page breaks, and is thus not exposed to userspace.

type DocumentBuilder

type DocumentBuilder struct {
	// contains filtered or unexported fields
}

DocumentBuilder specifies the public interface for building documents

func NewDocumentBuilder

func NewDocumentBuilder(renderer DocumentRenderer, size documentSize, margin Size) *DocumentBuilder

NewDocumentBuilder creates a new document with the specified margin and size, and a renderer

func (*DocumentBuilder) CreateDocumentTree

func (d *DocumentBuilder) CreateDocumentTree(nodeList []*LayoutNode) error

CreateDocumentTree traverses the given list of nodes and calculates the coordinates for all rects in the tree.

func (*DocumentBuilder) PrettyPrintDocumentTree

func (d *DocumentBuilder) PrettyPrintDocumentTree()

PrettyPrintDocumentTree prints the AST of the document with sizing information. This method will only produce useful output after `CreateDocumentTree` has been called.

func (*DocumentBuilder) RenderToWriter

func (d *DocumentBuilder) RenderToWriter(writer io.Writer) error

RenderToWriter outputs the document tree to a specified writer. For example, to render the document tree as a PDF, you would create a DocumentBuilder with a pdf renderer as the renderer, and call RenderToWriter where the writer is the file that you want to save the PDF to.

type DocumentRenderer

type DocumentRenderer interface {
	// Render takes a document and renders it into some result value suitable
	// for the type of renderer.  For example, for an FPDF renderer, the result
	// would be an FPDF struct that can be saved to a file.  For a JPEG
	// renderer, the result would be JPEG byte data.  And so on.
	Render(root *Document) (result interface{}, err error)

	// SaveToFile takes in the render result and saves it via a io.Writer
	Save(renderResult interface{}, writer io.Writer) error

	// SplitText calculates from font attributes present in TextNode the
	// optimal number and size of lines required to render the text node into
	// the given rect.  This method is present in the renderer so that the main
	// layout engine can communicate with the renderer to prepare text node's
	// with different text overflow behaviors.
	SplitText(targetRect Rect, textNode TextNode) []string

	// GetTextWidth gets the width that a piece of text would have if it was
	// not split.  This is so that a text node can have an inherent width and
	// height can be used in a Height/WidthAsChildren definition.
	GetInherentTextRect(textNode TextNode) Rect
}

DocumentRenderer defines the interface which must be implemented by a type in order to render a document tree into a document. It's defined here in the docspec package so that users can write their own renderer beyond the ones already defined by docspec.

type FontConfig

type FontConfig struct {
	Name  string
	Style string
	File  string
}

FontConfig represents the font data required to initialize the font with the underlying FPDF library.

type Future

type Future struct {
	// contains filtered or unexported fields
}

Future is a datatype modeling a value defined by a recursive function that cannot be evaluated until the entire layout tree is constructed. For example, the height of a node is often defined by the height of its children, which do not yet exist at the time of the node's construction. While in this way it is conceptually similar to a "Future" as used in many programming language's asyncIO implementation, it has nothing to do with concurrency.

func HeightAsChildren

func HeightAsChildren() Future

HeightAsChildren creates a future that will resolve to a value that is the height of the full document tree below the current node.

func HeightFill

func HeightFill() Future

HeightFill creates a future that will resolve to whatever space is left available within the draw rect of the parent after all sibling trees have been resolved.

func HeightPercentage

func HeightPercentage(percentage Size) Future

HeightPercentage creates a future that will resolve to a value that is a percentage of the parent draw rect's height.

func StaticSize

func StaticSize(size Size) Future

StaticSize returns a future that is already resolved to a static value.

func WidthAsChildren

func WidthAsChildren() Future

WidthAsChildren creates a future that will resolve to a value that is the width of the full document tree below the current node.

func WidthFill

func WidthFill() Future

WidthFill creates a future that will resolve to whatever space is left available within the draw rect of the parent after all sibling trees have been resolved.

func WidthPercentage

func WidthPercentage(percentage Size) Future

WidthPercentage creates a future that will resolve to a value that is a percentage of the parent draw rect's width.

type ImageNode

type ImageNode struct {
	// full path to a local file from which to read the image.  Currently only
	// gif, jpeg, and png file types are supported.
	Src           string
	RatioBehavior imageFit
	Alignment     imageAlignment
	// contains filtered or unexported fields
}

ImageNode represents an image drawn from a local file

type LayoutChildAlignment

type LayoutChildAlignment struct {
	Vertical   childAlignment
	Horizontal childAlignment
}

LayoutChildAlignment configures where on the x and y axis inside a given node to position that node's children

type LayoutNode

type LayoutNode struct {
	ID                 string
	Children           []*LayoutNode
	VisualNode         interface{}
	Parent             *LayoutNode
	Page               *Page
	X                  Size
	Y                  Size
	Width              Future
	Height             Future
	Border             BooleanQuad
	BorderColor        Color
	ShowFill           bool
	FillColor          Color
	Padding            SizesQuad
	Margin             SizesQuad
	ChildAlignment     LayoutChildAlignment
	ChildFlowDirection childFlowDirection
	// contains filtered or unexported fields
}

LayoutNode represents a rectangular container into which content can be rendered. Layout nodes control all layout such as width, height, padding, and so on. Value nodes such as a text nodes or image nodes are used to render content into the rect defined by the LayoutNode.

Splitting over pages is determined by testing if the node's height would take it over the draw rect of the current page. If it would, then the tree is traversed backwards until the parent node that is a direct child of the Page is found, and the origin of that node is placed at the origin of the page, and x,y coordination calculation continues down the page as before. If that parent node's height is greater than the height of the page (i.e. the node cannot fit on a single page), then an error is raised. An error is also raised is a node's measured height is greater than the height of any page, meaning that the above test would result in infinite recursion.

Layout calculation takes place in two phases: first the widths and heights of all the nodes are calculated, and then the x,y coordinates of each rect is calculated. Thus the entire document tree is iterated over at least 3 times: twice as described above, and then at least once again during rendering.

func CreateNodeList

func CreateNodeList(nodes ...*LayoutNode) []*LayoutNode

CreateNodeList is syntactic suguar for creating a list of layout nodes

func Div

func Div(parent *LayoutNode, options LayoutNodeProps, cb func(*LayoutNode)) *LayoutNode

Div inserts a plain layout node into the document tree

func Image

func Image(parent *LayoutNode, options LayoutNodeProps, imageProps ImageNode) *LayoutNode

Image inserts an image component into the document tree. It has no callback because a visual node by definition must be a leaf of the document tree.

func Text

func Text(parent *LayoutNode, options LayoutNodeProps, textProps TextNode) *LayoutNode

Text inserts a text component in the document tree

func (*LayoutNode) Clone added in v0.0.2

func (n *LayoutNode) Clone() *LayoutNode

Clone returns a pointer to a copy of the original node tree, with all child nodes recursively cloned.

type LayoutNodeProps

type LayoutNodeProps struct {
	ID                 string
	Border             BooleanQuad
	BorderColor        Color
	ShowFill           bool
	FillColor          Color
	Padding            SizesQuad
	Margin             SizesQuad
	Width              Future
	Height             Future
	ChildAlignment     LayoutChildAlignment
	ChildFlowDirection childFlowDirection
}

LayoutNodeProps is the subset of the properties of the LayoutNode struct which can be passed into node constructors.

type PDF

type PDF = *gofpdf.Fpdf

PDF is an alias for a pointer to gopdf's FPDF type, created for easier typing

type PDFRenderer

type PDFRenderer struct {
	// contains filtered or unexported fields
}

PDFRenderer implements the DocumentRenderer interface for Adobe pdf documents.

func NewPDFRenderer

func NewPDFRenderer(ds documentSize, fontsDir string, fonts ...FontConfig) (*PDFRenderer, error)

NewPDFRenderer creates a new renderer that will render the document tree into a PDF. The first font in the list of FontConfig objects will

func (*PDFRenderer) GetInherentTextRect added in v0.0.3

func (r *PDFRenderer) GetInherentTextRect(textNode TextNode) Rect

GetInherentTextRect uses the calculated line height in MM and the underlying FPDF's GetStringWidth function to get the width and height that a text would have if unwrapped using the current font properties.

func (*PDFRenderer) Render

func (r *PDFRenderer) Render(document *Document) (result interface{}, err error)

Render walks the document and renders the result to the underlying FPDF instance.

func (*PDFRenderer) Save

func (r *PDFRenderer) Save(renderResult interface{}, writer io.Writer) error

Save outputs the created pdf to a given io.Writer

func (*PDFRenderer) SplitText

func (r *PDFRenderer) SplitText(targetRect Rect, textNode TextNode) []string

SplitText calculates the number and size of lines required to render the text node into the given rect.

type Page

type Page struct {
	Children []*LayoutNode
	Width    Size
	Height   Size
	Margin   SizesQuad
}

Page is a special layout node that is always statically sized at the size specified in the document settings.

type Rect

type Rect struct {
	// contains filtered or unexported fields
}

Rect represents a rectangle in unknown space (no x and y values are specified, only size)

type Size

type Size = float64

Size is the fundmental unit of measurement used for widths, heights, margins, and other measured areas.

type SizesQuad

type SizesQuad struct {
	// contains filtered or unexported fields
}

SizesQuad represents values that occur for each of the four sides of a rectangle, such as padding and margin

func NewSingletonSizeQuad

func NewSingletonSizeQuad(value Size) SizesQuad

NewSingletonSizeQuad creates a quadrilateral of sizes where each side has the same value as every other side.

func NewSizeQuad

func NewSizeQuad(top, right, bottom, left Size) SizesQuad

NewSizeQuad creates a quadrilateral of sizes

type TextNode

type TextNode struct {
	Text       string
	FontFamily string
	FontStyle  fontStyle
	// size of the font in points (1pt == 0.3528mm)
	FontSize Size
	Color    Color
	// multiplier used to determine line height based on font size
	LineHeight       Size
	Alignment        textAlignment
	Link             string
	OverflowBehavior overflowBehavior
}

TextNode represents a paragraph of text, possibly containing a link

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL