Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,37 @@ For more control use an `*engine.Engine` (from `engine.New()`): it adds
and a `DisableJS` field to render the static, no-JavaScript document.

The viewport width is fixed; the height grows to fit the whole page (at least the
viewport height). There is also a CLI:
viewport height).

### Vector output (PDF)

`LayoutHTML` / `LayoutDocument` run the same pipeline without painting and
return a `*PageLayout`. `Paint()` gives the image `RenderHTML` would;
`PaintVector(c)` draws the same boxes through a `paint.Canvas` instead — paths,
shadings, images placed in their boxes and positioned glyphs of the very faces
the layout measured with. The `pdfcanvas` package is that canvas for a
[go-pdfkit](https://github.com/go-pdfkit/pdfkit) page: text stays text
(subset-embedded Type0 fonts with a `/ToUnicode` map — selectable,
searchable), edges stay sharp at any zoom, and an image used on many pages is
embedded once.

```go
l, _ := eng.LayoutHTML(ctx, html, baseURL, image.Rect(0, 0, 1280, 720))
doc := pdfkit.New(pdfkit.Options{Compress: true})
page := doc.AddPage(pdfkit.NewPageSize(960, 540)) // a CSS px is 3/4 pt
stats := l.PaintVector(pdfcanvas.New(page, 0.75, pdfcanvas.NewFonts()))
```

What has no vector form here — a subtree under `opacity` < 1, a `filter`,
`mask-image` or `transform: rotate()`, a blurred `box-shadow`, a gradient
whose opacity fades between two stops, a form control — is painted by the
raster painter as a bitmap of **that element only**, placed where it was, with
its text laid over it invisibly (text rendering mode 3) so that it can still
be selected; `stats.Rasterized` counts them. Laying out and painting are split
so that pages can be laid out concurrently and painted, in order, into one
document.

There is also a CLI:

```
go run ./cmd/render -url https://example.com/ -out out.png -w 1024 -h 768
Expand All @@ -83,8 +113,9 @@ Fetch (go-browserhttp) → Parse (x/net/html → dom) → Cascade (css: +var
| `css` | Real CSS subset: value model, stylesheet/declaration parser, tag/class/id + descendant/child/sibling combinators + `:checked`/`:not()` selectors with specificity, `var()` custom properties, `@media` width queries, modern colour (`rgb()/hsl()`), dark-mode, UA stylesheet, cascade + inheritance. |
| `layout` | Full box model — block-and-inline flow, floats + clear, flexbox, CSS grid, tables, `position` (relative/absolute/fixed/sticky), margin collapsing, greedy word-wrap — driven by a `Measurer` interface (font-free, exactly testable). |
| `js` | JavaScript execution via [goja](https://github.com/dop251/goja) bound to a minimal real DOM, with `fetch()`/XHR and laid-out-geometry read-back (`getBoundingClientRect`, `offset*`, `getComputedStyle`). |
| `paint` | Rasterises the box tree to `*image.RGBA` — AA text (real bold + italic), gradients, border-radius, box-shadow, opacity, images and SVG; also the real `Measurer` (go-opentype faces). |
| `engine` (root) | `Fetch`, `Render`, `Screenshot`, `RenderHTML`, `RenderWithLinks`, the settle-then-render loop, image + SVG loading, and the anchor hit-map. |
| `paint` | Rasterises the box tree to `*image.RGBA` — AA text (real bold + italic), gradients, border-radius, box-shadow, opacity, images and SVG; also the real `Measurer` (go-opentype faces). `PaintVector` draws the same tree through a vector `Canvas`. |
| `pdfcanvas` | A `paint.Canvas` drawing into a go-pdfkit page: vector PDF with real text. |
| `engine` (root) | `Fetch`, `Render`, `Screenshot`, `RenderHTML`, `RenderWithLinks`, `LayoutHTML` → `PageLayout` (paint as image or vectors), the settle-then-render loop, image + SVG loading, and the anchor hit-map. |
| `cmd/render` | CLI: `render -url URL -out shot.png -w 1024 -h 768` (or `-file page.html`). |

Everything reused is pure-Go and BSD/MIT — see [`SURVEY.md`](SURVEY.md) for the
Expand Down
40 changes: 40 additions & 0 deletions css/gradient_geometry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) the go-webengine/engine authors.
// SPDX-License-Identifier: BSD-3-Clause

package css

import (
"math"
"testing"
)

// TestGradientGeometryAgreesWithAt: the shape Geometry reports is the one At
// samples — the colour At gives at a point is the stop colour at that point's
// position along the reported axis (linear) or ellipse (radial).
func TestGradientGeometryAgreesWithAt(t *testing.T) {
black, white := Color{A: 255}, Color{R: 255, G: 255, B: 255, A: 255}
stops := []ColorStop{{Color: black}, {Color: white}}

lin := (&Gradient{AngleDeg: 90, Stops: stops}).Sampler(200, 50)
g := lin.Geometry()
near := func(a, b float64) bool { return math.Abs(a-b) < 1e-9 }
if g.Radial || !near(g.X0, 0) || !near(g.X1, 200) || !near(g.Y0, 25) || !near(g.Y1, 25) {
t.Errorf("linear 90deg on 200×50: axis (%v,%v)→(%v,%v), want (0,25)→(200,25)", g.X0, g.Y0, g.X1, g.Y1)
}
if len(g.Stops) != 2 || g.Stops[0].Pos != 0 || g.Stops[1].Pos != 1 || g.Stops[1].Color != white {
t.Errorf("stops = %+v", g.Stops)
}
if c := lin.At(150, 10); math.Abs(float64(c.R)-0.75*255) > 1 {
t.Errorf("At(150) = %v, want 75 %% along the axis", c)
}

rad := (&Gradient{Radial: true, Shape: RadialCircle, Extent: ExtentClosestSide,
PosX: Length{Percent: .5, IsPercent: true}, PosY: Length{Percent: .5, IsPercent: true}, Stops: stops}).Sampler(100, 60)
g = rad.Geometry()
if !g.Radial || g.CX != 50 || g.CY != 30 || g.RX != 30 || g.RY != 30 {
t.Errorf("closest-side circle on 100×60: centre (%v,%v) radii (%v,%v), want (50,30) and 30", g.CX, g.CY, g.RX, g.RY)
}
if c := rad.At(50+15, 30); math.Abs(float64(c.R)-0.5*255) > 1 {
t.Errorf("At half the radius = %v, want the middle colour", c)
}
}
39 changes: 39 additions & 0 deletions css/gradient_sample.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,45 @@ func scaleToCorner(sx, sy, dx, dy float64) (float64, float64) {
return sx * k, sy * k
}

// GradientStop is one colour stop of a sampler, resolved: Pos is its
// fraction along the gradient line (linear) or ray (radial), non-decreasing
// from one stop to the next; two stops may share a position (a hard edge).
type GradientStop struct {
Pos float64
Color Color
}

// GradientGeometry is a sampler's resolved shape, in the box-local pixel
// space At is queried in: what a vector backend needs to emit the same
// gradient as a shading instead of sampling it pixel by pixel.
//
// For a linear gradient (Radial false) the colour at fraction t of the stops
// lies on the line through (X0,Y0) + t·((X1,Y1)−(X0,Y0)) perpendicular to
// that axis, clamped to the end colours beyond it. For a radial one it lies
// on the ellipse centred on (CX,CY) with radii t·RX and t·RY, clamped beyond
// t = 1.
type GradientGeometry struct {
Radial bool
X0, Y0, X1, Y1 float64
CX, CY, RX, RY float64
Stops []GradientStop
}

// Geometry returns the sampler's resolved shape and stops.
func (s GradientSampler) Geometry() GradientGeometry {
g := GradientGeometry{Radial: s.radial, Stops: make([]GradientStop, len(s.stops))}
for i, st := range s.stops {
g.Stops[i] = GradientStop{Pos: st.pos, Color: st.color}
}
if s.radial {
g.CX, g.CY, g.RX, g.RY = s.cx, s.cy, s.rx, s.ry
} else {
g.X0, g.Y0 = s.sx, s.sy
g.X1, g.Y1 = s.sx+s.dx*s.length, s.sy+s.dy*s.length
}
return g
}

// At returns the gradient colour at box-local point (px, py).
func (s GradientSampler) At(px, py float64) Color {
var t float64
Expand Down
36 changes: 25 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,39 @@ require (
github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9
github.com/evanw/esbuild v0.28.2
github.com/go-browserhttp/browserhttp v0.2.0
github.com/go-gfx/gfx v0.34.0
github.com/go-images/images v0.0.0-20260927173152-87444e36aac4
github.com/go-opentype/fonts v0.10.0
github.com/go-opentype/opentype v0.13.1-0.20260927180318-ae6327b14eac
github.com/go-gfx/gfx v0.35.0
github.com/go-images/images v0.1.0
github.com/go-opentype/fonts v0.12.0
github.com/go-opentype/opentype v0.15.0
github.com/go-pdfkit/pdfkit v0.15.0
github.com/go-webengine/esbuildsandbox v0.1.0
github.com/go-widgets/painter v0.13.0
github.com/go-widgets/painter v0.16.0
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
golang.org/x/image v0.46.0
golang.org/x/net v0.61.0
rsc.io/pdf v0.1.1
)

require (
github.com/ajroetker/go-highway v0.0.4 // indirect
github.com/andybalholm/brotli v1.2.5 // indirect
github.com/ajroetker/go-highway v0.0.12 // indirect
github.com/andybalholm/brotli v1.2.6 // indirect
github.com/breml/rootcerts v0.3.7 // indirect
github.com/coder/websocket v1.8.15 // indirect
github.com/dlclark/regexp2/v2 v2.5.2 // indirect
github.com/go-images/gif v0.1.0 // indirect
github.com/go-images/jpeg v0.2.0 // indirect
github.com/go-images/jpeg2000 v0.1.0 // indirect
github.com/go-images/png v0.1.0 // indirect
github.com/go-crdt/collab v0.77.1 // indirect
github.com/go-crdt/crdt v0.57.0 // indirect
github.com/go-icons/iconoir v0.3.0 // indirect
github.com/go-images/gif v0.2.0 // indirect
github.com/go-images/jpeg v0.3.0 // indirect
github.com/go-images/jpeg2000 v0.13.3 // indirect
github.com/go-images/png v0.2.0 // indirect
github.com/go-opentype/shape v0.7.0 // indirect
github.com/go-richdoc/richdoc v0.6.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/go-typeset/bidi v0.3.1 // indirect
github.com/go-widgets/mvvm v0.14.0 // indirect
github.com/go-widgets/toolkit v0.329.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/klauspost/compress v1.18.7 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect
Expand All @@ -37,4 +48,7 @@ require (
golang.org/x/crypto v0.58.0 // indirect
golang.org/x/sys v0.49.0 // indirect
golang.org/x/text v0.43.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
google.golang.org/grpc v1.84.0 // indirect
google.golang.org/protobuf v1.36.12 // indirect
)
80 changes: 58 additions & 22 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/ajroetker/go-highway v0.0.4 h1:RDQo+9OhTXI6BFctLo+5gYpHNbb92VYJ0ObnR4l6xAQ=
github.com/ajroetker/go-highway v0.0.4/go.mod h1:C/zYPNSSpOaraejY89FUTZTyQNEhi5+rEbU0LjlqJeU=
github.com/andybalholm/brotli v1.2.5 h1:BSI8V4zmx/3BAn6OKjF1PmfVq7Aoi52AdFsi6bpCx+s=
github.com/andybalholm/brotli v1.2.5/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/ajroetker/go-highway v0.0.12 h1:OcGp/l/W7WQtH5ELLuNnKXCuR7ePpWGh2RWZI/INtck=
github.com/ajroetker/go-highway v0.0.12/go.mod h1:un7fcrwix7UOHJrr18Lgh5eeF+1yaZiGWdTw3FbfDv4=
github.com/andybalholm/brotli v1.2.6 h1:ftYnfj6usCp+UGV5kSJ3+chpMQgU+gJf/AxsUQ52REI=
github.com/andybalholm/brotli v1.2.6/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/breml/rootcerts v0.3.7 h1:KZZkmd591bmq//te85L0u33zgtI3phyGlfD8u7/pzUg=
github.com/breml/rootcerts v0.3.7/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/dlclark/regexp2/v2 v2.5.2 h1:HAsucWRhsqcDzl6Ua9aR8JwYOTzrZyPrF0/FNxJVAI0=
github.com/dlclark/regexp2/v2 v2.5.2/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9 h1:Ksd3Vil12b/ykT5NjGzd3qNc688cJB9OUXJZZChEktI=
Expand All @@ -14,32 +16,56 @@ github.com/evanw/esbuild v0.28.2 h1:A2uETn4jrQTcXaT/shwTDTYBxDjl7fV7nXmUrJxfA2w=
github.com/evanw/esbuild v0.28.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
github.com/go-browserhttp/browserhttp v0.2.0 h1:3RcjOQVHgqhSOmI5cfsURpexw/vetoDUoLXKzgTCSac=
github.com/go-browserhttp/browserhttp v0.2.0/go.mod h1:VJiwBxLetXdc8eDadOcNbiO0EelNQPny0cLZhg1hTKU=
github.com/go-gfx/gfx v0.34.0 h1:K46srusNnZls/xkmJRvxpBajlptgrXc6d3exa4ZxhoA=
github.com/go-gfx/gfx v0.34.0/go.mod h1:TC2X+3GMXm8mYZPmp/v7bFrRa69p+k0xz0Nabbd9sII=
github.com/go-images/gif v0.1.0 h1:g2ZnRd41PutXIt1mCZSZsRpqqUiD0m+UbAjZ5ylC0RU=
github.com/go-images/gif v0.1.0/go.mod h1:8mk9sA/K3U61jL+rj4U+Yv3TRKeoGp2fMxF25ykOOwY=
github.com/go-images/images v0.0.0-20260927173152-87444e36aac4 h1:Likv0wVxr9vDo2ITd4A7SOMKCrN3efOHs9CsANj2y+0=
github.com/go-images/images v0.0.0-20260927173152-87444e36aac4/go.mod h1:bc99LrkZxVUjP+Di3JCmf4SoHzim/PNcHEJfKd1yUtg=
github.com/go-images/jpeg v0.2.0 h1:dn55cTczPdOJf+NQq+sETCFeov/KeP1haF1ES+FJa+4=
github.com/go-images/jpeg v0.2.0/go.mod h1:2C1DevAM+HaQlysTLbSdyol8X/RlvmsXmHAXs9teAqo=
github.com/go-images/jpeg2000 v0.1.0 h1:SiFAZ6NGvGLU+VmxKji74RiSQDeK6TyjUZr1wjlxRzY=
github.com/go-images/jpeg2000 v0.1.0/go.mod h1:MwSmrbiP6RK7eLphoNyzY60BAXWOD+8LVdBYDPIBrN4=
github.com/go-images/png v0.1.0 h1:OI4yHotS0lA75sLnG2FTL2Vooh4PWpVWEidre1msSe0=
github.com/go-images/png v0.1.0/go.mod h1:V9vaEbr2nrZGorUWn4xv9F24Yuu1/qLVtmailM9GBVo=
github.com/go-opentype/fonts v0.10.0 h1:9f7ik795SlBEJjz6iqBE/ZV2PaTauAj537MMWw9t4OQ=
github.com/go-opentype/fonts v0.10.0/go.mod h1:b+o8v1zDZ6B3GixA398jGILAh09+WwfyoYz52zKe7DA=
github.com/go-opentype/opentype v0.13.1-0.20260927180318-ae6327b14eac h1:mpzfJQe1RlF2zawKqvL5poyNdaV6pMbHkK1h+GVLMuM=
github.com/go-opentype/opentype v0.13.1-0.20260927180318-ae6327b14eac/go.mod h1:UjZOTCMtkHheDPzfP0ZghDoQ7wIr6L72T/kuC9Ph1hw=
github.com/go-crdt/collab v0.77.1 h1:eKzzFLIt4eQpM/YLRbbViMbSdHcN6ivSHeA5aldqgzw=
github.com/go-crdt/collab v0.77.1/go.mod h1:LFqHBOnnr2kRFrTxCVJYWGlaF8jc7l51Z7yVrUY2RnA=
github.com/go-crdt/crdt v0.57.0 h1:sNLDTVCEnSb20gm77BUuYav8dzj5krmly/z9O7eRMCo=
github.com/go-crdt/crdt v0.57.0/go.mod h1:zdVTTDLGV5JYAoTJDqCivcAT+/B5gGebDsVd8QNZpVs=
github.com/go-gfx/gfx v0.35.0 h1:AaXMxDMxNB1hROxzx+3EoO06KxsrQUvnw/10olmSgVo=
github.com/go-gfx/gfx v0.35.0/go.mod h1:qIZS9u2+VnBk5CX7o4f/fQ5uD8c+mPFTtAPE85s8V90=
github.com/go-icons/iconoir v0.3.0 h1:kHG2sU5IsCvtBS1JICuXgfFo5f8UfwY1m7KYMVy97CI=
github.com/go-icons/iconoir v0.3.0/go.mod h1:lPh5OuzVQ+rR4qDPGgAdSRUhnCbHeCAX70HDdwp8/RI=
github.com/go-images/gif v0.2.0 h1:8Xl4Nan+PvyqH4wPre7wJUH61T2eJKOKonFKOdMdpkU=
github.com/go-images/gif v0.2.0/go.mod h1:rjpP1X62GLs6pmdMxuvwBjpV4LYE4Ue6rPbeqANWHrQ=
github.com/go-images/images v0.1.0 h1:KbbxTUpCbddpf6UsMFQetm75xsRXREyUjX0CIvd8YvE=
github.com/go-images/images v0.1.0/go.mod h1:+8V6BRjITiKEZyyvZZm//bQ8Pdr5rgHgLEElkVOMm40=
github.com/go-images/jpeg v0.3.0 h1:i6CGNbN2YNEz8stBI0hXjN6UwVGwQ9zCh8LvFeD8fHg=
github.com/go-images/jpeg v0.3.0/go.mod h1:3eUjtNscxUULLUH88DvMN8OKTq2C0/0AjubOjAHoHHk=
github.com/go-images/jpeg2000 v0.13.3 h1:GSnkQWQ72ANwfi/xYWt3za9Nfss+C1iBbk4uubLWYHw=
github.com/go-images/jpeg2000 v0.13.3/go.mod h1:r07D0wUJxBPZcsLTkLFt3OkL3DubrH6DsmRjJRnZYlo=
github.com/go-images/png v0.2.0 h1:RFPkHPuRYRvrDJol/Mmn9P1DSaLV8VPmEQ8BkZoCpL0=
github.com/go-images/png v0.2.0/go.mod h1:g1qayDmWLClHo9R5KQ+RrcUNTBcaA4c1thaIfEoS3vg=
github.com/go-opentype/fonts v0.12.0 h1:gjYYkbDZBK3JHpraakgGNnXdkGq8oADerhDYjC22ILQ=
github.com/go-opentype/fonts v0.12.0/go.mod h1:Kiep7ojxxaR2ALFeB/akcFHQyl1gG5OLIobp8EzaZSA=
github.com/go-opentype/opentype v0.15.0 h1:ioE3eCbNZavlk/w073J3uX5FKjqhgOHQw+wZZdJ+gE4=
github.com/go-opentype/opentype v0.15.0/go.mod h1:ZB31jTJl2mhPESXYMuUW+p36UrOabw/2+6mtyh/vNnI=
github.com/go-opentype/shape v0.7.0 h1:hXUkI4xdpkzq31ZYf/6zfkDAgRLZnTjI1q8DWLu5MGQ=
github.com/go-opentype/shape v0.7.0/go.mod h1:n8pMQ/MH4Oscxwd9J0B7RiZ5gKlD0yagRQKYsAmJ9Rw=
github.com/go-pdfkit/pdfkit v0.15.0 h1:xfG96T7VYUgGMLNTZG1w0ISwLTGdgY1oWF0VDG5A0kE=
github.com/go-pdfkit/pdfkit v0.15.0/go.mod h1:FrCLKGwHSPlEvsT/Ud7mO81wK1rvKZFxeH2ioesa1/4=
github.com/go-richdoc/richdoc v0.6.0 h1:cema3hE5AAektDXTmvhWyBVnkCcZTVKhnkingwqsN7k=
github.com/go-richdoc/richdoc v0.6.0/go.mod h1:RhxAcqwoUg1pnTYCiRdKILzOZwOmraO0dMJI4N6yY70=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-typeset/bidi v0.3.1 h1:fsTa0b9txt2SkH7M55EZ06+q3N9C4O0Oerf+SPLLGFs=
github.com/go-typeset/bidi v0.3.1/go.mod h1:ct3cmYT8Qt1FGJQ+2QaakrUxqaP4ZysgdQQg2ym5+Xo=
github.com/go-webengine/esbuildsandbox v0.1.0 h1:1LdmX+0/yJarQlxZgJ7JIMu+Qy6P7Yzu4NFcX9+jFl8=
github.com/go-webengine/esbuildsandbox v0.1.0/go.mod h1:za8oj1vbDfVkTpHLeRk7JfEQjmJfj+GHNq4R47681vQ=
github.com/go-widgets/painter v0.13.0 h1:xBPbopFuYfeDVbQKUKviU6HnP2mAhztJ+BpasahlJLU=
github.com/go-widgets/painter v0.13.0/go.mod h1:IPRLqdUJuJX8sfuHeYLZCzjoLvA0ApbOlyIAVmguJDQ=
github.com/go-widgets/mvvm v0.14.0 h1:T7rDfZkoaWLycULc8XC6ZXYE0fBMhQgqkZRahPRBSS8=
github.com/go-widgets/mvvm v0.14.0/go.mod h1:LF77YW5D9sfgpS1WBP3uMG4gV4QfJhRUmFfUNPXxPlg=
github.com/go-widgets/painter v0.16.0 h1:j8FFObisySQChY2JiXS7Di2Mo4cqufIc0PDUnoDPMiA=
github.com/go-widgets/painter v0.16.0/go.mod h1:3wKWjaM8ZgEhir2HX0UDt8GemePKJ7l/yIjQb/vVSWk=
github.com/go-widgets/toolkit v0.329.0 h1:rJt/qAV+5skj5ie7najnKtolEm5fR4x6kd/eAEViVNQ=
github.com/go-widgets/toolkit v0.329.0/go.mod h1:9own9U7JOYqsiCp9p8/+zlIrobjZQj0uQ39KrY1cBgo=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/grpc-transports/websocket v0.4.0 h1:P/PyEwzEOQRooWw4HNGEk/D8bkuoHJ/kwVw8EaPspPg=
github.com/grpc-transports/websocket v0.4.0/go.mod h1:mgExQGqHkZm7empGD6W/+/Va58EaptwEqUpOR2DmnYY=
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
Expand Down Expand Up @@ -67,3 +93,13 @@ golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
Loading
Loading