diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index feadff73..5b232ce0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -79,3 +79,10 @@ jobs:
- name: Test docs examples
run: go test ./assets/examples/...
working-directory: docs
+
+ # docs/assets/js/paper-fs.js hand-implements the filesystem syscalls Go's
+ # js/wasm runtime calls. A regression there does not fail loudly — the
+ # renderer draws an error box and still emits a valid PDF — so it is
+ # pinned by its own tests. Node is preinstalled on ubuntu-latest.
+ - name: Test browser filesystem shim
+ run: node --test docs/assets/js/paper-fs.test.mjs
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index 0f554755..2a47c2db 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -46,14 +46,20 @@ jobs:
cp -r docs _site
rm -f _site/go.mod _site/go.sum
rm -rf _site/plans
- # Keep assets/examples/*/main.go — docsify embeds them via ':include'.
+ # Keep assets/examples/**/*.go — docsify embeds them via ':include'.
find _site -name '*_test.go' -delete
- mkdir -p _site/playground
- cp examples/cmd/wasm/web/index.html _site/playground/
- cp examples/cmd/wasm/web/paper.wasm _site/playground/
- cp examples/cmd/wasm/web/wasm_exec.js _site/playground/
+ # The docs pages generate their PDF previews in the browser, so the
+ # wasm has to be reachable from the site root as well as the
+ # playground. The playground keeps its own copy so that it stays
+ # servable standalone from examples/cmd/wasm/web.
+ mkdir -p _site/assets/wasm _site/playground
+ cp -f examples/cmd/wasm/web/paper.wasm _site/assets/wasm/
+ cp -f examples/cmd/wasm/web/wasm_exec.js _site/assets/wasm/
+ cp -f examples/cmd/wasm/web/index.html _site/playground/
+ cp -f examples/cmd/wasm/web/paper.wasm _site/playground/
+ cp -f examples/cmd/wasm/web/wasm_exec.js _site/playground/
touch _site/.nojekyll
- echo "Assembled site:" && ls -1 _site && echo "playground:" && ls -1 _site/playground
+ echo "Assembled site:" && ls -1 _site && echo "wasm:" && ls -1 _site/assets/wasm && echo "playground:" && ls -1 _site/playground
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
diff --git a/.gitignore b/.gitignore
index b2a6317b..e38dfb80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,22 @@ docs/plans/
examples/cmd/wasm/web/paper.wasm
examples/cmd/wasm/web/wasm_exec.js
+# Copy of the wasm the docs site serves for its in-browser PDF previews
+# (placed by `make docs` / `make site`; built, not authored)
+docs/assets/wasm/
+
+# Example PDFs are generated by `make examples` and rendered in the browser by
+# paper.wasm, so they are no longer committed. Six survive: five whose inputs are
+# impractical to ship to a browser and stay static embeds, plus paper.pdf, which
+# the mergepdf example reads as an input.
+docs/assets/pdf/*.pdf
+!docs/assets/pdf/paper.pdf
+!docs/assets/pdf/showcase.pdf
+!docs/assets/pdf/background.pdf
+!docs/assets/pdf/customfont.pdf
+!docs/assets/pdf/mergepdf.pdf
+!docs/assets/pdf/disablepagebreak.pdf
+
# playwright-cli E2E scratch output
.playwright-cli/
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 85176819..315262c9 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -79,8 +79,26 @@ Tests must create mocks via constructors (`m := mocks.NewProvider(t)`);
## Docs site
```bash
-make docs # docsify serve docs/ on http://localhost:3000
+make docs # builds the wasm, copies it into docs/assets/wasm/, then
+ # docsify serve docs/ on http://localhost:3000
+make site # assembles the full Pages site into site/ and serves :8080
```
+`make docs` depends on `make wasm` because the feature pages generate their PDF
+previews in the browser from `docs/assets/wasm/paper.wasm`; without it every
+preview renders an error box. That directory is generated and git-ignored.
+
+Example PDFs under `docs/assets/pdf/` are produced by `make examples` and are
+**not** committed, apart from six: `background`, `customfont`,
+`disablepagebreak`, `mergepdf` and `showcase` stay static embeds because their
+inputs are impractical to ship to a browser, and `paper.pdf` is an input the
+`mergepdf` example reads. Note that `paper.pdf` is generated by
+`examples/cmd/dev/pdf`, not by `make examples`.
+
+Regenerating the tracked PDFs always produces a diff even with no code change,
+because no example sets `WithDeterministic` and the embedded `CreationDate`
+moves. Check the structure fixtures under `test/paper/examples/` for real
+output changes.
+
`docs/plans/` holds internal planning documents and is excluded from version
control and the site.
diff --git a/Makefile b/Makefile
index 8e8aaad3..01169e67 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,10 @@ test:
go test $(GO_PATHS)
cd examples && go test ./...
cd docs && go test ./assets/examples/...
+ # The browser filesystem shim implements a Go syscall contract by hand, and a
+ # break in it renders an error box into an otherwise valid PDF rather than
+ # failing, so it gets its own tests.
+ node --test docs/assets/js/paper-fs.test.mjs
.PHONY: fmt
fmt:
@@ -44,8 +48,13 @@ mock-lint:
install:
bash shell/install.sh
+# Serve the docs locally. Depends on wasm because the feature pages generate
+# their PDF previews in the browser from docs/assets/wasm/paper.wasm.
.PHONY: docs
-docs:
+docs: wasm
+ mkdir -p docs/assets/wasm
+ # -f: the toolchain's wasm_exec.js is read-only (0444), so the copy is too.
+ cp -f examples/cmd/wasm/web/paper.wasm examples/cmd/wasm/web/wasm_exec.js docs/assets/wasm/
docsify serve docs/
.PHONY: godoc
@@ -72,46 +81,49 @@ site: wasm
cp -r docs site
rm -f site/go.mod site/go.sum
rm -rf site/plans
- find site -name '*.go' -delete
- mkdir -p site/playground
- cp examples/cmd/wasm/web/index.html examples/cmd/wasm/web/paper.wasm examples/cmd/wasm/web/wasm_exec.js site/playground/
+ # Only test files go: docsify ':include's the example sources, so deleting
+ # every *.go (as this used to) left the code samples on the site empty.
+ find site -name '*_test.go' -delete
+ mkdir -p site/assets/wasm site/playground
+ cp -f examples/cmd/wasm/web/paper.wasm examples/cmd/wasm/web/wasm_exec.js site/assets/wasm/
+ cp -f examples/cmd/wasm/web/index.html examples/cmd/wasm/web/paper.wasm examples/cmd/wasm/web/wasm_exec.js site/playground/
@echo "Serving site at http://localhost:8080/ (playground: http://localhost:8080/playground/)"
cd site && python3 -m http.server 8080
.PHONY: examples
examples:
- go run docs/assets/examples/addpage/main.go
- go run docs/assets/examples/autorow/main.go
- go run docs/assets/examples/background/main.go
- go run docs/assets/examples/barcodegrid/main.go
- go run docs/assets/examples/billing/main.go
- go run docs/assets/examples/bookmark/main.go
+ go run ./docs/assets/examples/addpage/cmd
+ go run ./docs/assets/examples/autorow/cmd
+ go run ./docs/assets/examples/background/cmd
+ go run ./docs/assets/examples/barcodegrid/cmd
+ go run ./docs/assets/examples/billing/cmd
+ go run ./docs/assets/examples/bookmark/cmd
cd examples && go run ./cmd/paper-showcase ../docs/assets/pdf/showcase.pdf
- go run docs/assets/examples/cellstyle/main.go
- go run docs/assets/examples/checkbox/main.go
- go run docs/assets/examples/compression/main.go
- go run docs/assets/examples/customdimensions/main.go
- go run docs/assets/examples/customfont/main.go
- go run docs/assets/examples/custompage/main.go
- go run docs/assets/examples/datamatrixgrid/main.go
- go run docs/assets/examples/disablepagebreak/main.go
- go run docs/assets/examples/footer/main.go
- go run docs/assets/examples/header/main.go
- go run docs/assets/examples/imagegrid/main.go
- go run docs/assets/examples/line/main.go
- go run docs/assets/examples/list/main.go
- go run docs/assets/examples/lowmemory/main.go
- go run docs/assets/examples/margins/main.go
- go run docs/assets/examples/maxgridsum/main.go
- go run docs/assets/examples/mergepdf/main.go
- go run docs/assets/examples/metadatas/main.go
- go run docs/assets/examples/orientation/main.go
- go run docs/assets/examples/pagenumber/main.go
- go run docs/assets/examples/parallelism/main.go
- go run docs/assets/examples/protection/main.go
- go run docs/assets/examples/qrgrid/main.go
- go run docs/assets/examples/signaturegrid/main.go
- go run docs/assets/examples/simplest/main.go
- go run docs/assets/examples/textgrid/main.go
- go run docs/assets/examples/watermark/main.go
+ go run ./docs/assets/examples/cellstyle/cmd
+ go run ./docs/assets/examples/checkbox/cmd
+ go run ./docs/assets/examples/compression/cmd
+ go run ./docs/assets/examples/customdimensions/cmd
+ go run ./docs/assets/examples/customfont/cmd
+ go run ./docs/assets/examples/custompage/cmd
+ go run ./docs/assets/examples/datamatrixgrid/cmd
+ go run ./docs/assets/examples/disablepagebreak/cmd
+ go run ./docs/assets/examples/footer/cmd
+ go run ./docs/assets/examples/header/cmd
+ go run ./docs/assets/examples/imagegrid/cmd
+ go run ./docs/assets/examples/line/cmd
+ go run ./docs/assets/examples/list/cmd
+ go run ./docs/assets/examples/lowmemory/cmd
+ go run ./docs/assets/examples/margins/cmd
+ go run ./docs/assets/examples/maxgridsum/cmd
+ go run ./docs/assets/examples/mergepdf/cmd
+ go run ./docs/assets/examples/metadatas/cmd
+ go run ./docs/assets/examples/orientation/cmd
+ go run ./docs/assets/examples/pagenumber/cmd
+ go run ./docs/assets/examples/parallelism/cmd
+ go run ./docs/assets/examples/protection/cmd
+ go run ./docs/assets/examples/qrgrid/cmd
+ go run ./docs/assets/examples/signaturegrid/cmd
+ go run ./docs/assets/examples/simplest/cmd
+ go run ./docs/assets/examples/textgrid/cmd
+ go run ./docs/assets/examples/watermark/cmd
go test docs/assets/examples/unittests/main_test.go
diff --git a/docs/README.md b/docs/README.md
index 77791cd4..5089a6c8 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -42,7 +42,7 @@ Use the row/column API when you need to mix HTML with manual components, headers
## Code Example
This is part of the [simplest example](examples/simplest?id=simplest).
-[filename](assets/examples/simplest/main.go ':include :type=code')
+[filename](assets/examples/simplest/paper.go ':include :type=code')
## PDF Example
This is part of the [showcase example](examples/showcase?id=showcase).
diff --git a/docs/assets/examples/addpage/cmd/main.go b/docs/assets/examples/addpage/cmd/main.go
new file mode 100644
index 00000000..6ab924c6
--- /dev/null
+++ b/docs/assets/examples/addpage/cmd/main.go
@@ -0,0 +1,29 @@
+// Command addpage writes the addpage example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/addpage/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/addpage"
+)
+
+func main() {
+ m := addpage.GetPaper()
+
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/addpage.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/addpage.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/addpage/main.go b/docs/assets/examples/addpage/paper.go
similarity index 66%
rename from docs/assets/examples/addpage/main.go
rename to docs/assets/examples/addpage/paper.go
index 4643cae4..b849fe55 100644
--- a/docs/assets/examples/addpage/main.go
+++ b/docs/assets/examples/addpage/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates adding pages explicitly, so content starts on a fresh page.
-package main
+// Package addpage demonstrates adding pages explicitly, so content starts on a fresh page.
+package addpage
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper"
"github.com/avdoseferovic/paper/pkg/components/page"
"github.com/avdoseferovic/paper/pkg/components/text"
@@ -13,25 +10,7 @@ import (
"github.com/avdoseferovic/paper/pkg/decorator"
)
-func main() {
- m := GetPaper()
-
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/addpage.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/addpage.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the addpage example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithPageNumber().
diff --git a/docs/assets/examples/addpage/main_test.go b/docs/assets/examples/addpage/paper_test.go
similarity index 93%
rename from docs/assets/examples/addpage/main_test.go
rename to docs/assets/examples/addpage/paper_test.go
index 3bdec164..aba84419 100644
--- a/docs/assets/examples/addpage/main_test.go
+++ b/docs/assets/examples/addpage/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package addpage
import (
"testing"
diff --git a/docs/assets/examples/autorow/cmd/main.go b/docs/assets/examples/autorow/cmd/main.go
new file mode 100644
index 00000000..c7f7aebe
--- /dev/null
+++ b/docs/assets/examples/autorow/cmd/main.go
@@ -0,0 +1,28 @@
+// Command autorow writes the autorow example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/autorow/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/autorow"
+)
+
+func main() {
+ m := autorow.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/autorow.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/autorow.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/autorow/main.go b/docs/assets/examples/autorow/paper.go
similarity index 80%
rename from docs/assets/examples/autorow/main.go
rename to docs/assets/examples/autorow/paper.go
index 69dd5865..942546da 100644
--- a/docs/assets/examples/autorow/main.go
+++ b/docs/assets/examples/autorow/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates rows that take their height from the content inside them.
-package main
+// Package autorow demonstrates rows that take their height from the content inside them.
+package autorow
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/components/code"
"github.com/avdoseferovic/paper/pkg/components/image"
"github.com/avdoseferovic/paper/pkg/components/text"
@@ -18,24 +15,7 @@ import (
"github.com/avdoseferovic/paper/pkg/config"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/autorow.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/autorow.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the autorow example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/autorow/main_test.go b/docs/assets/examples/autorow/paper_test.go
similarity index 93%
rename from docs/assets/examples/autorow/main_test.go
rename to docs/assets/examples/autorow/paper_test.go
index aeafada8..7e7aced0 100644
--- a/docs/assets/examples/autorow/main_test.go
+++ b/docs/assets/examples/autorow/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package autorow
import (
"testing"
diff --git a/docs/assets/examples/background/cmd/main.go b/docs/assets/examples/background/cmd/main.go
new file mode 100644
index 00000000..226ae47e
--- /dev/null
+++ b/docs/assets/examples/background/cmd/main.go
@@ -0,0 +1,29 @@
+// Command background writes the background example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/background/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/background"
+)
+
+func main() {
+ backgroundImage := "docs/assets/images/certificate.png"
+ m := background.GetPaper(backgroundImage)
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/background.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/background.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/background/main.go b/docs/assets/examples/background/paper.go
similarity index 73%
rename from docs/assets/examples/background/main.go
rename to docs/assets/examples/background/paper.go
index e6f21ffe..676a1df8 100644
--- a/docs/assets/examples/background/main.go
+++ b/docs/assets/examples/background/paper.go
@@ -1,8 +1,7 @@
-// Package main demonstrates drawing a full-page background image behind the content.
-package main
+// Package background demonstrates drawing a full-page background image behind the content.
+package background
import (
- "context"
"log"
"os"
@@ -23,25 +22,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- backgroundImage := "docs/assets/images/certificate.png"
- m := GetPaper(backgroundImage)
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/background.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/background.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the background example document.
func GetPaper(image string) core.Paper {
bytes, err := os.ReadFile(image)
if err != nil {
@@ -62,6 +43,7 @@ func GetPaper(image string) core.Paper {
return m
}
+// AddPage is part of the background example.
func AddPage() core.Page {
return page.New().Add(
row.New(70),
diff --git a/docs/assets/examples/background/main_test.go b/docs/assets/examples/background/paper_test.go
similarity index 96%
rename from docs/assets/examples/background/main_test.go
rename to docs/assets/examples/background/paper_test.go
index 7913721b..f4fa92f5 100644
--- a/docs/assets/examples/background/main_test.go
+++ b/docs/assets/examples/background/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package background
import (
"os"
diff --git a/docs/assets/examples/barcodegrid/cmd/main.go b/docs/assets/examples/barcodegrid/cmd/main.go
new file mode 100644
index 00000000..e11bb9e1
--- /dev/null
+++ b/docs/assets/examples/barcodegrid/cmd/main.go
@@ -0,0 +1,28 @@
+// Command barcodegrid writes the barcodegrid example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/barcodegrid/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/barcodegrid"
+)
+
+func main() {
+ m := barcodegrid.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/barcodegrid.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/barcodegrid.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/barcodegrid/main.go b/docs/assets/examples/barcodegrid/paper.go
similarity index 84%
rename from docs/assets/examples/barcodegrid/main.go
rename to docs/assets/examples/barcodegrid/paper.go
index 7a04139f..ddf8b05a 100644
--- a/docs/assets/examples/barcodegrid/main.go
+++ b/docs/assets/examples/barcodegrid/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates placing Code 128 barcodes across a grid of columns.
-package main
+// Package barcodegrid demonstrates placing Code 128 barcodes across a grid of columns.
+package barcodegrid
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/consts"
"github.com/avdoseferovic/paper/pkg/core"
@@ -17,24 +14,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/barcodegrid.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/barcodegrid.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the barcodegrid example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/barcodegrid/main_test.go b/docs/assets/examples/barcodegrid/paper_test.go
similarity index 92%
rename from docs/assets/examples/barcodegrid/main_test.go
rename to docs/assets/examples/barcodegrid/paper_test.go
index 4bdee565..8bf39d67 100644
--- a/docs/assets/examples/barcodegrid/main_test.go
+++ b/docs/assets/examples/barcodegrid/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package barcodegrid
import (
"testing"
diff --git a/docs/assets/examples/billing/cmd/main.go b/docs/assets/examples/billing/cmd/main.go
new file mode 100644
index 00000000..2cfe0777
--- /dev/null
+++ b/docs/assets/examples/billing/cmd/main.go
@@ -0,0 +1,28 @@
+// Command billing writes the billing example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/billing/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/billing"
+)
+
+func main() {
+ m := billing.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/billing.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/billing.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/billing/main.go b/docs/assets/examples/billing/paper.go
similarity index 92%
rename from docs/assets/examples/billing/main.go
rename to docs/assets/examples/billing/paper.go
index 39687263..f82f6d53 100644
--- a/docs/assets/examples/billing/main.go
+++ b/docs/assets/examples/billing/paper.go
@@ -1,8 +1,7 @@
-// Package main demonstrates a complete invoice: header, line-item table, and totals.
-package main
+// Package billing demonstrates a complete invoice: header, line-item table, and totals.
+package billing
import (
- "context"
"log"
"github.com/avdoseferovic/paper"
@@ -21,24 +20,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/billing.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/billing.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the billing example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithPageNumber().
diff --git a/docs/assets/examples/billing/main_test.go b/docs/assets/examples/billing/paper_test.go
similarity index 93%
rename from docs/assets/examples/billing/main_test.go
rename to docs/assets/examples/billing/paper_test.go
index d1e96310..e72686f0 100644
--- a/docs/assets/examples/billing/main_test.go
+++ b/docs/assets/examples/billing/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package billing
import (
"testing"
diff --git a/docs/assets/examples/bookmark/cmd/main.go b/docs/assets/examples/bookmark/cmd/main.go
new file mode 100644
index 00000000..5179973a
--- /dev/null
+++ b/docs/assets/examples/bookmark/cmd/main.go
@@ -0,0 +1,24 @@
+// Command bookmark writes the bookmark example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/bookmark/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/bookmark"
+)
+
+func main() {
+ m := bookmark.GetPaper()
+
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/bookmark.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/bookmark/main.go b/docs/assets/examples/bookmark/paper.go
similarity index 73%
rename from docs/assets/examples/bookmark/main.go
rename to docs/assets/examples/bookmark/paper.go
index e5562678..37f2d6f3 100644
--- a/docs/assets/examples/bookmark/main.go
+++ b/docs/assets/examples/bookmark/paper.go
@@ -1,18 +1,17 @@
-// Package main demonstrates PDF outline bookmarks: text components with an
+// Package bookmark demonstrates PDF outline bookmarks: text components with an
// Outline prop appear in the PDF viewer's bookmark sidebar.
-package main
+package bookmark
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper"
"github.com/avdoseferovic/paper/pkg/components/col"
"github.com/avdoseferovic/paper/pkg/components/text"
+ "github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
+// GetPaper builds the bookmark example document.
+func GetPaper() core.Paper {
m := paper.New()
m.AddAutoRow(col.New(12).Add(text.New("Introduction", props.Text{
@@ -34,13 +33,5 @@ func main() {
Outline: &props.Outline{Level: 1, Title: "Your first document"},
})))
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/bookmark.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
+ return m
}
diff --git a/docs/assets/examples/bookmark/paper_test.go b/docs/assets/examples/bookmark/paper_test.go
new file mode 100644
index 00000000..ca5586da
--- /dev/null
+++ b/docs/assets/examples/bookmark/paper_test.go
@@ -0,0 +1,16 @@
+package bookmark
+
+import (
+ "testing"
+
+ "github.com/avdoseferovic/paper/internal/test"
+)
+
+func TestGetPaper(t *testing.T) {
+ t.Parallel()
+ // Act
+ sut := GetPaper()
+
+ // Assert
+ test.New(t).Assert(sut.GetStructure()).Equals("examples/bookmark.json")
+}
diff --git a/docs/assets/examples/cellstyle/cmd/main.go b/docs/assets/examples/cellstyle/cmd/main.go
new file mode 100644
index 00000000..0ddb0e29
--- /dev/null
+++ b/docs/assets/examples/cellstyle/cmd/main.go
@@ -0,0 +1,28 @@
+// Command cellstyle writes the cellstyle example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/cellstyle/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/cellstyle"
+)
+
+func main() {
+ m := cellstyle.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/cellstyle.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/cellstyle.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/cellstyle/main.go b/docs/assets/examples/cellstyle/paper.go
similarity index 87%
rename from docs/assets/examples/cellstyle/main.go
rename to docs/assets/examples/cellstyle/paper.go
index a4bbbc82..7f7af052 100644
--- a/docs/assets/examples/cellstyle/main.go
+++ b/docs/assets/examples/cellstyle/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates styling individual cells with fills, borders, and padding.
-package main
+// Package cellstyle demonstrates styling individual cells with fills, borders, and padding.
+package cellstyle
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper"
"github.com/avdoseferovic/paper/pkg/consts"
"github.com/avdoseferovic/paper/pkg/core"
@@ -19,24 +16,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/cellstyle.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/cellstyle.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the cellstyle example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(false).
diff --git a/docs/assets/examples/cellstyle/main_test.go b/docs/assets/examples/cellstyle/paper_test.go
similarity index 92%
rename from docs/assets/examples/cellstyle/main_test.go
rename to docs/assets/examples/cellstyle/paper_test.go
index 1b56d0b8..ed69c3b1 100644
--- a/docs/assets/examples/cellstyle/main_test.go
+++ b/docs/assets/examples/cellstyle/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package cellstyle
import (
"testing"
diff --git a/docs/assets/examples/checkbox/cmd/main.go b/docs/assets/examples/checkbox/cmd/main.go
new file mode 100644
index 00000000..85b12556
--- /dev/null
+++ b/docs/assets/examples/checkbox/cmd/main.go
@@ -0,0 +1,28 @@
+// Command checkbox writes the checkbox example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/checkbox/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/checkbox"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/checkbox.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/checkbox.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/checkbox/main.go b/docs/assets/examples/checkbox/paper.go
similarity index 73%
rename from docs/assets/examples/checkbox/main.go
rename to docs/assets/examples/checkbox/paper.go
index e726cea1..df8f51cf 100644
--- a/docs/assets/examples/checkbox/main.go
+++ b/docs/assets/examples/checkbox/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates checkbox components with and without labels.
-package main
+// Package checkbox demonstrates checkbox components with and without labels.
+package checkbox
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper"
"github.com/avdoseferovic/paper/pkg/components/checkbox"
"github.com/avdoseferovic/paper/pkg/components/text"
@@ -14,24 +11,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/checkbox.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/checkbox.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the checkbox example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
@@ -40,7 +20,6 @@ func GetPaper() core.Paper {
mrt := paper.New(cfg)
m := decorator.NewMetrics(mrt)
- // Default: unchecked and checked with label
m.AddRow(20,
checkbox.NewCol(4, "Option A"),
checkbox.NewCol(4, "Option B", props.Checkbox{Checked: true}),
@@ -48,7 +27,6 @@ func GetPaper() core.Paper {
)
m.AddRows(text.NewRow(8, "Default: unchecked / checked / unchecked"))
- // Custom size
m.AddRow(20,
checkbox.NewCol(4, "Option A", props.Checkbox{Size: 8}),
checkbox.NewCol(4, "Option B", props.Checkbox{Size: 8, Checked: true}),
@@ -56,7 +34,6 @@ func GetPaper() core.Paper {
)
m.AddRows(text.NewRow(8, "Custom size: 8mm"))
- // With top and left offset
m.AddRow(20,
checkbox.NewCol(4, "Option A", props.Checkbox{Top: 5, Left: 5}),
checkbox.NewCol(4, "Option B", props.Checkbox{Top: 5, Left: 5, Checked: true}),
@@ -64,7 +41,6 @@ func GetPaper() core.Paper {
)
m.AddRows(text.NewRow(8, "With top=5 left=5 offset"))
- // Auto row
m.AddAutoRow(
checkbox.NewCol(3, "Item 1"),
checkbox.NewCol(3, "Item 2", props.Checkbox{Checked: true}),
diff --git a/docs/assets/examples/checkbox/main_test.go b/docs/assets/examples/checkbox/paper_test.go
similarity index 93%
rename from docs/assets/examples/checkbox/main_test.go
rename to docs/assets/examples/checkbox/paper_test.go
index ac98c676..bbd857b6 100644
--- a/docs/assets/examples/checkbox/main_test.go
+++ b/docs/assets/examples/checkbox/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package checkbox
import (
"testing"
diff --git a/docs/assets/examples/compression/cmd/main.go b/docs/assets/examples/compression/cmd/main.go
new file mode 100644
index 00000000..189450f3
--- /dev/null
+++ b/docs/assets/examples/compression/cmd/main.go
@@ -0,0 +1,32 @@
+// Command compression writes the compression example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/compression/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/compression"
+)
+
+func main() {
+ m, err := compression.GetPaper("docs/assets/images/frontpage.png")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/compression.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/compression.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/compression/main_test.go b/docs/assets/examples/compression/main_test.go
deleted file mode 100644
index 20606726..00000000
--- a/docs/assets/examples/compression/main_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package main
-
-import (
- "os"
- "path"
- "strings"
- "testing"
-
- "github.com/avdoseferovic/paper/internal/test"
-)
-
-func TestGetPaper(t *testing.T) {
- t.Parallel()
- // Act
- path := "docs/assets/images/frontpage.png"
- sut := GetPaper(buildPath(path))
-
- // Assert
- test.New(t).Assert(sut.GetStructure()).Equals("examples/compression.json")
-}
-
-func buildPath(file string) string {
- dir, err := os.Getwd()
- if err != nil {
- return ""
- }
-
- dir = strings.ReplaceAll(dir, "docs/assets/examples/compression", "")
- return path.Join(dir, file)
-}
diff --git a/docs/assets/examples/compression/main.go b/docs/assets/examples/compression/paper.go
similarity index 79%
rename from docs/assets/examples/compression/main.go
rename to docs/assets/examples/compression/paper.go
index 0fd2e553..d37d618f 100644
--- a/docs/assets/examples/compression/main.go
+++ b/docs/assets/examples/compression/paper.go
@@ -1,10 +1,8 @@
-// Package main demonstrates turning content-stream compression on and off.
-package main
+// Package compression demonstrates turning content-stream compression on and off.
+package compression
import (
- "context"
"fmt"
- "log"
"os"
"github.com/avdoseferovic/paper/pkg/components/code"
@@ -25,25 +23,11 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper("docs/assets/images/frontpage.png")
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/compression.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/compression.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
-func GetPaper(imagePath string) core.Paper {
+// GetPaper builds the compression demo document. It returns an error when the
+// image cannot be read so callers decide how to fail; the builder itself must
+// never terminate the process, because it also runs inside the wasm playground
+// where os.Exit would kill every registered export.
+func GetPaper(imagePath string) (core.Paper, error) {
cfg := config.NewBuilder().
WithCompression(true).
Build()
@@ -72,8 +56,7 @@ func GetPaper(imagePath string) core.Paper {
bytes, err := os.ReadFile(imagePath)
if err != nil {
- fmt.Println("Got error while opening file:", err)
- os.Exit(1)
+ return nil, fmt.Errorf("read image %s: %w", imagePath, err)
}
m.AddRows(
row.New(20).Add(
@@ -98,5 +81,5 @@ func GetPaper(imagePath string) core.Paper {
),
)
- return m
+ return m, nil
}
diff --git a/docs/assets/examples/compression/paper_test.go b/docs/assets/examples/compression/paper_test.go
new file mode 100644
index 00000000..3c362498
--- /dev/null
+++ b/docs/assets/examples/compression/paper_test.go
@@ -0,0 +1,50 @@
+package compression
+
+import (
+ "os"
+ "path"
+ "strings"
+ "testing"
+
+ "github.com/avdoseferovic/paper/internal/test"
+)
+
+func TestGetPaper(t *testing.T) {
+ t.Parallel()
+ // Act
+ path := "docs/assets/images/frontpage.png"
+ sut, err := GetPaper(buildPath(path))
+ // Assert
+ if err != nil {
+ t.Fatalf("GetPaper returned an error: %v", err)
+ }
+ test.New(t).Assert(sut.GetStructure()).Equals("examples/compression.json")
+}
+
+// TestGetPaperMissingImage pins the contract that a missing image is reported as
+// an error. The builder used to call os.Exit(1) here, which no recover() can
+// catch: under wasm that tears down the instance and permanently disables every
+// export on the page, and in a test run it kills the test binary outright.
+func TestGetPaperMissingImage(t *testing.T) {
+ t.Parallel()
+ // Act
+ sut, err := GetPaper(buildPath("docs/assets/images/does-not-exist.png"))
+
+ // Assert
+ if err == nil {
+ t.Fatal("expected an error for a missing image, got nil")
+ }
+ if sut != nil {
+ t.Errorf("expected a nil Paper alongside the error, got %T", sut)
+ }
+}
+
+func buildPath(file string) string {
+ dir, err := os.Getwd()
+ if err != nil {
+ return ""
+ }
+
+ dir = strings.ReplaceAll(dir, "docs/assets/examples/compression", "")
+ return path.Join(dir, file)
+}
diff --git a/docs/assets/examples/customdimensions/cmd/main.go b/docs/assets/examples/customdimensions/cmd/main.go
new file mode 100644
index 00000000..ea22d9e7
--- /dev/null
+++ b/docs/assets/examples/customdimensions/cmd/main.go
@@ -0,0 +1,28 @@
+// Command customdimensions writes the customdimensions example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/customdimensions/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/customdimensions"
+)
+
+func main() {
+ m := customdimensions.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/customdimensions.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/customdimensions.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/customdimensions/main.go b/docs/assets/examples/customdimensions/paper.go
similarity index 63%
rename from docs/assets/examples/customdimensions/main.go
rename to docs/assets/examples/customdimensions/paper.go
index 402d4920..8e9b065d 100644
--- a/docs/assets/examples/customdimensions/main.go
+++ b/docs/assets/examples/customdimensions/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates a page sized in millimetres instead of a named page size.
-package main
+// Package customdimensions demonstrates a page sized in millimetres instead of a named page size.
+package customdimensions
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper"
@@ -18,24 +15,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/customdimensions.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/customdimensions.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the customdimensions example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDimensions(200, 200).
diff --git a/docs/assets/examples/customdimensions/main_test.go b/docs/assets/examples/customdimensions/paper_test.go
similarity index 90%
rename from docs/assets/examples/customdimensions/main_test.go
rename to docs/assets/examples/customdimensions/paper_test.go
index e18c00dd..4c154839 100644
--- a/docs/assets/examples/customdimensions/main_test.go
+++ b/docs/assets/examples/customdimensions/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package customdimensions
import (
"testing"
diff --git a/docs/assets/examples/customfont/cmd/main.go b/docs/assets/examples/customfont/cmd/main.go
new file mode 100644
index 00000000..d93ae4ea
--- /dev/null
+++ b/docs/assets/examples/customfont/cmd/main.go
@@ -0,0 +1,28 @@
+// Command customfont writes the customfont example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/customfont/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/avdoseferovic/paper/docs/assets/examples/customfont"
+)
+
+func main() {
+ m := customfont.GetPaper("docs/assets/fonts/arial-unicode-ms.ttf")
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/customfont.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/customfont.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/customfont/main.go b/docs/assets/examples/customfont/paper.go
similarity index 95%
rename from docs/assets/examples/customfont/main.go
rename to docs/assets/examples/customfont/paper.go
index d8b4b352..1e719d37 100644
--- a/docs/assets/examples/customfont/main.go
+++ b/docs/assets/examples/customfont/paper.go
@@ -1,8 +1,7 @@
-// Package main demonstrates registering a TrueType font and drawing UTF-8 text with it.
-package main
+// Package customfont demonstrates registering a TrueType font and drawing UTF-8 text with it.
+package customfont
import (
- "context"
"log"
"github.com/avdoseferovic/paper/pkg/consts"
@@ -20,24 +19,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper("docs/assets/fonts/arial-unicode-ms.ttf")
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/customfont.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/customfont.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the customfont example document.
func GetPaper(customFontFile string) core.Paper {
customFont := "arial-unicode-ms"
diff --git a/docs/assets/examples/customfont/main_test.go b/docs/assets/examples/customfont/paper_test.go
similarity index 96%
rename from docs/assets/examples/customfont/main_test.go
rename to docs/assets/examples/customfont/paper_test.go
index fa271db3..2f62bcac 100644
--- a/docs/assets/examples/customfont/main_test.go
+++ b/docs/assets/examples/customfont/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package customfont
import (
"os"
diff --git a/docs/assets/examples/custompage/cmd/main.go b/docs/assets/examples/custompage/cmd/main.go
new file mode 100644
index 00000000..67bb2b60
--- /dev/null
+++ b/docs/assets/examples/custompage/cmd/main.go
@@ -0,0 +1,28 @@
+// Command custompage writes the custompage example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/custompage/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/custompage"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/custompage.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/custompage.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/custompage/main.go b/docs/assets/examples/custompage/paper.go
similarity index 65%
rename from docs/assets/examples/custompage/main.go
rename to docs/assets/examples/custompage/paper.go
index 5ba7c34d..fd195d8c 100644
--- a/docs/assets/examples/custompage/main.go
+++ b/docs/assets/examples/custompage/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates building a document from a custom page definition.
-package main
+// Package custompage demonstrates building a document from a custom page definition.
+package custompage
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper"
@@ -20,24 +17,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/custompage.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/custompage.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the custompage example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithPageSize(pagesize.A2).
diff --git a/docs/assets/examples/custompage/main_test.go b/docs/assets/examples/custompage/paper_test.go
similarity index 92%
rename from docs/assets/examples/custompage/main_test.go
rename to docs/assets/examples/custompage/paper_test.go
index 380098a5..13e40b9d 100644
--- a/docs/assets/examples/custompage/main_test.go
+++ b/docs/assets/examples/custompage/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package custompage
import (
"testing"
diff --git a/docs/assets/examples/datamatrixgrid/cmd/main.go b/docs/assets/examples/datamatrixgrid/cmd/main.go
new file mode 100644
index 00000000..3e6825a5
--- /dev/null
+++ b/docs/assets/examples/datamatrixgrid/cmd/main.go
@@ -0,0 +1,28 @@
+// Command datamatrixgrid writes the datamatrixgrid example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/datamatrixgrid/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/datamatrixgrid"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/datamatrixgrid.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/datamatrixgrid.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/datamatrixgrid/main.go b/docs/assets/examples/datamatrixgrid/paper.go
similarity index 83%
rename from docs/assets/examples/datamatrixgrid/main.go
rename to docs/assets/examples/datamatrixgrid/paper.go
index a6ddbd5f..ac94b1f4 100644
--- a/docs/assets/examples/datamatrixgrid/main.go
+++ b/docs/assets/examples/datamatrixgrid/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates placing Data Matrix codes across a grid of columns.
-package main
+// Package datamatrixgrid demonstrates placing Data Matrix codes across a grid of columns.
+package datamatrixgrid
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper"
@@ -16,24 +13,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/datamatrixgrid.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/datamatrixgrid.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the datamatrixgrid example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/datamatrixgrid/main_test.go b/docs/assets/examples/datamatrixgrid/paper_test.go
similarity index 91%
rename from docs/assets/examples/datamatrixgrid/main_test.go
rename to docs/assets/examples/datamatrixgrid/paper_test.go
index e5e2b1d4..0b516268 100644
--- a/docs/assets/examples/datamatrixgrid/main_test.go
+++ b/docs/assets/examples/datamatrixgrid/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package datamatrixgrid
import (
"testing"
diff --git a/docs/assets/examples/disablepagebreak/cmd/main.go b/docs/assets/examples/disablepagebreak/cmd/main.go
new file mode 100644
index 00000000..0c3d3431
--- /dev/null
+++ b/docs/assets/examples/disablepagebreak/cmd/main.go
@@ -0,0 +1,29 @@
+// Command disablepagebreak writes the disablepagebreak example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/disablepagebreak/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/disablepagebreak"
+)
+
+func main() {
+ backgroundImage := "docs/assets/images/certificate.png"
+ m := example.GetPaper(backgroundImage)
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/disablepagebreak.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/disablepagebreak.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/disablepagebreak/main.go b/docs/assets/examples/disablepagebreak/paper.go
similarity index 61%
rename from docs/assets/examples/disablepagebreak/main.go
rename to docs/assets/examples/disablepagebreak/paper.go
index 5aeb1669..294c3cae 100644
--- a/docs/assets/examples/disablepagebreak/main.go
+++ b/docs/assets/examples/disablepagebreak/paper.go
@@ -1,8 +1,7 @@
-// Package main demonstrates keeping a row on one page instead of letting it split.
-package main
+// Package disablepagebreak demonstrates keeping a row on one page instead of letting it split.
+package disablepagebreak
import (
- "context"
"log"
"os"
@@ -17,25 +16,7 @@ import (
"github.com/avdoseferovic/paper/pkg/core"
)
-func main() {
- backgroundImage := "docs/assets/images/certificate.png"
- m := GetPaper(backgroundImage)
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/disablepagebreak.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/disablepagebreak.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the disablepagebreak example document.
func GetPaper(image string) core.Paper {
bytes, err := os.ReadFile(image)
if err != nil {
diff --git a/docs/assets/examples/disablepagebreak/main_test.go b/docs/assets/examples/disablepagebreak/paper_test.go
similarity index 95%
rename from docs/assets/examples/disablepagebreak/main_test.go
rename to docs/assets/examples/disablepagebreak/paper_test.go
index 529cdfc9..4d2b2c6c 100644
--- a/docs/assets/examples/disablepagebreak/main_test.go
+++ b/docs/assets/examples/disablepagebreak/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package disablepagebreak
import (
"os"
diff --git a/docs/assets/examples/footer/cmd/main.go b/docs/assets/examples/footer/cmd/main.go
new file mode 100644
index 00000000..3d3f69e2
--- /dev/null
+++ b/docs/assets/examples/footer/cmd/main.go
@@ -0,0 +1,28 @@
+// Command footer writes the footer example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/footer/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/footer"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/footer.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/footer.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/footer/main.go b/docs/assets/examples/footer/paper.go
similarity index 65%
rename from docs/assets/examples/footer/main.go
rename to docs/assets/examples/footer/paper.go
index df55e84e..6bce1a46 100644
--- a/docs/assets/examples/footer/main.go
+++ b/docs/assets/examples/footer/paper.go
@@ -1,8 +1,7 @@
-// Package main demonstrates a footer that repeats at the bottom of every page.
-package main
+// Package footer demonstrates a footer that repeats at the bottom of every page.
+package footer
import (
- "context"
"log"
"github.com/avdoseferovic/paper/pkg/consts"
@@ -19,24 +18,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/footer.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/footer.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the footer example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/footer/main_test.go b/docs/assets/examples/footer/paper_test.go
similarity index 93%
rename from docs/assets/examples/footer/main_test.go
rename to docs/assets/examples/footer/paper_test.go
index 94043861..c660524b 100644
--- a/docs/assets/examples/footer/main_test.go
+++ b/docs/assets/examples/footer/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package footer
import (
"testing"
diff --git a/docs/assets/examples/header/cmd/main.go b/docs/assets/examples/header/cmd/main.go
new file mode 100644
index 00000000..25297315
--- /dev/null
+++ b/docs/assets/examples/header/cmd/main.go
@@ -0,0 +1,28 @@
+// Command header writes the header example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/header/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/header"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/header.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/header.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/header/main.go b/docs/assets/examples/header/paper.go
similarity index 65%
rename from docs/assets/examples/header/main.go
rename to docs/assets/examples/header/paper.go
index e21e8340..bbda4136 100644
--- a/docs/assets/examples/header/main.go
+++ b/docs/assets/examples/header/paper.go
@@ -1,8 +1,7 @@
-// Package main demonstrates a header that repeats at the top of every page.
-package main
+// Package header demonstrates a header that repeats at the top of every page.
+package header
import (
- "context"
"log"
"github.com/avdoseferovic/paper/pkg/consts"
@@ -19,24 +18,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/header.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/header.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the header example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/header/main_test.go b/docs/assets/examples/header/paper_test.go
similarity index 93%
rename from docs/assets/examples/header/main_test.go
rename to docs/assets/examples/header/paper_test.go
index 793729fc..d2239248 100644
--- a/docs/assets/examples/header/main_test.go
+++ b/docs/assets/examples/header/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package header
import (
"testing"
diff --git a/docs/assets/examples/imagegrid/cmd/main.go b/docs/assets/examples/imagegrid/cmd/main.go
new file mode 100644
index 00000000..20df94d4
--- /dev/null
+++ b/docs/assets/examples/imagegrid/cmd/main.go
@@ -0,0 +1,28 @@
+// Command imagegrid writes the imagegrid example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/imagegrid/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/imagegrid"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/imagegrid.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/imagegrid.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/imagegrid/main.go b/docs/assets/examples/imagegrid/paper.go
similarity index 88%
rename from docs/assets/examples/imagegrid/main.go
rename to docs/assets/examples/imagegrid/paper.go
index b3eaf143..c57a57e1 100644
--- a/docs/assets/examples/imagegrid/main.go
+++ b/docs/assets/examples/imagegrid/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates placing images across a grid of columns.
-package main
+// Package imagegrid demonstrates placing images across a grid of columns.
+package imagegrid
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper"
@@ -16,24 +13,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/imagegrid.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/imagegrid.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the imagegrid example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/imagegrid/main_test.go b/docs/assets/examples/imagegrid/paper_test.go
similarity index 92%
rename from docs/assets/examples/imagegrid/main_test.go
rename to docs/assets/examples/imagegrid/paper_test.go
index 8bda45ec..8000a600 100644
--- a/docs/assets/examples/imagegrid/main_test.go
+++ b/docs/assets/examples/imagegrid/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package imagegrid
import (
"testing"
diff --git a/docs/assets/examples/line/cmd/main.go b/docs/assets/examples/line/cmd/main.go
new file mode 100644
index 00000000..4f6d0da8
--- /dev/null
+++ b/docs/assets/examples/line/cmd/main.go
@@ -0,0 +1,28 @@
+// Command line writes the line example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/line/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/line"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/linegrid.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/linegrid.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/line/main.go b/docs/assets/examples/line/paper.go
similarity index 83%
rename from docs/assets/examples/line/main.go
rename to docs/assets/examples/line/paper.go
index 8e57faad..7714ab9c 100644
--- a/docs/assets/examples/line/main.go
+++ b/docs/assets/examples/line/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates horizontal rules, including dashed and coloured styles.
-package main
+// Package line demonstrates horizontal rules, including dashed and coloured styles.
+package line
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/consts"
"github.com/avdoseferovic/paper/pkg/core"
@@ -17,24 +14,7 @@ import (
"github.com/avdoseferovic/paper/pkg/config"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/linegrid.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/linegrid.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the line example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/line/main_test.go b/docs/assets/examples/line/paper_test.go
similarity index 94%
rename from docs/assets/examples/line/main_test.go
rename to docs/assets/examples/line/paper_test.go
index 064e2b69..6df8f9a8 100644
--- a/docs/assets/examples/line/main_test.go
+++ b/docs/assets/examples/line/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package line
import (
"testing"
diff --git a/docs/assets/examples/list/cmd/main.go b/docs/assets/examples/list/cmd/main.go
new file mode 100644
index 00000000..a57dd29c
--- /dev/null
+++ b/docs/assets/examples/list/cmd/main.go
@@ -0,0 +1,28 @@
+// Command list writes the list example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/list/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/list"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/list.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/list.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/list/main.go b/docs/assets/examples/list/paper.go
similarity index 77%
rename from docs/assets/examples/list/main.go
rename to docs/assets/examples/list/paper.go
index 60a0292e..7e29a7cc 100644
--- a/docs/assets/examples/list/main.go
+++ b/docs/assets/examples/list/paper.go
@@ -1,8 +1,7 @@
-// Package main demonstrates rendering a slice of values as a table with a header row.
-package main
+// Package list demonstrates rendering a slice of values as a table with a header row.
+package list
import (
- "context"
"fmt"
"log"
@@ -24,24 +23,7 @@ var background = &props.Color{
Blue: 200,
}
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/list.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/list.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the list example document.
func GetPaper() core.Paper {
mrt := paper.New()
m := decorator.NewMetrics(mrt)
@@ -56,11 +38,14 @@ func GetPaper() core.Paper {
return m
}
+// Object is one key/value row rendered by the list example. It implements the
+// list component's interfaces so a slice of Objects can be built into a table.
type Object struct {
Key string
Value string
}
+// GetHeader is part of the list example.
func (o Object) GetHeader() core.Row {
return row.New(10).Add(
text.NewCol(4, "Key", props.Text{Style: fontstyle.Bold}),
@@ -68,6 +53,7 @@ func (o Object) GetHeader() core.Row {
)
}
+// GetContent is part of the list example.
func (o Object) GetContent(i int) core.Row {
r := row.New(5).Add(
text.NewCol(4, o.Key),
diff --git a/docs/assets/examples/list/main_test.go b/docs/assets/examples/list/paper_test.go
similarity index 94%
rename from docs/assets/examples/list/main_test.go
rename to docs/assets/examples/list/paper_test.go
index 9d2f8f32..f8c460a9 100644
--- a/docs/assets/examples/list/main_test.go
+++ b/docs/assets/examples/list/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package list
import (
"testing"
diff --git a/docs/assets/examples/lowmemory/cmd/main.go b/docs/assets/examples/lowmemory/cmd/main.go
new file mode 100644
index 00000000..2ff8d1cf
--- /dev/null
+++ b/docs/assets/examples/lowmemory/cmd/main.go
@@ -0,0 +1,28 @@
+// Command lowmemory writes the lowmemory example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/lowmemory/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/lowmemory"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/lowmemory.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/lowmemory.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/lowmemory/main.go b/docs/assets/examples/lowmemory/paper.go
similarity index 56%
rename from docs/assets/examples/lowmemory/main.go
rename to docs/assets/examples/lowmemory/paper.go
index aafed98b..2aa50f08 100644
--- a/docs/assets/examples/lowmemory/main.go
+++ b/docs/assets/examples/lowmemory/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates low-memory mode, which writes pages out as they are built.
-package main
+// Package lowmemory demonstrates low-memory mode, which writes pages out as they are built.
+package lowmemory
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper"
@@ -16,24 +13,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/lowmemory.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/lowmemory.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the lowmemory example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithSequentialLowMemoryMode(7).
diff --git a/docs/assets/examples/lowmemory/main_test.go b/docs/assets/examples/lowmemory/paper_test.go
similarity index 92%
rename from docs/assets/examples/lowmemory/main_test.go
rename to docs/assets/examples/lowmemory/paper_test.go
index 2fe47f35..104d38c3 100644
--- a/docs/assets/examples/lowmemory/main_test.go
+++ b/docs/assets/examples/lowmemory/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package lowmemory
import (
"testing"
diff --git a/docs/assets/examples/margins/cmd/main.go b/docs/assets/examples/margins/cmd/main.go
new file mode 100644
index 00000000..b078d284
--- /dev/null
+++ b/docs/assets/examples/margins/cmd/main.go
@@ -0,0 +1,28 @@
+// Command margins writes the margins example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/margins/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/margins"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/margins.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/margins.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/margins/main.go b/docs/assets/examples/margins/paper.go
similarity index 71%
rename from docs/assets/examples/margins/main.go
rename to docs/assets/examples/margins/paper.go
index be209473..e6e28701 100644
--- a/docs/assets/examples/margins/main.go
+++ b/docs/assets/examples/margins/paper.go
@@ -1,8 +1,7 @@
-// Package main demonstrates setting the page margins.
-package main
+// Package margins demonstrates setting the page margins.
+package margins
import (
- "context"
"log"
"github.com/avdoseferovic/paper/pkg/components/row"
@@ -17,24 +16,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/margins.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/margins.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the margins example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithTopMargin(20).
diff --git a/docs/assets/examples/margins/main_test.go b/docs/assets/examples/margins/paper_test.go
similarity index 93%
rename from docs/assets/examples/margins/main_test.go
rename to docs/assets/examples/margins/paper_test.go
index 7591cde9..7da68e36 100644
--- a/docs/assets/examples/margins/main_test.go
+++ b/docs/assets/examples/margins/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package margins
import (
"testing"
diff --git a/docs/assets/examples/maxgridsum/cmd/main.go b/docs/assets/examples/maxgridsum/cmd/main.go
new file mode 100644
index 00000000..86ab3839
--- /dev/null
+++ b/docs/assets/examples/maxgridsum/cmd/main.go
@@ -0,0 +1,28 @@
+// Command maxgridsum writes the maxgridsum example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/maxgridsum/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/maxgridsum"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/maxgridsum.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/maxgridsum.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/maxgridsum/main.go b/docs/assets/examples/maxgridsum/paper.go
similarity index 69%
rename from docs/assets/examples/maxgridsum/main.go
rename to docs/assets/examples/maxgridsum/paper.go
index 51baee16..bc86cc15 100644
--- a/docs/assets/examples/maxgridsum/main.go
+++ b/docs/assets/examples/maxgridsum/paper.go
@@ -1,10 +1,8 @@
-// Package main demonstrates changing how many grid units a row spans.
-package main
+// Package maxgridsum demonstrates changing how many grid units a row spans.
+package maxgridsum
import (
- "context"
"fmt"
- "log"
"github.com/avdoseferovic/paper"
"github.com/avdoseferovic/paper/pkg/decorator"
@@ -18,24 +16,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/maxgridsum.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/maxgridsum.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the maxgridsum example document.
func GetPaper() core.Paper {
gridSum := 14
cfg := config.NewBuilder().
diff --git a/docs/assets/examples/maxgridsum/main_test.go b/docs/assets/examples/maxgridsum/paper_test.go
similarity index 92%
rename from docs/assets/examples/maxgridsum/main_test.go
rename to docs/assets/examples/maxgridsum/paper_test.go
index 2c2f8ca5..38a93cd2 100644
--- a/docs/assets/examples/maxgridsum/main_test.go
+++ b/docs/assets/examples/maxgridsum/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package maxgridsum
import (
"testing"
diff --git a/docs/assets/examples/mergepdf/main.go b/docs/assets/examples/mergepdf/cmd/main.go
similarity index 51%
rename from docs/assets/examples/mergepdf/main.go
rename to docs/assets/examples/mergepdf/cmd/main.go
index 0fd8768b..c6645751 100644
--- a/docs/assets/examples/mergepdf/main.go
+++ b/docs/assets/examples/mergepdf/cmd/main.go
@@ -1,4 +1,5 @@
-// Package main demonstrates merging a generated document with another PDF.
+// Command mergepdf writes the mergepdf example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/mergepdf/cmd
package main
import (
@@ -6,17 +7,11 @@ import (
"log"
"os"
- "github.com/avdoseferovic/paper/pkg/core"
-
- "github.com/avdoseferovic/paper"
- "github.com/avdoseferovic/paper/pkg/decorator"
-
- "github.com/avdoseferovic/paper/pkg/components/text"
- "github.com/avdoseferovic/paper/pkg/config"
+ example "github.com/avdoseferovic/paper/docs/assets/examples/mergepdf"
)
func main() {
- m := GetPaper()
+ m := example.GetPaper()
document, err := m.Generate(context.Background())
if err != nil {
log.Fatal(err.Error())
@@ -42,18 +37,3 @@ func main() {
log.Fatal(err.Error())
}
}
-
-func GetPaper() core.Paper {
- cfg := config.NewBuilder().
- WithPageNumber().
- Build()
-
- mrt := paper.New(cfg)
- m := decorator.NewMetrics(mrt)
-
- for range 50 {
- m.AddRows(text.NewRow(20, "content"))
- }
-
- return m
-}
diff --git a/docs/assets/examples/mergepdf/paper.go b/docs/assets/examples/mergepdf/paper.go
new file mode 100644
index 00000000..5b6cd646
--- /dev/null
+++ b/docs/assets/examples/mergepdf/paper.go
@@ -0,0 +1,28 @@
+// Package mergepdf demonstrates merging a generated document with another PDF.
+package mergepdf
+
+import (
+ "github.com/avdoseferovic/paper/pkg/core"
+
+ "github.com/avdoseferovic/paper"
+ "github.com/avdoseferovic/paper/pkg/decorator"
+
+ "github.com/avdoseferovic/paper/pkg/components/text"
+ "github.com/avdoseferovic/paper/pkg/config"
+)
+
+// GetPaper builds the mergepdf example document.
+func GetPaper() core.Paper {
+ cfg := config.NewBuilder().
+ WithPageNumber().
+ Build()
+
+ mrt := paper.New(cfg)
+ m := decorator.NewMetrics(mrt)
+
+ for range 50 {
+ m.AddRows(text.NewRow(20, "content"))
+ }
+
+ return m
+}
diff --git a/docs/assets/examples/mergepdf/main_test.go b/docs/assets/examples/mergepdf/paper_test.go
similarity index 93%
rename from docs/assets/examples/mergepdf/main_test.go
rename to docs/assets/examples/mergepdf/paper_test.go
index 5cae0d7f..5f46e821 100644
--- a/docs/assets/examples/mergepdf/main_test.go
+++ b/docs/assets/examples/mergepdf/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package mergepdf
import (
"testing"
diff --git a/docs/assets/examples/metadatas/cmd/main.go b/docs/assets/examples/metadatas/cmd/main.go
new file mode 100644
index 00000000..79bc7d4b
--- /dev/null
+++ b/docs/assets/examples/metadatas/cmd/main.go
@@ -0,0 +1,28 @@
+// Command metadatas writes the metadatas example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/metadatas/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/metadatas"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/metadatas.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/metadatas.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/metadatas/main.go b/docs/assets/examples/metadatas/paper.go
similarity index 57%
rename from docs/assets/examples/metadatas/main.go
rename to docs/assets/examples/metadatas/paper.go
index 3544b733..3d1e9900 100644
--- a/docs/assets/examples/metadatas/main.go
+++ b/docs/assets/examples/metadatas/paper.go
@@ -1,9 +1,7 @@
-// Package main demonstrates setting document metadata such as title, author, and subject.
-package main
+// Package metadatas demonstrates setting document metadata such as title, author, and subject.
+package metadatas
import (
- "context"
- "log"
"time"
"github.com/avdoseferovic/paper/pkg/core"
@@ -15,24 +13,7 @@ import (
"github.com/avdoseferovic/paper/pkg/config"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/metadatas.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/metadatas.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the metadatas example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithAuthor("author", false).
diff --git a/docs/assets/examples/metadatas/main_test.go b/docs/assets/examples/metadatas/paper_test.go
similarity index 92%
rename from docs/assets/examples/metadatas/main_test.go
rename to docs/assets/examples/metadatas/paper_test.go
index 72a223cb..7cffcf17 100644
--- a/docs/assets/examples/metadatas/main_test.go
+++ b/docs/assets/examples/metadatas/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package metadatas
import (
"testing"
diff --git a/docs/assets/examples/orientation/cmd/main.go b/docs/assets/examples/orientation/cmd/main.go
new file mode 100644
index 00000000..650554c3
--- /dev/null
+++ b/docs/assets/examples/orientation/cmd/main.go
@@ -0,0 +1,28 @@
+// Command orientation writes the orientation example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/orientation/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/orientation"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/orientation.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/orientation.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/orientation/main.go b/docs/assets/examples/orientation/paper.go
similarity index 55%
rename from docs/assets/examples/orientation/main.go
rename to docs/assets/examples/orientation/paper.go
index d0ae6b3a..b09e6916 100644
--- a/docs/assets/examples/orientation/main.go
+++ b/docs/assets/examples/orientation/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates landscape orientation.
-package main
+// Package orientation demonstrates landscape orientation.
+package orientation
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/consts"
"github.com/avdoseferovic/paper/pkg/core"
@@ -15,24 +12,7 @@ import (
"github.com/avdoseferovic/paper/pkg/config"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/orientation.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/orientation.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the orientation example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithOrientation(consts.OrientationHorizontal).
diff --git a/docs/assets/examples/orientation/main_test.go b/docs/assets/examples/orientation/paper_test.go
similarity index 92%
rename from docs/assets/examples/orientation/main_test.go
rename to docs/assets/examples/orientation/paper_test.go
index bae6c47b..aaa71411 100644
--- a/docs/assets/examples/orientation/main_test.go
+++ b/docs/assets/examples/orientation/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package orientation
import (
"testing"
diff --git a/docs/assets/examples/pagenumber/cmd/main.go b/docs/assets/examples/pagenumber/cmd/main.go
new file mode 100644
index 00000000..161fa0fa
--- /dev/null
+++ b/docs/assets/examples/pagenumber/cmd/main.go
@@ -0,0 +1,28 @@
+// Command pagenumber writes the pagenumber example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/pagenumber/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/pagenumber"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/pagenumber.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/pagenumber.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/pagenumber/main.go b/docs/assets/examples/pagenumber/paper.go
similarity index 66%
rename from docs/assets/examples/pagenumber/main.go
rename to docs/assets/examples/pagenumber/paper.go
index 3e089c8f..9bd82194 100644
--- a/docs/assets/examples/pagenumber/main.go
+++ b/docs/assets/examples/pagenumber/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates page numbering in a footer.
-package main
+// Package pagenumber demonstrates page numbering in a footer.
+package pagenumber
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/consts"
"github.com/avdoseferovic/paper/pkg/consts/fontstyle"
@@ -18,24 +15,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/pagenumber.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/pagenumber.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the pagenumber example document.
func GetPaper() core.Paper {
pageNumber := props.PageNumber{
Pattern: "Page {current} of {total}",
diff --git a/docs/assets/examples/pagenumber/main_test.go b/docs/assets/examples/pagenumber/paper_test.go
similarity index 92%
rename from docs/assets/examples/pagenumber/main_test.go
rename to docs/assets/examples/pagenumber/paper_test.go
index e29462f3..1f9d6428 100644
--- a/docs/assets/examples/pagenumber/main_test.go
+++ b/docs/assets/examples/pagenumber/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package pagenumber
import (
"testing"
diff --git a/docs/assets/examples/parallelism/cmd/main.go b/docs/assets/examples/parallelism/cmd/main.go
new file mode 100644
index 00000000..85a1dcda
--- /dev/null
+++ b/docs/assets/examples/parallelism/cmd/main.go
@@ -0,0 +1,28 @@
+// Command parallelism writes the parallelism example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/parallelism/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/parallelism"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/parallelism.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/parallelism.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/parallelism/main.go b/docs/assets/examples/parallelism/paper.go
similarity index 57%
rename from docs/assets/examples/parallelism/main.go
rename to docs/assets/examples/parallelism/paper.go
index f2539479..fd798fda 100644
--- a/docs/assets/examples/parallelism/main.go
+++ b/docs/assets/examples/parallelism/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates generating page chunks concurrently.
-package main
+// Package parallelism demonstrates generating page chunks concurrently.
+package parallelism
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper"
@@ -16,24 +13,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/parallelism.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/parallelism.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the parallelism example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithParallelPagesMode(7).
diff --git a/docs/assets/examples/parallelism/main_test.go b/docs/assets/examples/parallelism/paper_test.go
similarity index 92%
rename from docs/assets/examples/parallelism/main_test.go
rename to docs/assets/examples/parallelism/paper_test.go
index ab0a74ce..ba1b301d 100644
--- a/docs/assets/examples/parallelism/main_test.go
+++ b/docs/assets/examples/parallelism/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package parallelism
import (
"testing"
diff --git a/docs/assets/examples/protection/cmd/main.go b/docs/assets/examples/protection/cmd/main.go
new file mode 100644
index 00000000..ccb40ae0
--- /dev/null
+++ b/docs/assets/examples/protection/cmd/main.go
@@ -0,0 +1,28 @@
+// Command protection writes the protection example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/protection/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/protection"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/protection.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/protection.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/protection/main.go b/docs/assets/examples/protection/paper.go
similarity index 54%
rename from docs/assets/examples/protection/main.go
rename to docs/assets/examples/protection/paper.go
index 19295561..c8527137 100644
--- a/docs/assets/examples/protection/main.go
+++ b/docs/assets/examples/protection/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates password protection and permission flags.
-package main
+// Package protection demonstrates password protection and permission flags.
+package protection
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper"
@@ -16,24 +13,7 @@ import (
"github.com/avdoseferovic/paper/pkg/config"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/protection.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/protection.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the protection example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithProtection(protection.None, "user", "owner").
diff --git a/docs/assets/examples/protection/main_test.go b/docs/assets/examples/protection/paper_test.go
similarity index 92%
rename from docs/assets/examples/protection/main_test.go
rename to docs/assets/examples/protection/paper_test.go
index 7421d780..e95c1d8a 100644
--- a/docs/assets/examples/protection/main_test.go
+++ b/docs/assets/examples/protection/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package protection
import (
"testing"
diff --git a/docs/assets/examples/qrgrid/cmd/main.go b/docs/assets/examples/qrgrid/cmd/main.go
new file mode 100644
index 00000000..e28e2d7a
--- /dev/null
+++ b/docs/assets/examples/qrgrid/cmd/main.go
@@ -0,0 +1,28 @@
+// Command qrgrid writes the qrgrid example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/qrgrid/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/qrgrid"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/qrgrid.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/qrgrid.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/qrgrid/main.go b/docs/assets/examples/qrgrid/paper.go
similarity index 83%
rename from docs/assets/examples/qrgrid/main.go
rename to docs/assets/examples/qrgrid/paper.go
index 1a3340b4..72889ff1 100644
--- a/docs/assets/examples/qrgrid/main.go
+++ b/docs/assets/examples/qrgrid/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates placing QR codes across a grid of columns.
-package main
+// Package qrgrid demonstrates placing QR codes across a grid of columns.
+package qrgrid
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper"
@@ -16,24 +13,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/qrgrid.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/qrgrid.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the qrgrid example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/qrgrid/main_test.go b/docs/assets/examples/qrgrid/paper_test.go
similarity index 93%
rename from docs/assets/examples/qrgrid/main_test.go
rename to docs/assets/examples/qrgrid/paper_test.go
index 81bd05b8..01a9b342 100644
--- a/docs/assets/examples/qrgrid/main_test.go
+++ b/docs/assets/examples/qrgrid/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package qrgrid
import (
"testing"
diff --git a/docs/assets/examples/signaturegrid/cmd/main.go b/docs/assets/examples/signaturegrid/cmd/main.go
new file mode 100644
index 00000000..729ec4fe
--- /dev/null
+++ b/docs/assets/examples/signaturegrid/cmd/main.go
@@ -0,0 +1,28 @@
+// Command signaturegrid writes the signaturegrid example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/signaturegrid/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/signaturegrid"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/signaturegrid.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/signaturegrid.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/signaturegrid/main.go b/docs/assets/examples/signaturegrid/paper.go
similarity index 77%
rename from docs/assets/examples/signaturegrid/main.go
rename to docs/assets/examples/signaturegrid/paper.go
index b9fc11bb..80aa4e8a 100644
--- a/docs/assets/examples/signaturegrid/main.go
+++ b/docs/assets/examples/signaturegrid/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates signature lines across a grid of columns.
-package main
+// Package signaturegrid demonstrates signature lines across a grid of columns.
+package signaturegrid
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/consts"
"github.com/avdoseferovic/paper/pkg/core"
@@ -18,24 +15,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/signaturegrid.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/signaturegrid.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the signaturegrid example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/signaturegrid/main_test.go b/docs/assets/examples/signaturegrid/paper_test.go
similarity index 91%
rename from docs/assets/examples/signaturegrid/main_test.go
rename to docs/assets/examples/signaturegrid/paper_test.go
index dff524a5..600fd372 100644
--- a/docs/assets/examples/signaturegrid/main_test.go
+++ b/docs/assets/examples/signaturegrid/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package signaturegrid
import (
"testing"
diff --git a/docs/assets/examples/simplest/cmd/main.go b/docs/assets/examples/simplest/cmd/main.go
new file mode 100644
index 00000000..dccf17bc
--- /dev/null
+++ b/docs/assets/examples/simplest/cmd/main.go
@@ -0,0 +1,23 @@
+// Command simplest writes the simplest example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/simplest/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/simplest"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = document.Save("docs/assets/pdf/simplest.pdf")
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/docs/assets/examples/simplest/main.go b/docs/assets/examples/simplest/paper.go
similarity index 74%
rename from docs/assets/examples/simplest/main.go
rename to docs/assets/examples/simplest/paper.go
index b37461a1..f3355aa6 100644
--- a/docs/assets/examples/simplest/main.go
+++ b/docs/assets/examples/simplest/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates the smallest document: one row with one line of text.
-package main
+// Package simplest demonstrates the smallest document: one row with one line of text.
+package simplest
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/components/checkbox"
"github.com/avdoseferovic/paper/pkg/core"
@@ -20,19 +17,7 @@ import (
"github.com/avdoseferovic/paper/pkg/components/text"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err)
- }
-
- err = document.Save("docs/assets/pdf/simplest.pdf")
- if err != nil {
- log.Fatal(err)
- }
-}
-
+// GetPaper builds the simplest example document.
func GetPaper() core.Paper {
m := paper.New()
diff --git a/docs/assets/examples/simplest/main_test.go b/docs/assets/examples/simplest/paper_test.go
similarity index 93%
rename from docs/assets/examples/simplest/main_test.go
rename to docs/assets/examples/simplest/paper_test.go
index ff333621..4a340e59 100644
--- a/docs/assets/examples/simplest/main_test.go
+++ b/docs/assets/examples/simplest/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package simplest
import (
"testing"
diff --git a/docs/assets/examples/textgrid/cmd/main.go b/docs/assets/examples/textgrid/cmd/main.go
new file mode 100644
index 00000000..c361dd73
--- /dev/null
+++ b/docs/assets/examples/textgrid/cmd/main.go
@@ -0,0 +1,28 @@
+// Command textgrid writes the textgrid example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/textgrid/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/textgrid"
+)
+
+func main() {
+ m := example.GetPaper()
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/textgrid.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.GetReport().Save("docs/assets/text/textgrid.txt")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/textgrid/main.go b/docs/assets/examples/textgrid/paper.go
similarity index 89%
rename from docs/assets/examples/textgrid/main.go
rename to docs/assets/examples/textgrid/paper.go
index 14e86e9f..2a4d062b 100644
--- a/docs/assets/examples/textgrid/main.go
+++ b/docs/assets/examples/textgrid/paper.go
@@ -1,10 +1,7 @@
-// Package main demonstrates text components across a grid of columns.
-package main
+// Package textgrid demonstrates text components across a grid of columns.
+package textgrid
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper/pkg/consts"
"github.com/avdoseferovic/paper/pkg/consts/fontstyle"
@@ -19,24 +16,7 @@ import (
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
- m := GetPaper()
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/textgrid.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.GetReport().Save("docs/assets/text/textgrid.txt")
- if err != nil {
- log.Fatal(err.Error())
- }
-}
-
+// GetPaper builds the textgrid example document.
func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithDebug(true).
diff --git a/docs/assets/examples/textgrid/main_test.go b/docs/assets/examples/textgrid/paper_test.go
similarity index 93%
rename from docs/assets/examples/textgrid/main_test.go
rename to docs/assets/examples/textgrid/paper_test.go
index b7fa46e1..7288e249 100644
--- a/docs/assets/examples/textgrid/main_test.go
+++ b/docs/assets/examples/textgrid/paper_test.go
@@ -1,4 +1,4 @@
-package main
+package textgrid
import (
"testing"
diff --git a/docs/assets/examples/watermark/cmd/main.go b/docs/assets/examples/watermark/cmd/main.go
new file mode 100644
index 00000000..4ccef16a
--- /dev/null
+++ b/docs/assets/examples/watermark/cmd/main.go
@@ -0,0 +1,24 @@
+// Command watermark writes the watermark example's PDF into docs/assets/pdf.
+// Run it from the repository root: go run ./docs/assets/examples/watermark/cmd
+package main
+
+import (
+ "context"
+ "log"
+
+ example "github.com/avdoseferovic/paper/docs/assets/examples/watermark"
+)
+
+func main() {
+ m := example.GetPaper()
+
+ document, err := m.Generate(context.Background())
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ err = document.Save("docs/assets/pdf/watermark.pdf")
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+}
diff --git a/docs/assets/examples/watermark/main.go b/docs/assets/examples/watermark/paper.go
similarity index 62%
rename from docs/assets/examples/watermark/main.go
rename to docs/assets/examples/watermark/paper.go
index 4ce9e29f..99c9d2e7 100644
--- a/docs/assets/examples/watermark/main.go
+++ b/docs/assets/examples/watermark/paper.go
@@ -1,19 +1,18 @@
-// Package main demonstrates the per-page text watermark: translucent
+// Package watermark demonstrates the per-page text watermark: translucent
// diagonal text drawn under the content of every page.
-package main
+package watermark
import (
- "context"
- "log"
-
"github.com/avdoseferovic/paper"
"github.com/avdoseferovic/paper/pkg/components/col"
"github.com/avdoseferovic/paper/pkg/components/text"
"github.com/avdoseferovic/paper/pkg/config"
+ "github.com/avdoseferovic/paper/pkg/core"
"github.com/avdoseferovic/paper/pkg/props"
)
-func main() {
+// GetPaper builds the watermark example document.
+func GetPaper() core.Paper {
cfg := config.NewBuilder().
WithWatermark("DRAFT").
Build()
@@ -23,13 +22,5 @@ func main() {
m.AddRow(250, col.New(12).Add(text.New("Body content flows over the watermark.", props.Text{Top: 5})))
}
- document, err := m.Generate(context.Background())
- if err != nil {
- log.Fatal(err.Error())
- }
-
- err = document.Save("docs/assets/pdf/watermark.pdf")
- if err != nil {
- log.Fatal(err.Error())
- }
+ return m
}
diff --git a/docs/assets/examples/watermark/paper_test.go b/docs/assets/examples/watermark/paper_test.go
new file mode 100644
index 00000000..f58c1e49
--- /dev/null
+++ b/docs/assets/examples/watermark/paper_test.go
@@ -0,0 +1,16 @@
+package watermark
+
+import (
+ "testing"
+
+ "github.com/avdoseferovic/paper/internal/test"
+)
+
+func TestGetPaper(t *testing.T) {
+ t.Parallel()
+ // Act
+ sut := GetPaper()
+
+ // Assert
+ test.New(t).Assert(sut.GetStructure()).Equals("examples/watermark.json")
+}
diff --git a/docs/assets/js/paper-fs.js b/docs/assets/js/paper-fs.js
new file mode 100644
index 00000000..c3f94a11
--- /dev/null
+++ b/docs/assets/js/paper-fs.js
@@ -0,0 +1,261 @@
+// paper-fs.js — a read-only, in-memory filesystem for Go's js/wasm runtime.
+//
+// Go's `os.ReadFile` in a browser goes through `globalThis.fs`, which
+// `wasm_exec.js` otherwise fills with stubs that answer ENOSYS to everything.
+// That guard is `if (!globalThis.fs)`, so defining our own object *before*
+// `wasm_exec.js` loads replaces the stub wholesale and lets the Paper library
+// read images with no change to the library or to the documented example code.
+//
+// Load order matters:
+//
+//
+//
+//
+// Only `fs` is defined here. `wasm_exec.js` installs `globalThis.process` and
+// `globalThis.path` behind their own separate guards and those stubs are fine —
+// `syscall.Open` calls `path.resolve()`, which the stub implements, and nothing
+// on the read path calls `process.cwd()`.
+//
+// Files are registered by the exact path Go will ask for:
+//
+// paperFS.set("docs/assets/images/biplane.jpg", bytes);
+//
+// Paths are treated as opaque keys, not as a hierarchy — `syscall.Open` does not
+// prepend the working directory, so whatever string the Go code passes to
+// `os.ReadFile` is what arrives here.
+(function () {
+ "use strict";
+
+ // Regular-file mode bits (S_IFREG). Without this, Go reads the entry as a
+ // directory and follows a readdir path that has no meaning here.
+ var S_IFREG = 0o100000;
+
+ // Lowest fd we hand out. 0/1/2 stay reserved for stdin/stdout/stderr.
+ var FIRST_FD = 3;
+
+ var files = new Map(); // path -> Uint8Array
+ var open = new Map(); // fd -> { path, bytes, cursor }
+ var nextFd = FIRST_FD;
+
+ // Line buffers for fd 1 and 2, so Go's log output arrives at the console in
+ // whole lines rather than one call per write.
+ var outputBuf = { 1: "", 2: "" };
+ var decoder = new TextDecoder("utf-8");
+
+ function err(code, message) {
+ var e = new Error(message || code);
+ e.code = code;
+ return e;
+ }
+
+ function enosys() {
+ return err("ENOSYS", "not implemented");
+ }
+
+ // Go's syscall.setStat reads every one of these fields with `.Int()`, which
+ // panics on undefined. Returning a partial object kills the wasm instance on
+ // the first stat, so all thirteen are always present.
+ function statFor(bytes) {
+ return {
+ dev: 0,
+ ino: 0,
+ mode: S_IFREG | 0o444,
+ nlink: 1,
+ uid: 0,
+ gid: 0,
+ rdev: 0,
+ size: bytes.length,
+ blksize: 4096,
+ blocks: Math.ceil(bytes.length / 512),
+ atimeMs: 0,
+ mtimeMs: 0,
+ ctimeMs: 0,
+ isDirectory: function () {
+ return false;
+ },
+ };
+ }
+
+ function flush(fd) {
+ var nl = outputBuf[fd].lastIndexOf("\n");
+ if (nl === -1) {
+ return;
+ }
+ var line = outputBuf[fd].substring(0, nl);
+ outputBuf[fd] = outputBuf[fd].substring(nl + 1);
+ if (fd === 2) {
+ console.error(line);
+ } else {
+ console.log(line);
+ }
+ }
+
+ globalThis.fs = {
+ // The write flags keep wasm_exec.js's -1 sentinels. A read-only open
+ // passes O_RDONLY (0), so none of these bits are ever set and the -1
+ // values are never compared against anything meaningful.
+ constants: {
+ O_WRONLY: -1,
+ O_RDWR: -1,
+ O_CREAT: -1,
+ O_TRUNC: -1,
+ O_APPEND: -1,
+ O_EXCL: -1,
+ O_DIRECTORY: -1,
+ },
+
+ writeSync: function (fd, buf) {
+ if (fd !== 1 && fd !== 2) {
+ throw enosys();
+ }
+ outputBuf[fd] += decoder.decode(buf);
+ flush(fd);
+ return buf.length;
+ },
+
+ write: function (fd, buf, offset, length, position, callback) {
+ if (offset !== 0 || length !== buf.length || position !== null) {
+ callback(enosys());
+ return;
+ }
+ var n = this.writeSync(fd, buf);
+ callback(null, n);
+ },
+
+ open: function (path, flags, mode, callback) {
+ var bytes = files.get(path);
+ if (bytes === undefined) {
+ callback(err("ENOENT", "no such file or directory: " + path));
+ return;
+ }
+ var fd = nextFd++;
+ open.set(fd, { path: path, bytes: bytes, cursor: 0 });
+ callback(null, fd);
+ },
+
+ close: function (fd, callback) {
+ open.delete(fd);
+ callback(null);
+ },
+
+ // `syscall.Read` passes position === null, meaning "continue from this
+ // descriptor's own cursor and advance it". `syscall.Pread` passes a
+ // number, which reads from that offset and must leave the cursor alone.
+ // Returning 0 is how EOF is signalled; without it os.ReadFile loops.
+ read: function (fd, buffer, offset, length, position, callback) {
+ var f = open.get(fd);
+ if (f === undefined) {
+ callback(err("EBADF", "bad file descriptor"));
+ return;
+ }
+
+ var start =
+ position === null || position === undefined ? f.cursor : position;
+ if (start >= f.bytes.length) {
+ callback(null, 0, buffer);
+ return;
+ }
+
+ var end = Math.min(start + length, f.bytes.length);
+ var n = end - start;
+ buffer.set(f.bytes.subarray(start, end), offset);
+
+ if (position === null || position === undefined) {
+ f.cursor = end;
+ }
+ callback(null, n, buffer);
+ },
+
+ fstat: function (fd, callback) {
+ var f = open.get(fd);
+ if (f === undefined) {
+ callback(err("EBADF", "bad file descriptor"));
+ return;
+ }
+ callback(null, statFor(f.bytes));
+ },
+
+ stat: function (path, callback) {
+ var bytes = files.get(path);
+ if (bytes === undefined) {
+ callback(err("ENOENT", "no such file or directory: " + path));
+ return;
+ }
+ callback(null, statFor(bytes));
+ },
+
+ lstat: function (path, callback) {
+ this.stat(path, callback);
+ },
+
+ // Reached only if a stat wrongly reported a directory. ENOTDIR makes that
+ // mistake obvious instead of looking like a generic unsupported call.
+ readdir: function (path, callback) {
+ callback(err("ENOTDIR", "not a directory: " + path));
+ },
+
+ chmod: function (path, mode, callback) {
+ callback(enosys());
+ },
+ chown: function (path, uid, gid, callback) {
+ callback(enosys());
+ },
+ fchmod: function (fd, mode, callback) {
+ callback(enosys());
+ },
+ fchown: function (fd, uid, gid, callback) {
+ callback(enosys());
+ },
+ fsync: function (fd, callback) {
+ callback(null);
+ },
+ ftruncate: function (fd, length, callback) {
+ callback(enosys());
+ },
+ lchown: function (path, uid, gid, callback) {
+ callback(enosys());
+ },
+ link: function (path, link, callback) {
+ callback(enosys());
+ },
+ mkdir: function (path, perm, callback) {
+ callback(enosys());
+ },
+ readlink: function (path, callback) {
+ callback(enosys());
+ },
+ rename: function (from, to, callback) {
+ callback(enosys());
+ },
+ rmdir: function (path, callback) {
+ callback(enosys());
+ },
+ symlink: function (path, link, callback) {
+ callback(enosys());
+ },
+ truncate: function (path, length, callback) {
+ callback(enosys());
+ },
+ unlink: function (path, callback) {
+ callback(enosys());
+ },
+ utimes: function (path, atime, mtime, callback) {
+ callback(enosys());
+ },
+ };
+
+ globalThis.paperFS = {
+ set: function (path, bytes) {
+ files.set(
+ path,
+ bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes),
+ );
+ },
+ has: function (path) {
+ return files.has(path);
+ },
+ clear: function () {
+ files.clear();
+ },
+ };
+})();
diff --git a/docs/assets/js/paper-fs.test.mjs b/docs/assets/js/paper-fs.test.mjs
new file mode 100644
index 00000000..f0038a40
--- /dev/null
+++ b/docs/assets/js/paper-fs.test.mjs
@@ -0,0 +1,170 @@
+// Tests for paper-fs.js, the in-memory filesystem Go's js/wasm runtime calls.
+//
+// node --test docs/assets/js/
+//
+// These pin the contract that syscall/fs_js.go actually relies on. Getting any
+// of it wrong fails in a way that is easy to miss: the renderer records an
+// image-load issue, draws an error box, and still returns a valid PDF, so a
+// "did a PDF come out?" check passes with the shim completely broken.
+//
+// No browser needed — the shim only touches globalThis, so plain node runs it.
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { test, beforeEach } from "node:test";
+
+const SOURCE = new URL("./paper-fs.js", import.meta.url);
+
+// paper-fs.js is a plain script (an IIFE assigning to globalThis), not a module,
+// so it is evaluated rather than imported. Re-evaluating resets its state.
+function loadShim() {
+ delete globalThis.fs;
+ delete globalThis.paperFS;
+ new Function(readFileSync(SOURCE, "utf8"))();
+}
+
+// Promisified wrappers over the Node-style callbacks Go uses.
+const call = (method, ...args) =>
+ new Promise((resolve, reject) => {
+ globalThis.fs[method](...args, (err, ...rest) =>
+ err ? reject(err) : resolve(rest.length > 1 ? rest : rest[0]),
+ );
+ });
+
+const open = (path) => call("open", path, 0, 0o444);
+const close = (fd) => call("close", fd);
+const fstat = (fd) => call("fstat", fd);
+const read = (fd, buf, offset, length, position) =>
+ call("read", fd, buf, offset, length, position);
+
+beforeEach(loadShim);
+
+test("open reports ENOENT for an unregistered path", async () => {
+ await assert.rejects(open("missing.bin"), (err) => err.code === "ENOENT");
+});
+
+test("read and close reject a bad file descriptor", async () => {
+ await assert.rejects(
+ read(999, new Uint8Array(4), 0, 4, null),
+ (err) => err.code === "EBADF",
+ );
+});
+
+test("fstat exposes every field syscall.setStat reads", async () => {
+ globalThis.paperFS.set("a.bin", new Uint8Array([1, 2, 3]));
+ const fd = await open("a.bin");
+ const stat = await fstat(fd);
+
+ // setStat calls .Int() on each of these; .Int() on undefined panics and takes
+ // the whole wasm instance with it.
+ for (const field of [
+ "dev", "ino", "mode", "nlink", "uid", "gid", "rdev", "size",
+ "blksize", "blocks", "atimeMs", "mtimeMs", "ctimeMs",
+ ]) {
+ assert.equal(typeof stat[field], "number", `${field} must be a number`);
+ }
+ assert.equal(stat.size, 3);
+ // syscall.Open always follows open with fstat + isDirectory(), and treats a
+ // true result as a directory it should readdir.
+ assert.equal(typeof stat.isDirectory, "function");
+ assert.equal(stat.isDirectory(), false);
+ await close(fd);
+});
+
+test("sequential reads advance the cursor and signal EOF with 0", async () => {
+ const data = Uint8Array.from({ length: 1000 }, (_, i) => i % 251);
+ globalThis.paperFS.set("big.bin", data);
+
+ const fd = await open("big.bin");
+ const out = new Uint8Array(data.length);
+ const chunk = new Uint8Array(300);
+ let total = 0;
+
+ // position === null means "continue from this descriptor's cursor".
+ for (;;) {
+ const [n] = await read(fd, chunk, 0, chunk.length, null);
+ if (n === 0) break; // os.ReadFile loops forever if EOF is never reported
+ out.set(chunk.subarray(0, n), total);
+ total += n;
+ assert.ok(total <= data.length, "read past end of file");
+ }
+
+ assert.equal(total, data.length);
+ assert.deepEqual(out, data);
+ await close(fd);
+});
+
+test("a positioned read does not move the cursor", async () => {
+ const data = Uint8Array.from({ length: 100 }, (_, i) => i);
+ globalThis.paperFS.set("p.bin", data);
+ const fd = await open("p.bin");
+
+ const buf = new Uint8Array(10);
+ const [n] = await read(fd, buf, 0, 10, 50); // syscall.Pread passes a number
+ assert.equal(n, 10);
+ assert.deepEqual(buf, data.subarray(50, 60));
+
+ // The cursor must still be at 0, so the next cursor-read starts at the top.
+ const [m] = await read(fd, buf, 0, 10, null);
+ assert.equal(m, 10);
+ assert.deepEqual(buf, data.subarray(0, 10));
+ await close(fd);
+});
+
+test("an empty file reads as 0 bytes immediately", async () => {
+ globalThis.paperFS.set("empty.bin", new Uint8Array([]));
+ const fd = await open("empty.bin");
+ assert.equal((await fstat(fd)).size, 0);
+ const [n] = await read(fd, new Uint8Array(8), 0, 8, null);
+ assert.equal(n, 0);
+ await close(fd);
+});
+
+test("two descriptors on one file keep independent cursors", async () => {
+ globalThis.paperFS.set("s.bin", Uint8Array.from({ length: 20 }, (_, i) => i));
+ const a = await open("s.bin");
+ const b = await open("s.bin");
+ assert.notEqual(a, b);
+
+ const buf = new Uint8Array(5);
+ await read(a, buf, 0, 5, null); // advances a's cursor only
+ const [n] = await read(b, buf, 0, 5, null);
+ assert.equal(n, 5);
+ assert.deepEqual(buf, Uint8Array.from({ length: 5 }, (_, i) => i));
+ await close(a);
+ await close(b);
+});
+
+test("readdir reports ENOTDIR rather than a generic failure", async () => {
+ globalThis.paperFS.set("f.bin", new Uint8Array([0]));
+ await assert.rejects(
+ call("readdir", "f.bin"),
+ (err) => err.code === "ENOTDIR",
+ );
+});
+
+test("unsupported operations answer ENOSYS", async () => {
+ await assert.rejects(call("unlink", "x"), (err) => err.code === "ENOSYS");
+ await assert.rejects(call("mkdir", "d", 0o755), (err) => err.code === "ENOSYS");
+});
+
+test("stdout and stderr writes are line buffered to the console", () => {
+ const lines = [];
+ const realLog = console.log;
+ console.log = (line) => lines.push(line);
+ try {
+ const enc = new TextEncoder();
+ globalThis.fs.writeSync(1, enc.encode("hello "));
+ assert.deepEqual(lines, [], "nothing flushes until a newline arrives");
+ globalThis.fs.writeSync(1, enc.encode("world\n"));
+ assert.deepEqual(lines, ["hello world"]);
+ } finally {
+ console.log = realLog;
+ }
+});
+
+test("paperFS.set accepts an ArrayBuffer as well as a Uint8Array", async () => {
+ globalThis.paperFS.set("ab.bin", new Uint8Array([9, 8, 7]).buffer);
+ const fd = await open("ab.bin");
+ assert.equal((await fstat(fd)).size, 3);
+ await close(fd);
+});
diff --git a/docs/assets/js/paper-preview.js b/docs/assets/js/paper-preview.js
new file mode 100644
index 00000000..6f1a5665
--- /dev/null
+++ b/docs/assets/js/paper-preview.js
@@ -0,0 +1,282 @@
+// paper-preview.js — renders the PDF previews on the docs site.
+//
+// Two fenced block languages are handled:
+//
+// ```pdf a path to a committed PDF, e.g. assets/pdf/showcase.pdf
+// ```pdf-example the name of a registered example, e.g. imagegrid
+//
+// The second kind is generated in the browser by paper.wasm from the same
+// GetPaper builder the page shows as its code sample, so the preview cannot
+// drift from the code beside it. The wasm is ~4.7MB gzipped, so it is fetched
+// lazily: a page with no pdf-example fence never requests it, and the
+// instantiation is cached for the whole SPA session rather than per route.
+//
+// This replaces docsify-pdf-embed-plugin, which round-tripped pending embeds
+// through localStorage, required $docsify.executeScript, and built absolute
+// URLs from location.hostname only — dropping the /paper/ project-pages prefix.
+(function () {
+ "use strict";
+
+ var WASM_DIR = "assets/wasm/";
+ var VIEWER_HEIGHT = "50rem";
+
+ var wasmReady = null; // cached instantiation promise, shared across routes
+ var assetCache = new Map(); // repo-relative path -> Uint8Array
+ var pending = []; // previews awaiting the current render pass
+ var liveURLs = []; // object URLs to revoke when the next route renders
+ var seq = 0;
+
+ // Resolve against the document's own URL so the site works under a
+ // project-pages path prefix such as /paper/. document.baseURI is absolute,
+ // which URL() requires as a base; the route hash does not affect resolution.
+ function siteURL(rel) {
+ return new URL(rel, document.baseURI).href;
+ }
+
+ function el(tag, className, text) {
+ var node = document.createElement(tag);
+ if (className) node.className = className;
+ if (text) node.textContent = text;
+ return node;
+ }
+
+ function loadScript(src) {
+ return new Promise(function (resolve, reject) {
+ var s = document.createElement("script");
+ s.src = src;
+ s.onload = resolve;
+ s.onerror = function () {
+ reject(new Error("failed to load " + src));
+ };
+ document.head.appendChild(s);
+ });
+ }
+
+ // instantiateStreaming rejects unless the response Content-Type is exactly
+ // application/wasm, which plain static file servers do not always send —
+ // Python's http.server only learned the .wasm mapping in 3.11, and `make site`
+ // uses it. Stream when we can, fall back to buffering when we cannot, so the
+ // local flow works anywhere. The playground does the same for the same reason.
+ async function instantiate(src, importObject) {
+ var response = await fetch(src);
+ if (!response.ok) {
+ throw new Error("fetch " + src + ": HTTP " + response.status);
+ }
+ if (WebAssembly.instantiateStreaming) {
+ try {
+ return await WebAssembly.instantiateStreaming(
+ response.clone(),
+ importObject,
+ );
+ } catch (_) {
+ // Fall through: almost always a Content-Type the browser refused.
+ }
+ }
+ return WebAssembly.instantiate(await response.arrayBuffer(), importObject);
+ }
+
+ // paper-fs.js must be evaluated before wasm_exec.js: the latter only installs
+ // its own stub filesystem when globalThis.fs is absent.
+ function ensureWasm() {
+ if (wasmReady) return wasmReady;
+
+ wasmReady = (async function () {
+ if (!globalThis.paperFS)
+ await loadScript(siteURL("assets/js/paper-fs.js"));
+ if (typeof Go === "undefined")
+ await loadScript(siteURL(WASM_DIR + "wasm_exec.js"));
+
+ var go = new Go();
+ var src = siteURL(WASM_DIR + "paper.wasm");
+ var result = await instantiate(src, go.importObject);
+ // main() ends in select{}, so this never resolves; the exports stay
+ // callable for the lifetime of the page.
+ go.run(result.instance);
+
+ for (
+ var i = 0;
+ i < 50 && typeof globalThis.paperGenerateExample !== "function";
+ i++
+ ) {
+ await new Promise(function (r) {
+ setTimeout(r, 20);
+ });
+ }
+ if (typeof globalThis.paperGenerateExample !== "function") {
+ throw new Error("paper.wasm loaded but registered no exports");
+ }
+ })();
+
+ // A failed load must not be cached, or every later preview inherits it.
+ wasmReady.catch(function () {
+ wasmReady = null;
+ });
+ return wasmReady;
+ }
+
+ // The map key is the repo-relative path Go passes to os.ReadFile; the URL
+ // drops the leading "docs/" because the deployed site root is docs/ itself.
+ async function ensureAssets(paths) {
+ for (const path of paths) {
+ if (assetCache.has(path)) {
+ globalThis.paperFS.set(path, assetCache.get(path));
+ continue;
+ }
+ var url = siteURL(path.replace(/^docs\//, ""));
+ var response = await fetch(url);
+ if (!response.ok) {
+ throw new Error("fetch " + url + ": HTTP " + response.status);
+ }
+ var bytes = new Uint8Array(await response.arrayBuffer());
+ assetCache.set(path, bytes);
+ globalThis.paperFS.set(path, bytes);
+ }
+ }
+
+ function unwrap(result, what) {
+ if (!result || result.error) {
+ throw new Error(
+ result && result.error ? result.error : what + " returned nothing",
+ );
+ }
+ return result;
+ }
+
+ async function generate(name) {
+ await ensureWasm();
+ var assets =
+ unwrap(globalThis.paperExampleAssets(name), "paperExampleAssets")
+ .assets || [];
+ await ensureAssets(assets);
+
+ var base64 = unwrap(
+ globalThis.paperGenerateExample(name),
+ "paperGenerateExample",
+ ).pdf;
+ var binary = atob(base64);
+ var bytes = new Uint8Array(binary.length);
+ for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
+ return URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }));
+ }
+
+ // A download link always accompanies the embed. iOS Safari and Android Chrome
+ // render a blob