package ui import ( "gioui.org/unit" "pad/internal/io/pool/types" ) // Dp represents device-independent pixels. Use for all element positions, // sizes, and spacing in the logic/layout layer. type Dp unit.Dp // Px represents physical device pixels. Use only when interfacing with // Gio's layout.Context (gtx.Constraints, gtx.Dp(), etc.). type Px int // ToDp converts physical pixels to device-independent pixels using the // given scale factor (pixels per DP). Typically from gtx.Metric.PxPerDp. func ToDp(px Px, scale float32) Dp { return Dp(float32(px) / scale) } // ToPx converts device-independent pixels to physical pixels using the // given scale factor (pixels per DP). func ToPx(dp Dp, scale float32) Px { return Px(float32(dp) * scale) } // PxPerDp returns the scale factor: how many physical pixels per DP. // Use this to convert between Dp and Px. func PxPerDp(scale float32) float32 { return scale } // --- Gio interop helpers --- // DpToPx converts a gioui unit.Dp to our Px using the scale factor. func DpToPx(d unit.Dp, scale float32) Px { return Px(float32(d) * scale) } // PxToDp converts our Px to a gioui unit.Dp using the scale factor. func PxToDp(p Px, scale float32) unit.Dp { return unit.Dp(float32(p) / scale) } // RegionPx is a region in physical pixels. Used for Gio interop only. type RegionPx struct { X, Y Px W, H Px } // ToDp converts a RegionPx to a Region (Dp) using the scale factor. func (r RegionPx) ToDp(scale float32) Region { return Region{ X: ToDp(r.X, scale), Y: ToDp(r.Y, scale), W: ToDp(r.W, scale), H: ToDp(r.H, scale), } } // FromDp converts a Region (Dp) to a RegionPx using the scale factor. func FromDp(r Region, scale float32) RegionPx { return RegionPx{ X: ToPx(r.X, scale), Y: ToPx(r.Y, scale), W: ToPx(r.W, scale), H: ToPx(r.H, scale), } } // GlyphLayout holds per-glyph layout data captured during text shaping. // Each index i represents one glyph (one rune in the source string). // ByteOffsets[i] is the byte position in the buffer, (X[i], Y[i]) is the // glyph's screen location in Dp (X relative to text region origin, Y is the // shaper baseline), and Advance[i] is the glyph's width in Dp. // LineHeight is the shaper's actual baseline-to-baseline line height in Dp, // derived from consecutive lines' Y values. type GlyphLayout struct { ByteOffsets []int // byte offset of each glyph in the buffer X []Dp // screen X (Dp) of each glyph, relative to text region origin Y []Dp // screen Y (Dp) baseline of each glyph (shaper value) Advance []Dp // advance width (Dp) of each glyph LineHeight Dp // shaper's actual baseline-to-baseline line height in Dp VisualLineStarts []int // byte offsets where each visual line starts (for word wrap) VisualLineIndex *types.VisualLineIndex // Optional: pre-computed visual line index for this layout } // VisualLineOffsets returns the byte offset of each visual line start. // Uses pre-captured VisualLineStarts if available, otherwise computes from glyph data. func (gl GlyphLayout) VisualLineOffsets() []int32 { if len(gl.VisualLineStarts) > 0 { // Use pre-captured visual line starts (more accurate for word wrap) offsets := make([]int32, len(gl.VisualLineStarts)) for i, v := range gl.VisualLineStarts { offsets[i] = int32(v) } return offsets } // Fallback: compute from glyph data (less accurate for word wrap) var offsets []int32 if len(gl.ByteOffsets) == 0 { return offsets } // The first line always starts at byte 0 offsets = append(offsets, int32(gl.ByteOffsets[0])) // Add byte offset for each new Y coordinate for i := 1; i < len(gl.Y); i++ { if gl.Y[i] != gl.Y[i-1] { offsets = append(offsets, int32(gl.ByteOffsets[i])) } } return offsets }