diff --git a/docs/CHECK_PLAN.md b/docs/CHECK_PLAN.md new file mode 100644 index 0000000..4dddb63 --- /dev/null +++ b/docs/CHECK_PLAN.md @@ -0,0 +1,268 @@ +# PLAN: Reduce WASM binary — Remaining optimizations + +## Current Status: 423.5 KB (post-optimization, down from 738 KB — 43% reduction) + +## Remaining target: ~150 KB + +--- + +## Post-execution twiggy analysis + +| Dependency | Before | After | Status | +|---|---|---|---| +| encoding/json | 91 KB | 0 KB | Eliminated | +| time stdlib | 75 KB | ~1 KB | Eliminated | +| image/gif | 12 KB | 0 KB | Eliminated | +| compress/* | 38 KB | ~0.1 KB | Eliminated | +| stdlib fmt | 45 KB | 0 KB | Eliminated | +| crypto/* | 30 KB | **25 KB** | **Partial — attachments.go still imports crypto/md5** | +| runtime | 61 KB | 61 KB | Irreducible | + +--- + +## STAGES + +### Stage 1: Eliminate crypto/md5 from attachments.go — Savings ~25 KB +**Risk**: low | **Complexity**: low + +**Context**: `fpdf/attachments.go` imports `crypto/md5` for the `checksum()` function (line 32-35). +This is used in `writeCompressedFileObject()` to embed the MD5 checksum of attachment content. +Attachments are NOT part of the WASM functional scope (generate-only, no embedded files needed in browser). + +**Current code** (`fpdf/attachments.go:32-35`): +```go +func checksum(data []byte) string { + sl := md5.Sum(data) + return hex.EncodeToString(sl[:]) +} +``` + +**What to do**: +1. Add `//go:build !wasm` to `fpdf/attachments.go` +2. Create `fpdf/attachments_wasm.go` (`//go:build wasm`) with: + - The `Attachment` struct definition (needed for compilation) + - Stub functions that set `f.err` with "attachments not supported in WASM" + - The `annotationAttach` struct and `pageAttachments` type if referenced elsewhere + +**Important**: before adding build tags, verify what symbols from `attachments.go` are referenced in other files that compile for WASM: +```bash +grep -rn "Attachment\|attachments\|pageAttachments\|annotationAttach\|putAttachments\|getEmbeddedFiles\|putAnnotationsAttachments\|putAttachmentAnnotationLinks\|AddAttachmentAnnotation\|SetAttachments" fpdf/*.go | grep -v _test.go | grep -v attachments.go +``` +All referenced symbols must have stubs in the WASM file. + +**Files**: +- `fpdf/attachments.go` → add `//go:build !wasm` +- `fpdf/attachments_wasm.go` → new, struct definitions + stub functions + +**Validation**: +1. `wasmbuild` compiles without errors +2. `twiggy top ... | grep crypto` → 0 results +3. `go test ./...` backend tests still pass +4. Measure new size: `ls -lh web/public/client.wasm` + +--- + +### Stage 2: Unify fontid — Eliminate duplicated code + crypto/sha1 from backend +**Risk**: low | **Complexity**: low + +**Context**: `fpdf/fontid_back.go` and `fpdf/fontid_wasm.go` duplicate logic unnecessarily. +The backend version uses `crypto/sha1` + `encoding/json` for `generateFontID` and SHA1 hashing for `generateImageID`. The WASM version uses simple deterministic strings. The simple version is sufficient for both platforms — IDs only need to be unique map keys within a document session. + +**Current state**: +- `fpdf/fontid_back.go` (!wasm): `crypto/sha1`, `encoding/json` — heavy dependencies for no real benefit +- `fpdf/fontid_wasm.go` (wasm): simple `Tp + "_" + Name` for fonts, `img_w_h_len(data)` for images + +**Problem with WASM image ID**: `img_w_h_len(data)` can collide — two different images with same dimensions and data length get the same ID. + +**What to do**: +1. Delete `fpdf/fontid_back.go` and `fpdf/fontid_wasm.go` +2. Create single `fpdf/fontid.go` (no build tags) with: + ```go + func generateFontID(fdt fontDefType) (string, error) { + return fdt.Tp + "_" + fdt.Name, nil + } + ``` +3. For `generateImageID`: use `tinywasm/unixid.GetNewID()` — unique, thread-safe, no crypto. Each image gets a unique ID regardless of content. + ```go + func generateImageID(info *ImageInfoType) (string, error) { + var id string + uid.SetNewID(&id) + return id, nil + } + ``` + Where `uid` is a package-level `*unixid.UnixID` instance initialized once. + +**Files**: +- Delete `fpdf/fontid_back.go` +- Delete `fpdf/fontid_wasm.go` +- Create `fpdf/fontid.go` — unified, no build tags, uses `tinywasm/unixid` + +**Validation**: +1. `wasmbuild` compiles +2. `go test ./...` passes +3. `twiggy top ... | grep crypto/sha1` → 0 results +4. Multiple images in same PDF render correctly (no ID collisions) + +--- + +### Stage 3: Unify fonts_json — Remove encoding/json duplication +**Risk**: low | **Complexity**: low + +**Context**: `fpdf/fonts_json_back.go` uses `encoding/json.Unmarshal` and `fpdf/fonts_json_wasm.go` uses `tinywasm/json.Decode`. Since `tinywasm/json` is platform-agnostic, both can use it. + +**Note**: `fpdf/font.go` also imports `encoding/json` for `MakeFont` (build tool, not runtime). That file gets `!wasm` build tag in Stage 4 and keeps `encoding/json` since it's backend-only tooling. + +**What to do**: +1. Delete `fpdf/fonts_json_back.go` and `fpdf/fonts_json_wasm.go` +2. Create single `fpdf/fonts_json.go` (no build tags): + ```go + package fpdf + + import "github.com/tinywasm/json" + + func unmarshalFontDef(data []byte, def *fontDefType) error { + return json.Decode(data, def) + } + ``` + +**Files**: +- Delete `fpdf/fonts_json_back.go` +- Delete `fpdf/fonts_json_wasm.go` +- Create `fpdf/fonts_json.go` — unified, no build tags + +**Validation**: +1. `wasmbuild` compiles +2. `go test ./...` passes — fonts load correctly with tinywasm/json on backend +3. Generate PDF with multiple fonts to verify character widths (Cw) parse correctly + +--- + +### Stage 4: Add `!wasm` build tag to font.go — Preventive +**Risk**: low | **Complexity**: low + +**Context**: `fpdf/font.go` imports `encoding/json`, `compress/zlib`, `os` but currently has no build tag. +TinyGo eliminates it via dead code elimination, but this is fragile — any future call to `MakeFont` from WASM code would silently pull in ~130 KB of dependencies. + +**What to do**: +1. Add `//go:build !wasm` to `fpdf/font.go` +2. Verify no WASM code calls any function from this file + +**Validation**: +1. `wasmbuild` compiles +2. `go test ./...` passes + +--- + +### Stage 5: Unify time — Remove time stdlib duplication +**Risk**: low | **Complexity**: low + +**Context**: `fpdf/time_back.go` uses `time.Time` (stdlib) and `fpdf/time_wasm.go` uses `int64` (tinywasm/time). The underlying type `pdfTime` is also split: `types_back.go` defines it as `time.Time`, `types_wasm.go` as `int64`. Since `tinywasm/time` is platform-agnostic and uses `int64` everywhere, unify to `int64` only. + +**Files to unify/delete**: +- Delete `fpdf/types_back.go` and `fpdf/types_wasm.go` → create `fpdf/types.go`: `type pdfTime int64` +- Delete `fpdf/time_back.go` and `fpdf/time_wasm.go` → create `fpdf/time.go` using `tinywasm/time` only: + - API: `SetCreationDate(tm int64)`, `GetCreationDate() int64`, etc. + - `timeOrNow(tm pdfTime) int64`: if 0 return `time.Now()`, else return `int64(tm)` + - `formatPDFDate(tm pdfTime) string`: use `time.FormatISO8601` + string slicing (from current wasm version) + +**Tests to update** (use `int64` unix nano instead of `time.Time`): +- `fpdf/getter_test.go:126,448` — `TestGetCreationDate`, `TestGetModificationDate` +- `fpdf/fpdf_test.go:2691` — `Test_SetModificationDate` +- `fpdf/exampleDir_test.go:55-56` — replace `time.Date(...)` with equivalent unix nano value + +**Note**: once `tinywasm/time` has `FormatCompact` (see tinywasm/time PLAN.md), replace the ISO8601 string slicing with `time.FormatCompact(nano)`. + +**Validation**: +1. `wasmbuild` compiles +2. `go test ./...` passes (tests updated to int64) +3. `grep -rn '"time"' fpdf/*.go | grep -v _test.go` → 0 results (only tinywasm/time) +4. PDF CreationDate/ModDate are correct in generated output + +--- + +### Stage 6: Remove `os` from shared production files — Use io interfaces +**Risk**: low | **Complexity**: low + +**Context**: `os` package should not be used in shared fpdf code. The library already has an injection pattern for file operations (`env.front.go`/`env.back.go`, `def.go:431 fileSize func`). Three production files still import `os`: + +| File | Usage | Fix | +|---|---|---| +| `fpdf/svgbasic.go:302` | `os.ReadFile()` in `SVGBasicFileParse()` | Move file-path variant to `!wasm` file. `SVGBasicParse([]byte)` already exists in shared code | +| `fpdf/util.go:38,50` | `os.Stat()` in `fileExist()`, `fileSize()` | Only called from `font.go` (already `!wasm`). Move to `!wasm` file | +| `fpdf/list/list.go:37` | `filepath.Walk` + `os.FileInfo` | Backend-only tooling, add `!wasm` build tag | + +**What to do**: +1. **svgbasic.go**: remove `os` import and `SVGBasicFileParse`. Create `fpdf/svgbasic_back.go` (!wasm) with the file-path variant using `os.ReadFile`. +2. **util.go**: move `fileExist()` and `fileSize()` to `fpdf/util_back.go` (!wasm). Remove `os` import from `util.go`. +3. **list/list.go**: add `//go:build !wasm`. + +**Files**: +- `fpdf/svgbasic.go` → remove `os` import, remove `SVGBasicFileParse` +- `fpdf/svgbasic_back.go` → new (!wasm), `SVGBasicFileParse` with `os.ReadFile` +- `fpdf/util.go` → remove `os` import, remove `fileExist`/`fileSize` +- `fpdf/util_back.go` → new (!wasm), `fileExist`/`fileSize` with `os.Stat` +- `fpdf/list/list.go` → add `//go:build !wasm` + +**Validation**: +1. `wasmbuild` compiles +2. `go test ./...` passes +3. Verify: `grep -rn '"os"' fpdf/*.go | grep -v _test.go | grep -v _back.go` → only `font.go` (already `!wasm`) + +--- + +### Stage 6: Verify xcompr_wasm.go imports compress/zlib — Bug fix +**Risk**: low | **Complexity**: low + +**Context**: `fpdf/xcompr_wasm.go` currently imports `compress/zlib` for the `uncompress()` function. +The agent kept decompression functional in case fonts need it. +twiggy shows compress/* at only 0.1 KB, suggesting TinyGo may be eliminating most of it. + +**What to do**: +1. Verify if `uncompress()` is actually called in WASM builds (grep for call sites) +2. If not called: remove `compress/zlib` import, make `uncompress()` return error +3. If called (e.g., for .z compressed fonts): keep it but document why + +**Validation**: +1. `wasmbuild` compiles +2. Font loading works in WASM +3. `twiggy top ... | grep compress` → verify size impact + +--- + +### Stage 7: Analyze remaining size for Round 2 candidates +**Risk**: N/A | **Complexity**: analysis only + +After Stages 1-6, run full twiggy analysis to identify next optimization targets: +```bash +twiggy top web/public/client.wasm -n 50 +``` + +Known Round 2 candidates (evaluate in separate plan): +1. `regexp` in `htmlbasic.go` and `ttfparser.go` → manual parsing +2. `image/jpeg` (26 KB) + `image/png` (17 KB) → decode in JS Canvas API +3. fpdf dead code: layers, gradients, spot colors, blending modes +4. Strip "function names" WASM subsection (~36 KB) +5. `web/ui.setupUI` (70 KB) — demo code, not the library + +--- + +## Execution Order + +``` +Stage 1 (attachments crypto/md5) ── Main WASM savings (~25 KB) +Stage 2 (unify fontid) ── Remove duplication + crypto/sha1 +Stage 3 (unify fonts_json) ── Remove encoding/json duplication +Stage 4 (font.go build tag) ── Preventive +Stage 5 (unify time) ── Remove time stdlib duplication +Stage 6 (remove os from shared) ── Clean architecture +Stage 7 (xcompr_wasm.go verify) ── Bug fix +Stage 8 (analysis) ── Plan next round +``` + +## Validation per stage +Each stage MUST: +1. Compile: `wasmbuild` +2. Measure: `ls -lh web/public/client.wasm` +3. Analyze: `twiggy top web/public/client.wasm -n 30` +4. Functional: generate PDF with text, table, chart and image in WASM +5. Backend: `go test ./...` diff --git a/docs/PLAN.md b/docs/PLAN.md index 4dddb63..e0d08a0 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -1,264 +1,135 @@ -# PLAN: Reduce WASM binary — Remaining optimizations +# PLAN: WASM binary size — Round 2 -## Current Status: 423.5 KB (post-optimization, down from 738 KB — 43% reduction) +## Current Status: 364.3 KB (down from 738 KB — 50.6% reduction) ## Remaining target: ~150 KB ---- - -## Post-execution twiggy analysis - -| Dependency | Before | After | Status | +## Completed (Round 1) +- encoding/json → tinywasm/json: eliminated +- time stdlib → tinywasm/time: eliminated +- crypto/* (protection, sha1, md5): eliminated +- image/gif: eliminated +- compress/* (zlib write): eliminated (120 bytes remain for PNG decompress, acceptable) +- stdlib fmt: eliminated +- os from shared code: eliminated +- Duplicated build-tag files unified: fontid, fonts_json, time, types + +## Completed (Bug fixes — post Round 1) +- **Blank PDF in WASM**: `f.compress` defaulted to `true` but `xmem.compress()` in WASM copies bytes without compressing. PDF headers declared `/Filter /FlateDecode` causing viewers to fail decompressing raw data → blank pages. + - Added `init() { gl.noCompress = true }` in `fpdf/xcompr_wasm.go` + - Added `f.compress` checks in `fpdf/fonts.go` (~lines 793, 807) around cidToGidMap and font stream FlateDecode headers + - Added `f.compress` check in `fpdf/document.go` (~line 1062) for ICC profile FlateDecode header +- **def_gob.go build tag**: Fixed from `//go:build !tinygo && !js && !wasm` to `//go:build !wasm` + +## Current breakdown (twiggy, 364.3 KB) + +| Component | KB | % | Reducible? | |---|---|---|---| -| encoding/json | 91 KB | 0 KB | Eliminated | -| time stdlib | 75 KB | ~1 KB | Eliminated | -| image/gif | 12 KB | 0 KB | Eliminated | -| compress/* | 38 KB | ~0.1 KB | Eliminated | -| stdlib fmt | 45 KB | 0 KB | Eliminated | -| crypto/* | 30 KB | **25 KB** | **Partial — attachments.go still imports crypto/md5** | -| runtime | 61 KB | 61 KB | Irreducible | +| web/ui.setupUI (demo) | 61 KB | 16.8% | YES — not part of the lib | +| runtime | 45 KB | 11.9% | NO — irreducible | +| rodata segments | ~45 KB | 12% | PARTIAL — reduces with code | +| tinywasm/fmt | ~25 KB | 6.7% | NO — core dependency | +| fpdf core | ~30 KB | 8% | PARTIAL — dead code | +| image/jpeg | ~16 KB | 4.3% | MAYBE — decode in JS | +| function names subsection | 12 KB | 3.2% | YES — debug metadata | +| tinywasm/json | ~10 KB | 2.7% | NO — needed for fonts | +| image/png | ~8 KB | 2.1% | MAYBE — decode in JS | +| other (fetch, syscall/js, etc) | ~112 KB | ~30% | Various | + +## Blockers +- **tinywasm/json FieldIntSlice**: `tinywasm/fmt` and `tinywasm/json` need `FieldIntSlice` support for `fontDefType.Cw []int`. Plans created in both repos (`tinywasm/fmt/docs/PLAN.md` and `tinywasm/json/docs/PLAN.md`). Must be completed before fonts work with unified `tinywasm/json` on backend tests. + +## Known Issues (not blocking optimization but pending) +- **UTF-8 garbled characters on backend**: `loadDefaultFont()` in `document.go:44-51` reads `fonts/Arial.ttf` relative to CWD via `readFile()`. If file not found, silently falls back to built-in Latin-1 Arial. Backend demos show garbled characters (e.g. "LÃ-nea") because the font file path doesn't resolve. Fix: ensure the caller sets the correct working directory or provides an absolute path to `DefaultFontPath`. --- ## STAGES -### Stage 1: Eliminate crypto/md5 from attachments.go — Savings ~25 KB +### Stage 1: Strip "function names" WASM subsection — Savings ~12 KB **Risk**: low | **Complexity**: low -**Context**: `fpdf/attachments.go` imports `crypto/md5` for the `checksum()` function (line 32-35). -This is used in `writeCompressedFileObject()` to embed the MD5 checksum of attachment content. -Attachments are NOT part of the WASM functional scope (generate-only, no embedded files needed in browser). - -**Current code** (`fpdf/attachments.go:32-35`): -```go -func checksum(data []byte) string { - sl := md5.Sum(data) - return hex.EncodeToString(sl[:]) -} -``` +**Context**: The "function names" subsection (12 KB, 3.2%) is debug metadata embedded in the WASM binary. It contains human-readable names for every function — useful for debugging but unnecessary in production. **What to do**: -1. Add `//go:build !wasm` to `fpdf/attachments.go` -2. Create `fpdf/attachments_wasm.go` (`//go:build wasm`) with: - - The `Attachment` struct definition (needed for compilation) - - Stub functions that set `f.err` with "attachments not supported in WASM" - - The `annotationAttach` struct and `pageAttachments` type if referenced elsewhere - -**Important**: before adding build tags, verify what symbols from `attachments.go` are referenced in other files that compile for WASM: -```bash -grep -rn "Attachment\|attachments\|pageAttachments\|annotationAttach\|putAttachments\|getEmbeddedFiles\|putAnnotationsAttachments\|putAttachmentAnnotationLinks\|AddAttachmentAnnotation\|SetAttachments" fpdf/*.go | grep -v _test.go | grep -v attachments.go -``` -All referenced symbols must have stubs in the WASM file. - -**Files**: -- `fpdf/attachments.go` → add `//go:build !wasm` -- `fpdf/attachments_wasm.go` → new, struct definitions + stub functions +1. Run `wasmbuild` with current config and note exact file size: `ls -lh web/public/client.wasm` +2. Try `wasm-opt --strip-debug -O2 web/public/client.wasm -o web/public/client.wasm` (install via `apt install binaryen` if missing) +3. If `wasm-opt` not available, try `wasm-tools strip web/public/client.wasm -o web/public/client.wasm` (install via `cargo install wasm-tools` if missing) +4. If neither tool is available, check if TinyGo's `-no-debug` flag works: modify `wasmbuild` command to add it **Validation**: -1. `wasmbuild` compiles without errors -2. `twiggy top ... | grep crypto` → 0 results -3. `go test ./...` backend tests still pass -4. Measure new size: `ls -lh web/public/client.wasm` +1. `ls -lh web/public/client.wasm` — verify ~12 KB reduction +2. Load in browser — WASM still loads, PDF generates correctly with text, table, chart, image +3. `twiggy top web/public/client.wasm -n 30` — confirm "function names" subsection gone or reduced --- -### Stage 2: Unify fontid — Eliminate duplicated code + crypto/sha1 from backend -**Risk**: low | **Complexity**: low +### Stage 2: Remove fpdf dead code — Savings ~10-20 KB estimated +**Risk**: medium | **Complexity**: medium -**Context**: `fpdf/fontid_back.go` and `fpdf/fontid_wasm.go` duplicate logic unnecessarily. -The backend version uses `crypto/sha1` + `encoding/json` for `generateFontID` and SHA1 hashing for `generateImageID`. The WASM version uses simple deterministic strings. The simple version is sufficient for both platforms — IDs only need to be unique map keys within a document session. +**Context**: fpdf has many features not used in the WASM scope (generate-only PDFs with text, tables, charts, images). Unused features add code that TinyGo may not fully eliminate. Several files have NO build tag and compile into WASM unnecessarily. -**Current state**: -- `fpdf/fontid_back.go` (!wasm): `crypto/sha1`, `encoding/json` — heavy dependencies for no real benefit -- `fpdf/fontid_wasm.go` (wasm): simple `Tp + "_" + Name` for fonts, `img_w_h_len(data)` for images +**Candidate files to evaluate** (all currently lack `//go:build` tags and compile in WASM): -**Problem with WASM image ID**: `img_w_h_len(data)` can collide — two different images with same dimensions and data length get the same ID. - -**What to do**: -1. Delete `fpdf/fontid_back.go` and `fpdf/fontid_wasm.go` -2. Create single `fpdf/fontid.go` (no build tags) with: - ```go - func generateFontID(fdt fontDefType) (string, error) { - return fdt.Tp + "_" + fdt.Name, nil - } - ``` -3. For `generateImageID`: use `tinywasm/unixid.GetNewID()` — unique, thread-safe, no crypto. Each image gets a unique ID regardless of content. - ```go - func generateImageID(info *ImageInfoType) (string, error) { - var id string - uid.SetNewID(&id) - return id, nil - } - ``` - Where `uid` is a package-level `*unixid.UnixID` instance initialized once. - -**Files**: -- Delete `fpdf/fontid_back.go` -- Delete `fpdf/fontid_wasm.go` -- Create `fpdf/fontid.go` — unified, no build tags, uses `tinywasm/unixid` - -**Validation**: -1. `wasmbuild` compiles -2. `go test ./...` passes -3. `twiggy top ... | grep crypto/sha1` → 0 results -4. Multiple images in same PDF render correctly (no ID collisions) - ---- - -### Stage 3: Unify fonts_json — Remove encoding/json duplication -**Risk**: low | **Complexity**: low - -**Context**: `fpdf/fonts_json_back.go` uses `encoding/json.Unmarshal` and `fpdf/fonts_json_wasm.go` uses `tinywasm/json.Decode`. Since `tinywasm/json` is platform-agnostic, both can use it. - -**Note**: `fpdf/font.go` also imports `encoding/json` for `MakeFont` (build tool, not runtime). That file gets `!wasm` build tag in Stage 4 and keeps `encoding/json` since it's backend-only tooling. - -**What to do**: -1. Delete `fpdf/fonts_json_back.go` and `fpdf/fonts_json_wasm.go` -2. Create single `fpdf/fonts_json.go` (no build tags): - ```go - package fpdf - - import "github.com/tinywasm/json" - - func unmarshalFontDef(data []byte, def *fontDefType) error { - return json.Decode(data, def) - } - ``` - -**Files**: -- Delete `fpdf/fonts_json_back.go` -- Delete `fpdf/fonts_json_wasm.go` -- Create `fpdf/fonts_json.go` — unified, no build tags - -**Validation**: -1. `wasmbuild` compiles -2. `go test ./...` passes — fonts load correctly with tinywasm/json on backend -3. Generate PDF with multiple fonts to verify character widths (Cw) parse correctly - ---- - -### Stage 4: Add `!wasm` build tag to font.go — Preventive -**Risk**: low | **Complexity**: low - -**Context**: `fpdf/font.go` imports `encoding/json`, `compress/zlib`, `os` but currently has no build tag. -TinyGo eliminates it via dead code elimination, but this is fragile — any future call to `MakeFont` from WASM code would silently pull in ~130 KB of dependencies. - -**What to do**: -1. Add `//go:build !wasm` to `fpdf/font.go` -2. Verify no WASM code calls any function from this file +| File | Feature | Heavy import | Action if unused | +|---|---|---|---| +| `fpdf/htmlbasic.go` | HTML parser | `regexp` (HEAVY) | Add `//go:build !wasm` | +| `fpdf/layer.go` | PDF layers | none | Add `//go:build !wasm` | +| `fpdf/spotcolor.go` | Spot colors | none | Add `//go:build !wasm` | +| `fpdf/fpdftrans.go` | Transformations | none | Add `//go:build !wasm` if not used by charts | +| `fpdf/grid.go` | Grid drawing | none | Add `//go:build !wasm` if not used by charts | +| `fpdf/javascripts.go` | PDF JavaScript | none | Add `//go:build !wasm` | +| `fpdf/subwrite.go` | Subscript/superscript | none | Add `//go:build !wasm` if unused | +| `fpdf/label.go` | Axis label formatting | none | Keep if used by charts | +| `fpdf/font_afm.go` | AFM font parser | `bufio` | Add `//go:build !wasm` if unused | + +**What to do for each file**: +1. Find all exported functions in the file: `grep -E '^func \(f \*Fpdf\)|^func [A-Z]' fpdf/.go` +2. For each function, check if it's called from WASM code paths: `grep -r '' fpdf/ web/ --include='*.go' -l` +3. Exclude files that have `//go:build !wasm` already (they won't match) +4. If NO WASM code path calls any function in the file → add `//go:build !wasm` at the top +5. If some functions are used and others not → leave the file as-is (don't split unless the savings are significant) +6. **Special case `htmlbasic.go`**: This imports `regexp` which is very heavy in TinyGo. Prioritize confirming whether `WriteHTML` or related functions are called from WASM paths. If not → `//go:build !wasm` gives the biggest single-file win. **Validation**: -1. `wasmbuild` compiles -2. `go test ./...` passes +1. `wasmbuild` compiles without errors +2. `ls -lh web/public/client.wasm` — measure reduction +3. `twiggy top web/public/client.wasm -n 30` — verify removed components +4. `go test ./...` — backend tests still pass (files still compile for backend) +5. Browser: generate PDF with text, table, chart, image — all render correctly --- -### Stage 5: Unify time — Remove time stdlib duplication -**Risk**: low | **Complexity**: low - -**Context**: `fpdf/time_back.go` uses `time.Time` (stdlib) and `fpdf/time_wasm.go` uses `int64` (tinywasm/time). The underlying type `pdfTime` is also split: `types_back.go` defines it as `time.Time`, `types_wasm.go` as `int64`. Since `tinywasm/time` is platform-agnostic and uses `int64` everywhere, unify to `int64` only. - -**Files to unify/delete**: -- Delete `fpdf/types_back.go` and `fpdf/types_wasm.go` → create `fpdf/types.go`: `type pdfTime int64` -- Delete `fpdf/time_back.go` and `fpdf/time_wasm.go` → create `fpdf/time.go` using `tinywasm/time` only: - - API: `SetCreationDate(tm int64)`, `GetCreationDate() int64`, etc. - - `timeOrNow(tm pdfTime) int64`: if 0 return `time.Now()`, else return `int64(tm)` - - `formatPDFDate(tm pdfTime) string`: use `time.FormatISO8601` + string slicing (from current wasm version) - -**Tests to update** (use `int64` unix nano instead of `time.Time`): -- `fpdf/getter_test.go:126,448` — `TestGetCreationDate`, `TestGetModificationDate` -- `fpdf/fpdf_test.go:2691` — `Test_SetModificationDate` -- `fpdf/exampleDir_test.go:55-56` — replace `time.Date(...)` with equivalent unix nano value - -**Note**: once `tinywasm/time` has `FormatCompact` (see tinywasm/time PLAN.md), replace the ISO8601 string slicing with `time.FormatCompact(nano)`. - -**Validation**: -1. `wasmbuild` compiles -2. `go test ./...` passes (tests updated to int64) -3. `grep -rn '"time"' fpdf/*.go | grep -v _test.go` → 0 results (only tinywasm/time) -4. PDF CreationDate/ModDate are correct in generated output - ---- +### Stage 3: Evaluate image decoders → JS Canvas API — Savings ~24 KB +**Risk**: high | **Complexity**: high -### Stage 6: Remove `os` from shared production files — Use io interfaces -**Risk**: low | **Complexity**: low +**Context**: `image/jpeg` (16 KB) + `image/png` (8 KB) = 24 KB. In browser, images can be decoded via Canvas API (`createImageBitmap` + `getImageData`) and passed as raw RGBA pixels to Go. -**Context**: `os` package should not be used in shared fpdf code. The library already has an injection pattern for file operations (`env.front.go`/`env.back.go`, `def.go:431 fileSize func`). Three production files still import `os`: +**Trade-offs**: -| File | Usage | Fix | +| Aspect | Go decoders (current) | JS Canvas API | |---|---|---| -| `fpdf/svgbasic.go:302` | `os.ReadFile()` in `SVGBasicFileParse()` | Move file-path variant to `!wasm` file. `SVGBasicParse([]byte)` already exists in shared code | -| `fpdf/util.go:38,50` | `os.Stat()` in `fileExist()`, `fileSize()` | Only called from `font.go` (already `!wasm`). Move to `!wasm` file | -| `fpdf/list/list.go:37` | `filepath.Walk` + `os.FileInfo` | Backend-only tooling, add `!wasm` build tag | - -**What to do**: -1. **svgbasic.go**: remove `os` import and `SVGBasicFileParse`. Create `fpdf/svgbasic_back.go` (!wasm) with the file-path variant using `os.ReadFile`. -2. **util.go**: move `fileExist()` and `fileSize()` to `fpdf/util_back.go` (!wasm). Remove `os` import from `util.go`. -3. **list/list.go**: add `//go:build !wasm`. - -**Files**: -- `fpdf/svgbasic.go` → remove `os` import, remove `SVGBasicFileParse` -- `fpdf/svgbasic_back.go` → new (!wasm), `SVGBasicFileParse` with `os.ReadFile` -- `fpdf/util.go` → remove `os` import, remove `fileExist`/`fileSize` -- `fpdf/util_back.go` → new (!wasm), `fileExist`/`fileSize` with `os.Stat` -- `fpdf/list/list.go` → add `//go:build !wasm` - -**Validation**: -1. `wasmbuild` compiles -2. `go test ./...` passes -3. Verify: `grep -rn '"os"' fpdf/*.go | grep -v _test.go | grep -v _back.go` → only `font.go` (already `!wasm`) - ---- - -### Stage 6: Verify xcompr_wasm.go imports compress/zlib — Bug fix -**Risk**: low | **Complexity**: low - -**Context**: `fpdf/xcompr_wasm.go` currently imports `compress/zlib` for the `uncompress()` function. -The agent kept decompression functional in case fonts need it. -twiggy shows compress/* at only 0.1 KB, suggesting TinyGo may be eliminating most of it. - -**What to do**: -1. Verify if `uncompress()` is actually called in WASM builds (grep for call sites) -2. If not called: remove `compress/zlib` import, make `uncompress()` return error -3. If called (e.g., for .z compressed fonts): keep it but document why +| Binary size | +24 KB | 0 KB | +| Complexity | Low (direct import) | High (async JS interop) | +| Format support | PNG, JPEG only | All browser-supported formats | +| Color space | Controlled | Browser-dependent | +| Portability | Works everywhere | Browser-only | -**Validation**: -1. `wasmbuild` compiles -2. Font loading works in WASM -3. `twiggy top ... | grep compress` → verify size impact - ---- - -### Stage 7: Analyze remaining size for Round 2 candidates -**Risk**: N/A | **Complexity**: analysis only - -After Stages 1-6, run full twiggy analysis to identify next optimization targets: -```bash -twiggy top web/public/client.wasm -n 50 -``` - -Known Round 2 candidates (evaluate in separate plan): -1. `regexp` in `htmlbasic.go` and `ttfparser.go` → manual parsing -2. `image/jpeg` (26 KB) + `image/png` (17 KB) → decode in JS Canvas API -3. fpdf dead code: layers, gradients, spot colors, blending modes -4. Strip "function names" WASM subsection (~36 KB) -5. `web/ui.setupUI` (70 KB) — demo code, not the library +**Decision**: evaluate in separate plan if Stages 1-2 don't reach target. The complexity is high and the savings moderate. --- ## Execution Order ``` -Stage 1 (attachments crypto/md5) ── Main WASM savings (~25 KB) -Stage 2 (unify fontid) ── Remove duplication + crypto/sha1 -Stage 3 (unify fonts_json) ── Remove encoding/json duplication -Stage 4 (font.go build tag) ── Preventive -Stage 5 (unify time) ── Remove time stdlib duplication -Stage 6 (remove os from shared) ── Clean architecture -Stage 7 (xcompr_wasm.go verify) ── Bug fix -Stage 8 (analysis) ── Plan next round +Stage 1 (strip function names) ── Quick win (~12 KB) +Stage 2 (fpdf dead code) ── Medium effort (~10-20 KB) +Stage 3 (image → JS Canvas) ── Only if needed, separate plan ``` +## Note on web/ui.setupUI (61 KB) +This is the demo application, not the library itself. It does not affect the size of the library when used by other projects. No action needed — it only appears in this binary because `web/client.go` imports it. + ## Validation per stage Each stage MUST: 1. Compile: `wasmbuild` diff --git a/docs/img/badges.svg b/docs/img/badges.svg index c338e7f..540ba0a 100644 --- a/docs/img/badges.svg +++ b/docs/img/badges.svg @@ -51,7 +51,7 @@ text-anchor="middle" font-family="sans-serif" font-size="11" fill="white">Coverage 82.7% + text-anchor="middle" font-family="sans-serif" font-size="11" fill="white">82.4% diff --git a/document.go b/document.go index 31b9c40..0d8aa57 100644 --- a/document.go +++ b/document.go @@ -3,7 +3,6 @@ package pdf import ( "bytes" "io" - "strings" . "github.com/tinywasm/fmt" "github.com/tinywasm/pdf/fpdf" @@ -20,7 +19,11 @@ type Document struct { } // DefaultFontPath is the default path to the Arial UTF-8 font. -const DefaultFontPath = "fonts/Arial.ttf" +//const DefaultFontPath = "fonts/Arial.ttf" + +const DefaultFontPath = "fonts/Spleen.ttf" + +//const DefaultFontPath = "fonts/calligra.ttf" // NewDocument creates a new Document instance with UTF-8 support. func NewDocument() *Document { @@ -93,7 +96,7 @@ func (d *Document) Load(cb func(error)) { } ext := "" - if idx := strings.LastIndex(path, "."); idx != -1 { + if idx := LastIndex(path, "."); idx != -1 { ext = path[idx+1:] } diff --git a/fpdf/attachments.go b/fpdf/attachments.go index 180a358..ebb20d4 100644 --- a/fpdf/attachments.go +++ b/fpdf/attachments.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. +//go:build !wasm + package fpdf import ( diff --git a/fpdf/attachments_wasm.go b/fpdf/attachments_wasm.go new file mode 100644 index 0000000..7cea0bd --- /dev/null +++ b/fpdf/attachments_wasm.go @@ -0,0 +1,56 @@ +// Copyright ©2023 The go-pdf Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build wasm + +package fpdf + +import ( + . "github.com/tinywasm/fmt" +) + +// Attachment defines a content to be included in the pdf +type Attachment struct { + Content []byte + Filename string + Description string + objectNumber int +} + +func (f *Fpdf) writeCompressedFileObject(content []byte) { + f.err = Err("attachments", "not supported in WASM") +} + +func (f *Fpdf) embed(a *Attachment) { + f.err = Err("attachments", "not supported in WASM") +} + +func (f *Fpdf) SetAttachments(as []Attachment) { + f.err = Err("attachments", "not supported in WASM") +} + +func (f *Fpdf) putAttachments() { + // Stub for WASM +} + +func (f Fpdf) getEmbeddedFiles() string { + return "" +} + +type annotationAttach struct { + *Attachment + x, y, w, h float64 +} + +func (f *Fpdf) AddAttachmentAnnotation(a *Attachment, x, y, w, h float64) { + f.err = Err("attachments", "not supported in WASM") +} + +func (f *Fpdf) putAnnotationsAttachments() { + // Stub for WASM +} + +func (f *Fpdf) putAttachmentAnnotationLinks(out *fmtBuffer, page int) { + // Stub for WASM +} diff --git a/fpdf/def.go b/fpdf/def.go index 9828fb2..d8f28f3 100644 --- a/fpdf/def.go +++ b/fpdf/def.go @@ -675,7 +675,7 @@ func (f *fontDefType) Schema() []fmt.Field { {Name: "Desc", Type: fmt.FieldStruct}, {Name: "Up", Type: fmt.FieldInt}, {Name: "Ut", Type: fmt.FieldInt}, - {Name: "Cw", Type: fmt.FieldInt}, + {Name: "Cw", Type: fmt.FieldIntSlice}, {Name: "Enc", Type: fmt.FieldText}, {Name: "Diff", Type: fmt.FieldText}, {Name: "File", Type: fmt.FieldText}, diff --git a/fpdf/document.go b/fpdf/document.go index 41e3b27..a347568 100644 --- a/fpdf/document.go +++ b/fpdf/document.go @@ -1061,13 +1061,17 @@ func (f *Fpdf) putOutputIntentStreams() { f.outputIntentStartN = f.n + 1 for _, oi := range f.outputIntents { f.newobj() - mem := xmem.compress(oi.ICCProfile) - compressedICC := mem.bytes() - f.outf("<< /N 3 /Alternate /DeviceRGB /Length %d /Filter /FlateDecode >>", len(compressedICC)) - f.putstream(compressedICC) + if f.compress { + mem := xmem.compress(oi.ICCProfile) + compressedICC := mem.bytes() + f.outf("<< /N 3 /Alternate /DeviceRGB /Length %d /Filter /FlateDecode >>", len(compressedICC)) + f.putstream(compressedICC) + mem.release() + } else { + f.outf("<< /N 3 /Alternate /DeviceRGB /Length %d >>", len(oi.ICCProfile)) + f.putstream(oi.ICCProfile) + } f.out("endobj") - - mem.release() } } diff --git a/fpdf/exampleDir_test.go b/fpdf/exampleDir_test.go index 969f383..68968d0 100644 --- a/fpdf/exampleDir_test.go +++ b/fpdf/exampleDir_test.go @@ -52,8 +52,8 @@ func NewDocPdfTest(options ...any) *fpdf.Fpdf { pdf := fpdf.New(options...) pdf.SetCompression(false) pdf.SetCatalogSort(true) - pdf.SetCreationDate(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)) - pdf.SetModificationDate(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)) + pdf.SetCreationDate(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano()) + pdf.SetModificationDate(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano()) return pdf } diff --git a/fpdf/font.go b/fpdf/font.go index 676a122..b989d10 100644 --- a/fpdf/font.go +++ b/fpdf/font.go @@ -1,3 +1,5 @@ +//go:build !wasm + package fpdf import ( diff --git a/fpdf/fontid_wasm.go b/fpdf/fontid.go similarity index 53% rename from fpdf/fontid_wasm.go rename to fpdf/fontid.go index 6599fb5..66bffad 100644 --- a/fpdf/fontid_wasm.go +++ b/fpdf/fontid.go @@ -1,18 +1,18 @@ -//go:build wasm - package fpdf import ( - . "github.com/tinywasm/fmt" + "github.com/tinywasm/unixid" ) +var uid, _ = unixid.NewUnixID() + func generateImageID(info *ImageInfoType) (string, error) { - // Simple deterministic ID for WASM to avoid crypto/sha1 - return Sprintf("img_%d_%d_%d", int(info.w), int(info.h), len(info.data)), nil + var id string + uid.SetNewID(&id) + return id, nil } // generateFontID generates a font Id from the font definition func generateFontID(fdt fontDefType) (string, error) { - // Simple deterministic ID for WASM return fdt.Tp + "_" + fdt.Name, nil } diff --git a/fpdf/fontid_back.go b/fpdf/fontid_back.go deleted file mode 100644 index ab940f1..0000000 --- a/fpdf/fontid_back.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build !wasm - -package fpdf - -import ( - "crypto/sha1" - "encoding/json" - . "github.com/tinywasm/fmt" -) - -func generateImageID(info *ImageInfoType) (string, error) { - sha := sha1.New() - enc := newIDEncoder(sha) - enc.bytes(info.data) - enc.bytes(info.smask) - enc.i64(int64(info.n)) - enc.f64(info.w) - enc.f64(info.h) - enc.str(info.cs) - enc.bytes(info.pal) - enc.i64(int64(info.bpc)) - enc.str(info.f) - enc.str(info.dp) - for _, v := range info.trns { - enc.i64(int64(v)) - } - enc.f64(info.scale) - enc.f64(info.dpi) - enc.str(info.i) - - return Sprintf("%x", sha.Sum(nil)), nil -} - -// generateFontID generates a font Id from the font definition -func generateFontID(fdt fontDefType) (string, error) { - // file can be different if generated in different instance - fdt.File = "" - b, err := json.Marshal(&fdt) - return Sprintf("%x", sha1.Sum(b)), err -} diff --git a/fpdf/fonts.go b/fpdf/fonts.go index 72fc378..3571ce9 100644 --- a/fpdf/fonts.go +++ b/fpdf/fonts.go @@ -789,7 +789,11 @@ func (f *Fpdf) putfonts() { mem := xmem.compress(cidToGidMap) cidToGidMap = mem.bytes() f.newobj() - f.out("<>") + if f.compress { + f.out("<>") + } else { + f.out("<>") + } f.putstream(cidToGidMap) f.out("endobj") mem.release() @@ -799,7 +803,9 @@ func (f *Fpdf) putfonts() { compressedFontStream := mem.bytes() f.newobj() f.out("<>") f.putstream(compressedFontStream) diff --git a/fpdf/fonts/Spleen.ttf b/fpdf/fonts/Spleen.ttf new file mode 100644 index 0000000..2fc3057 Binary files /dev/null and b/fpdf/fonts/Spleen.ttf differ diff --git a/fpdf/fonts_json_wasm.go b/fpdf/fonts_json.go similarity index 89% rename from fpdf/fonts_json_wasm.go rename to fpdf/fonts_json.go index b041266..2ba4385 100644 --- a/fpdf/fonts_json_wasm.go +++ b/fpdf/fonts_json.go @@ -1,5 +1,3 @@ -//go:build wasm - package fpdf import ( diff --git a/fpdf/fonts_json_back.go b/fpdf/fonts_json_back.go deleted file mode 100644 index 731f824..0000000 --- a/fpdf/fonts_json_back.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !wasm - -package fpdf - -import ( - "encoding/json" -) - -func unmarshalFontDef(data []byte, def *fontDefType) error { - return json.Unmarshal(data, def) -} diff --git a/fpdf/fpdf_test.go b/fpdf/fpdf_test.go index 852cd50..e037f2b 100644 --- a/fpdf/fpdf_test.go +++ b/fpdf/fpdf_test.go @@ -2696,7 +2696,7 @@ func Test_SetModificationDate(t *testing.T) { // ModDate: Sun Jan 2 10:22:30 2000 pdf := NewDocPdfTest() pdf.AddPage() - pdf.SetModificationDate(time.Date(2000, 1, 2, 10, 22, 30, 0, time.UTC)) + pdf.SetModificationDate(time.Date(2000, 1, 2, 10, 22, 30, 0, time.UTC).UnixNano()) fileStr := Filename("Test_SetModificationDate") err := pdf.OutputFileAndClose(fileStr) SummaryCompare(err, fileStr) diff --git a/fpdf/getter_test.go b/fpdf/getter_test.go index a05edcd..380389d 100644 --- a/fpdf/getter_test.go +++ b/fpdf/getter_test.go @@ -126,11 +126,11 @@ func TestGetConversionRatio(t *testing.T) { func TestGetCreationDate(t *testing.T) { setDate, _ := time.Parse(time.RFC3339, "2003-06-17T01:23:45Z") pdf := NewDocPdfTest() - pdf.SetCreationDate(setDate) + pdf.SetCreationDate(setDate.UnixNano()) creationDate := pdf.GetCreationDate() - if got, want := creationDate, setDate; !got.Equal(want) { + if got, want := creationDate, setDate.UnixNano(); got != want { t.Errorf("invalid creationDate: got=%v, want=%v", got, want) } } @@ -448,11 +448,11 @@ func TestGetMargins(t *testing.T) { func TestGetModificationDate(t *testing.T) { setDate, _ := time.Parse(time.RFC3339, "9-08-02T09:54:32Z") pdf := NewDocPdfTest() - pdf.SetModificationDate(setDate) + pdf.SetModificationDate(setDate.UnixNano()) modificationDate := pdf.GetModificationDate() - if got, want := modificationDate, setDate; !got.Equal(want) { + if got, want := modificationDate, setDate.UnixNano(); got != want { t.Errorf("invalid modificationDate: got=%v, want=%v", got, want) } } diff --git a/fpdf/list/list.go b/fpdf/list/list.go index 8c54f4b..4f5b27a 100644 --- a/fpdf/list/list.go +++ b/fpdf/list/list.go @@ -1,3 +1,5 @@ +//go:build !wasm + package main import ( diff --git a/fpdf/svgbasic.go b/fpdf/svgbasic.go index 7c002c1..f0afa1c 100644 --- a/fpdf/svgbasic.go +++ b/fpdf/svgbasic.go @@ -2,7 +2,6 @@ package fpdf import ( "encoding/xml" - "os" . "github.com/tinywasm/fmt" ) @@ -295,13 +294,3 @@ func SVGBasicParse(buf []byte) (sig SVGBasicType, err error) { return } -// SVGBasicFileParse parses a simple scalable vector graphics (SVG) file into a -// basic descriptor. The SVGBasicWrite() example demonstrates this method. -func SVGBasicFileParse(svgFileStr string) (sig SVGBasicType, err error) { - var buf []byte - buf, err = os.ReadFile(svgFileStr) - if err == nil { - sig, err = SVGBasicParse(buf) - } - return -} diff --git a/fpdf/svgbasic_back.go b/fpdf/svgbasic_back.go new file mode 100644 index 0000000..68637ae --- /dev/null +++ b/fpdf/svgbasic_back.go @@ -0,0 +1,18 @@ +//go:build !wasm + +package fpdf + +import ( + "os" +) + +// SVGBasicFileParse parses a simple scalable vector graphics (SVG) file into a +// basic descriptor. The SVGBasicWrite() example demonstrates this method. +func SVGBasicFileParse(svgFileStr string) (sig SVGBasicType, err error) { + var buf []byte + buf, err = os.ReadFile(svgFileStr) + if err == nil { + sig, err = SVGBasicParse(buf) + } + return +} diff --git a/fpdf/time_wasm.go b/fpdf/time.go similarity index 99% rename from fpdf/time_wasm.go rename to fpdf/time.go index 0ed887a..d8c107a 100644 --- a/fpdf/time_wasm.go +++ b/fpdf/time.go @@ -1,5 +1,3 @@ -//go:build wasm - package fpdf import ( diff --git a/fpdf/time_back.go b/fpdf/time_back.go deleted file mode 100644 index d40b22e..0000000 --- a/fpdf/time_back.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build !wasm - -package fpdf - -import "time" - -// SetDefaultCreationDate sets the default value of the document creation date -// that will be used when initializing a new Fpdf instance. See -// SetCreationDate() for more details. -func SetDefaultCreationDate(tm time.Time) { - gl.creationDate = pdfTime(tm) -} - -// SetDefaultModificationDate sets the default value of the document modification date -// that will be used when initializing a new Fpdf instance. See -// SetCreationDate() for more details. -func SetDefaultModificationDate(tm time.Time) { - gl.modDate = pdfTime(tm) -} - -// GetCreationDate returns the document's internal CreationDate value. -func (f *Fpdf) GetCreationDate() time.Time { - return time.Time(f.creationDate) -} - -// SetCreationDate fixes the document's internal CreationDate value. By -// default, the time when the document is generated is used for this value. -// This method is typically only used for testing purposes to facilitate PDF -// comparison. Specify a zero-value time to revert to the default behavior. -func (f *Fpdf) SetCreationDate(tm time.Time) { - f.creationDate = pdfTime(tm) -} - -// GetModificationDate returns the document's internal ModDate value. -func (f *Fpdf) GetModificationDate() time.Time { - return time.Time(f.modDate) -} - -// SetModificationDate fixes the document's internal ModDate value. -// See `SetCreationDate` for more details. -func (f *Fpdf) SetModificationDate(tm time.Time) { - f.modDate = pdfTime(tm) -} - -// returns Now() if tm is zero -func timeOrNow(tm pdfTime) time.Time { - t := time.Time(tm) - if t.IsZero() { - return time.Now() - } - return t -} - -func formatPDFDate(tm pdfTime) string { - t := timeOrNow(tm) - return "D:" + t.Format("20060102150405") -} diff --git a/fpdf/types_wasm.go b/fpdf/types.go similarity index 66% rename from fpdf/types_wasm.go rename to fpdf/types.go index cb0784c..ae95b1e 100644 --- a/fpdf/types_wasm.go +++ b/fpdf/types.go @@ -1,5 +1,3 @@ -//go:build wasm - package fpdf type pdfTime int64 diff --git a/fpdf/types_back.go b/fpdf/types_back.go deleted file mode 100644 index 2cc61e0..0000000 --- a/fpdf/types_back.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !wasm - -package fpdf - -import "time" - -type pdfTime time.Time diff --git a/fpdf/util.go b/fpdf/util.go index 8f13e04..46bbd61 100644 --- a/fpdf/util.go +++ b/fpdf/util.go @@ -5,7 +5,6 @@ import ( "bytes" "io" "math" - "os" . "github.com/tinywasm/fmt" ) @@ -33,27 +32,6 @@ func sprintf(fmtStr string, args ...any) string { return Sprintf(fmtStr, args...) } -// fileExist returns true if the specified normal file exists -func fileExist(filename string) (ok bool) { - info, err := os.Stat(filename) - if err == nil { - if ^os.ModePerm&info.Mode() == 0 { - ok = true - } - } - return ok -} - -// fileSize returns the size of the specified file; ok will be false -// if the file does not exist or is not an ordinary file -func fileSize(filename string) (size int64, ok bool) { - info, err := os.Stat(filename) - ok = err == nil - if ok { - size = info.Size() - } - return -} // utf8toutf16 converts UTF-8 to UTF-16BE; from http://www.fpdf.org/ func utf8toutf16(s string, withBOM ...bool) string { diff --git a/fpdf/util_back.go b/fpdf/util_back.go new file mode 100644 index 0000000..a3aabfd --- /dev/null +++ b/fpdf/util_back.go @@ -0,0 +1,29 @@ +//go:build !wasm + +package fpdf + +import ( + "os" +) + +// fileExist returns true if the specified normal file exists +func fileExist(filename string) (ok bool) { + info, err := os.Stat(filename) + if err == nil { + if ^os.ModePerm&info.Mode() == 0 { + ok = true + } + } + return ok +} + +// fileSize returns the size of the specified file; ok will be false +// if the file does not exist or is not an ordinary file +func fileSize(filename string) (size int64, ok bool) { + info, err := os.Stat(filename) + ok = err == nil + if ok { + size = info.Size() + } + return +} diff --git a/fpdf/xcompr_wasm.go b/fpdf/xcompr_wasm.go index 01473a9..2c95a51 100644 --- a/fpdf/xcompr_wasm.go +++ b/fpdf/xcompr_wasm.go @@ -8,6 +8,10 @@ import ( "sync" ) +func init() { + gl.noCompress = true +} + var xmem = xmempool{ Pool: sync.Pool{ New: func() any { @@ -26,6 +30,8 @@ func (pool *xmempool) compress(data []byte) *membuffer { return mem } +// uncompress is retained for WASM builds because it is required by fpdf/png.go +// for supporting PNG alpha channels. func (pool *xmempool) uncompress(data []byte) (*membuffer, error) { zr, err := zlib.NewReader(bytes.NewReader(data)) if err != nil { diff --git a/go.mod b/go.mod index e24a979..1e95104 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,8 @@ go 1.25.2 require ( github.com/tinywasm/fetch v0.1.23 - github.com/tinywasm/fmt v0.22.1 -) - -require ( - github.com/tinywasm/json v0.4.0 // indirect - github.com/tinywasm/time v0.4.0 // indirect + github.com/tinywasm/fmt v0.22.2 + github.com/tinywasm/json v0.4.1 + github.com/tinywasm/time v0.4.0 + github.com/tinywasm/unixid v0.2.23 ) diff --git a/go.sum b/go.sum index b725cfc..6dbdd2f 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,10 @@ github.com/tinywasm/fetch v0.1.23 h1:R8Nr1LwcFUVgQsk5f0CilNNP8r0wc3DPaZuC5O8hYTM= github.com/tinywasm/fetch v0.1.23/go.mod h1:XR4SHs1qYUFcPcFVw/r+TrwG+9+jENQPslWAKVC6nHQ= -github.com/tinywasm/fmt v0.22.1 h1:GCI1ovXnbDgb3tkGm/z/IapxqVs6zkvmXGTBmdRNDHw= -github.com/tinywasm/fmt v0.22.1/go.mod h1:L2GCAi6asgytPV6TVvGrRq5Ml+DkUt1Ijo5i/2J1jOY= -github.com/tinywasm/json v0.4.0 h1:1p1+Qe38VEGNhNr20Zq7cv/orrCFF+91uflQllyFsdI= -github.com/tinywasm/json v0.4.0/go.mod h1:4jrd8Oqx5JDeDtR7OM4J4UbSxFJYp+pPooB9W6PnId0= +github.com/tinywasm/fmt v0.22.2 h1:/KL2WP3kNlRrkjK//0rwZpF80sxV7p3o3KyvYaeMCQE= +github.com/tinywasm/fmt v0.22.2/go.mod h1:L2GCAi6asgytPV6TVvGrRq5Ml+DkUt1Ijo5i/2J1jOY= +github.com/tinywasm/json v0.4.1 h1:UjBmaoDhUdD43ekAJ82PzzCT8nudYKFoa9sKBU/DPQs= +github.com/tinywasm/json v0.4.1/go.mod h1:+HsLi3s5kjiZTnv8+1kyV+IS274oU3iRD1NS5T5PkHM= github.com/tinywasm/time v0.4.0 h1:Wo5qFsIM3hg1gy9iADRsbKrKnOt+wG5tFZIrpbWV55E= github.com/tinywasm/time v0.4.0/go.mod h1:/c7FOB2jSYEyaQh+/f0g9UflP+Th6U91F6ghO8zbvcs= +github.com/tinywasm/unixid v0.2.23 h1:Lp/TER0RwbaT7EcHbQNzXj9LOGA2VD8rC8XLYmxJBc0= +github.com/tinywasm/unixid v0.2.23/go.mod h1:5KPbI26CrW8S7rgMS46gqlLKTYPno3QH49GSB5NSLMU= diff --git a/tests/test_api.pdf b/tests/test_api.pdf index 60059fe..4e49ccc 100644 Binary files a/tests/test_api.pdf and b/tests/test_api.pdf differ diff --git a/tests/test_charts.pdf b/tests/test_charts.pdf index 9367246..21b12ac 100644 Binary files a/tests/test_charts.pdf and b/tests/test_charts.pdf differ diff --git a/tests/test_table.pdf b/tests/test_table.pdf index d0553f7..1687c96 100644 Binary files a/tests/test_table.pdf and b/tests/test_table.pdf differ diff --git a/web/public/client.wasm b/web/public/client.wasm index a38c9b2..875918e 100755 Binary files a/web/public/client.wasm and b/web/public/client.wasm differ diff --git a/web/public/fonts/Spleen.ttf b/web/public/fonts/Spleen.ttf new file mode 100644 index 0000000..2fc3057 Binary files /dev/null and b/web/public/fonts/Spleen.ttf differ