1package core
2
3import (
4 "errors"
5 "image"
6 "math"
7
8 "github.com/bnema/nefercap/internal/ports"
9)
10
11// GridLineCount is the number of guide lines of a monitor grid.
12const GridLineCount = 6
13
14// MaxGridScale is the largest output scale a grid accepts. It bounds the
15// physical size to MaxDimension * MaxGridScale, far inside int32.
16const MaxGridScale = 8
17
18// scaleEpsilon absorbs binary floating-point noise at exact integer
19// boundaries (1600 * 1.2 is 1920.0000000000002), like the surface buffer
20// size the window system derives from the same logical size and scale.
21const scaleEpsilon = 1e-9
22
23// ErrInvalidGrid reports a grid requested for an unusable size or scale.
24var ErrInvalidGrid = errors.New("invalid grid size or scale")
25
26// GridLines are the guide lines of a monitor in physical pixels, origin at the
27// monitor's top-left: three vertical lines (1/3, 1/2, 2/3 of the width) then
28// three horizontal lines (1/3, 1/2, 2/3 of the height). Each line is one
29// physical pixel thick and spans the whole monitor. It is a plain value.
30type GridLines [GridLineCount]image.Rectangle
31
32// PhysicalExtent returns the physical pixel count of a logical extent at a
33// possibly fractional scale: ceil(logical * scale), computed like the surface
34// buffer size with a small epsilon so float noise never adds a pixel. It
35// fails with ErrInvalidGrid for a logical extent outside 1..MaxDimension, a
36// scale that is not finite or above MaxGridScale, or a result below one pixel.
37func PhysicalExtent(logical int, scale float64) (int, error) {
38 if logical < 1 || logical > ports.MaxDimension ||
39 math.IsNaN(scale) || scale <= 0 || scale > MaxGridScale {
40 return 0, ErrInvalidGrid
41 }
42 n := int(math.Ceil(float64(logical)*scale - scaleEpsilon))
43 if n < 1 {
44 return 0, ErrInvalidGrid
45 }
46 return n, nil
47}
48
49// MonitorGrid returns the fixed guide lines of a whole monitor of the given
50// logical size at the given scale, on the surface buffer PhysicalExtent gives
51// for each axis. Every line coordinate is snapped to a whole physical pixel
52// first (floor of the exact 1/3, 1/2 and 2/3 position) and is exactly one
53// pixel thick, so callers convert to logical units only afterwards. It does
54// not allocate and returns the zero value with ErrInvalidGrid on bad input.
55//
56// The buffer can be a pixel larger than the output mode when the logical size
57// was rounded; use MonitorGridPhysical with the mode when it is known.
58func MonitorGrid(width, height int, scale float64) (GridLines, error) {
59 pw, err := PhysicalExtent(width, scale)
60 if err != nil {
61 return GridLines{}, err
62 }
63 ph, err := PhysicalExtent(height, scale)
64 if err != nil {
65 return GridLines{}, err
66 }
67 return physicalGrid(pw, ph), nil
68}
69
70// MonitorGridPhysical returns the guide lines of a monitor whose physical
71// size is exactly width x height pixels, for example the output mode. The
72// lines are snapped and one pixel thick as in MonitorGrid and span the given
73// size. Each size must be 1..MaxDimension*MaxGridScale, since a physical mode
74// can exceed MaxDimension when the scale is above 1. It does not allocate and
75// returns the zero value with ErrInvalidGrid on bad input.
76func MonitorGridPhysical(width, height int) (GridLines, error) {
77 const limit = ports.MaxDimension * MaxGridScale
78 if width < 1 || width > limit || height < 1 || height > limit {
79 return GridLines{}, ErrInvalidGrid
80 }
81 return physicalGrid(width, height), nil
82}
83
84// physicalGrid lays the lines out on an exact physical size of at least 1x1.
85func physicalGrid(pw, ph int) GridLines {
86 var g GridLines
87 // Integer floor division keeps the snap exact for every size.
88 xs := [3]int{pw / 3, pw / 2, pw * 2 / 3}
89 ys := [3]int{ph / 3, ph / 2, ph * 2 / 3}
90 for i := range xs {
91 g[i] = image.Rect(xs[i], 0, xs[i]+1, ph)
92 g[3+i] = image.Rect(0, ys[i], pw, ys[i]+1)
93 }
94 return g
95}