Documentation
¶
Index ¶
- func Draw(bounds image.Rectangle, dst *ebiten.Image, str string, options *DrawOptions)
- func FindWordBoundaries(text string, idx int) (start, end int)
- func FirstLineBreakPositionAndLen(str string) (pos, length int)
- func LastLineBreakPositionAndLen(str string) (pos, length int)
- func Measure(width int, str string, autoWrap bool, face text.Face, lineHeight float64, ...) (float64, float64)
- func MeasureHeight(width int, str string, autoWrap bool, face text.Face, lineHeight float64, ...) float64
- func MeasureLogicalLine(width int, logicalLine string, autoWrap bool, face text.Face, ...) (float64, float64)
- func MeasureLogicalLineHeight(width int, logicalLine string, autoWrap bool, face text.Face, ...) float64
- func NextPositionOnGraphemes(str string, position int) int
- func PrevPositionOnGraphemes(str string, position int) int
- func TextIndexFromPosition(p *TextIndexFromPositionParams) int
- func TextIndexFromPositionInLogicalLine(width int, position image.Point, logicalLine string, options *Options) int
- func VisualLineCountForLogicalLine(width int, logicalLine string, autoWrap bool, face text.Face, tabWidth float64, ...) int
- type CompositionInfo
- type CompositionInfoParams
- type DrawOptions
- type HorizontalAlign
- type LineByteOffsets
- func (l *LineByteOffsets) ByteOffsetByLineIndex(i int) int
- func (l *LineByteOffsets) LineCount() int
- func (l *LineByteOffsets) LineIndexForByteOffset(byteOffset int) int
- func (l *LineByteOffsets) Rebuild(scan func(io.Writer) error) error
- func (l *LineByteOffsets) Replace(newText string, start, end int, startCtx, endCtx string, atEOT bool)
- func (l *LineByteOffsets) Reset()
- type Options
- type TextIndexFromPositionParams
- type TextPosition
- type TextPositionFromIndexParams
- type VerticalAlign
- type VisibleRange
- type VisibleRangeInViewportParams
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FindWordBoundaries ¶
func LastLineBreakPositionAndLen ¶
LastLineBreakPositionAndLen returns the position and the byte length of the last line break in str. If no line break is found, it returns (-1, 0).
func MeasureHeight ¶
func MeasureHeight(width int, str string, autoWrap bool, face text.Face, lineHeight float64, tabWidth float64, keepTailingSpace bool) float64
MeasureHeight is like Measure but only returns height. When width does not need to be computed, this avoids per-visual-line shaping calls and is dramatically cheaper for very long text (e.g. a multi-megabyte editor buffer).
func MeasureLogicalLine ¶
func MeasureLogicalLine(width int, logicalLine string, autoWrap bool, face text.Face, lineHeight float64, tabWidth float64, keepTailingSpace bool, ellipsisString string) (float64, float64)
MeasureLogicalLine returns the rendered width and height of one logical line at the given width. Per-logical-line counterpart of Measure.
func MeasureLogicalLineHeight ¶
func MeasureLogicalLineHeight(width int, logicalLine string, autoWrap bool, face text.Face, lineHeight float64, tabWidth float64, keepTailingSpace bool) float64
MeasureLogicalLineHeight returns the rendered height of one logical line at the given width. This is the per-logical-line counterpart of MeasureHeight and is used by virtualized layout to size lines one at a time without scanning the whole document.
func NextPositionOnGraphemes ¶
func PrevPositionOnGraphemes ¶
func TextIndexFromPosition ¶
func TextIndexFromPosition(p *TextIndexFromPositionParams) int
TextIndexFromPosition returns the byte offset in the rendering text closest to p.Position. When p.LineByteOffsets is supplied, the visual-line walk is localized: it starts from (p.LogicalLineIndexHint, p.VisualLineIndexHint) and steps forward (or backward) one logical line at a time until the line covering p.Position.Y is found. With the hint placed inside the viewport this costs O(visible lines) of typesetting per query, instead of the O(documentLen) full scan the sidecar-less fallback performs.
When an active IME composition splices into the rendering text, the committed-text sidecar is reused: byte/visual-line shifts derived from ComputeCompositionInfo map between committed and rendering coordinates without rebuilding the sidecar. Falls back to the unrestricted whole-document walk when the composition crosses a logical-line boundary, when no sidecar is supplied, or when the document is empty. The fallback is observationally equivalent to the fast path.
func TextIndexFromPositionInLogicalLine ¶
func TextIndexFromPositionInLogicalLine(width int, position image.Point, logicalLine string, options *Options) int
TextIndexFromPositionInLogicalLine returns the byte offset within one logical line closest to the given position. The position's Y is relative to the top of the logical line. Counterpart of TextIndexFromPosition.
func VisualLineCountForLogicalLine ¶
func VisualLineCountForLogicalLine(width int, logicalLine string, autoWrap bool, face text.Face, tabWidth float64, keepTailingSpace bool) int
VisualLineCountForLogicalLine returns the number of visual lines one logical line wraps into at the given width. With autoWrap off (or when the line fits) the result is always 1.
Types ¶
type CompositionInfo ¶
type CompositionInfo struct {
// LineIndex is the logical-line index of the selection line.
// Lines with index > LineIndex are "past the splice" and have
// RenderingByteShift and RenderingYShift applied.
LineIndex int
// RenderingByteShift is added to a past-the-splice line's
// committed byte offset to get its rendering byte offset. Equals
// the composition's byte length minus the length of the committed
// range it replaces, so it can be negative for selection-
// replacement compositions.
RenderingByteShift int
// RenderingYShift is added to a past-the-splice line's committed
// visual-Y (in pixels, top-of-line) to get its rendering visual-Y.
// Non-zero only when AutoWrap is on and the composition causes the
// selection line to wrap into a different number of visual sub-lines.
RenderingYShift int
}
CompositionInfo describes how an active IME composition shifts the document layout for the visible-range slicer. The zero value is safe to pass when no composition is active: the shifts are zero, so any "past the splice" comparison the slicer makes is harmless.
func ComputeCompositionInfo ¶
func ComputeCompositionInfo(p *CompositionInfoParams) (CompositionInfo, bool)
ComputeCompositionInfo classifies an active composition and returns info that the textutil functions use to translate between committed and rendering byte/visual-line coordinates. ok is false when the splice changes the logical-line count - a hard line break inside the composition or a selection that straddles a logical line boundary - and the caller should fall back to drawing the unrestricted text.
type CompositionInfoParams ¶
type CompositionInfoParams struct {
// CompositionText is the active composition's bytes — the bytes
// inserted into the rendering text at SelectionStart, replacing
// committed[SelectionStart:SelectionEnd].
CompositionText string
// LineByteOffsets is the logical-line layout of the committed text.
LineByteOffsets *LineByteOffsets
// SelectionStart and SelectionEnd are byte offsets into the
// committed text describing the range the composition replaces.
// SelectionStart == SelectionEnd for a pure insertion.
SelectionStart int
SelectionEnd int
// AutoWrap toggles the visual-Y delta measurement for the
// selection line. When false, RenderingYShift in the result is
// always 0 and the fields below are ignored.
AutoWrap bool
// CommittedSelectionLine and RenderingSelectionLine are the bytes
// of the logical line containing the selection (SelectionStart ..
// SelectionEnd, which always lies within a single logical line —
// the function rejects multi-line selections), in committed and
// rendering coordinates respectively. Required when AutoWrap is
// true; ignored otherwise.
CommittedSelectionLine string
RenderingSelectionLine string
// Face, LineHeight, TabWidth, KeepTailingSpace are passed through
// to [MeasureLogicalLineHeight] when AutoWrap is true.
Face text.Face
LineHeight float64
TabWidth float64
KeepTailingSpace bool
// WrapWidth is the pixel width at which logical lines wrap into
// visual sublines. Values <= 0 are treated as math.MaxInt (no
// wrapping).
WrapWidth int
}
CompositionInfoParams describes the inputs for ComputeCompositionInfo.
type DrawOptions ¶
type DrawOptions struct {
Options
TextColor color.Color
DrawSelection bool
SelectionStart int
SelectionEnd int
SelectionColor color.Color
DrawComposition bool
CompositionStart int
CompositionEnd int
CompositionActiveStart int
CompositionActiveEnd int
InactiveCompositionColor color.Color
ActiveCompositionColor color.Color
CompositionBorderWidth float32
// VisibleBounds, when non-empty, restricts per-line drawing to lines that
// intersect this rectangle. Lines fully above or below are skipped without
// shaping, which matters for very long text whose [bounds] greatly exceed
// the on-screen viewport. When empty, [bounds] is used (back-compatible).
VisibleBounds image.Rectangle
}
type HorizontalAlign ¶
type HorizontalAlign int
const ( HorizontalAlignStart HorizontalAlign = iota HorizontalAlignCenter HorizontalAlignEnd HorizontalAlignLeft HorizontalAlignRight )
type LineByteOffsets ¶
type LineByteOffsets struct {
// contains filtered or unexported fields
}
LineByteOffsets holds the byte offsets where each logical line (segment separated by hard line breaks) begins within a source string. It is a precomputed sidecar that enables O(log n) line<->byte-offset lookups without rescanning the text.
After a rebuild the first entry is always 0 and the entries are strictly increasing. A trailing line break in the source string creates an extra empty line at the end (e.g. "abc\n" has two logical lines).
func (*LineByteOffsets) ByteOffsetByLineIndex ¶
func (l *LineByteOffsets) ByteOffsetByLineIndex(i int) int
ByteOffsetByLineIndex returns the byte offset of the start of the i-th logical line. Panics if i is out of range.
func (*LineByteOffsets) LineCount ¶
func (l *LineByteOffsets) LineCount() int
LineCount returns the number of logical lines.
The empty string has one logical line. A trailing line break creates an extra empty line, so "abc\n" has two logical lines.
func (*LineByteOffsets) LineIndexForByteOffset ¶
func (l *LineByteOffsets) LineIndexForByteOffset(byteOffset int) int
LineIndexForByteOffset returns the index of the logical line that contains byteOffset. byteOffset is clamped: negative values map to line 0 and values past the text map to the last line.
func (*LineByteOffsets) Rebuild ¶
func (l *LineByteOffsets) Rebuild(scan func(io.Writer) error) error
Rebuild discards any current contents and rescans the bytes written by scan for logical-line starts. The io.Writer passed to scan accepts bytes in any number of chunks; the recorded offsets are the same as if the concatenated bytes had been scanned in a single pass. Any error from scan is returned unchanged after the trailing partial-break state has been flushed.
func (*LineByteOffsets) Replace ¶
func (l *LineByteOffsets) Replace(newText string, start, end int, startCtx, endCtx string, atEOT bool)
Replace updates the offsets to reflect a splice that replaced the source text's [start, end) byte range with newText. The caller must invoke Replace immediately after applying the same splice to the underlying source.
startCtx is the up-to-2-byte slice of pre-splice text immediately before start (positions [max(0, start-2), start)); it is used to derive the scanner's entry state at start so a partial break ending at the splice boundary disambiguates correctly. endCtx is the up-to-3-byte slice of post-splice text immediately after newText (positions [start+len(newText), start+len(newText)+3)); it provides the lookahead the scanner needs for breaks formed at the splice's far boundary. atEOT must be true iff endCtx reaches end-of-text (i.e. start+len(newText)+3 ≥ post-splice length).
After Replace returns, the offsets describe the post-splice text.
func (*LineByteOffsets) Reset ¶
func (l *LineByteOffsets) Reset()
Reset clears the offsets. After Reset, LineCount returns 0; callers that expect at least one line must rebuild first.
type Options ¶
type Options struct {
AutoWrap bool
Face text.Face
LineHeight float64
HorizontalAlign HorizontalAlign
VerticalAlign VerticalAlign
TabWidth float64
KeepTailingSpace bool
EllipsisString string
}
type TextIndexFromPositionParams ¶
type TextIndexFromPositionParams struct {
// Position is the (x, y) point in the rendering plane to query.
// Y is measured from the top of the rendered text.
Position image.Point
// RenderingTextRange returns rendering[start:end), where the
// rendering text is the committed text with any active composition
// spliced in. RenderingTextLength is the total byte length of the
// rendering text. Required: all reads of the rendering text — both
// the fast path and the slow-path fallback — go through this
// callback so the caller never has to materialize the full
// document.
RenderingTextRange func(start, end int) string
RenderingTextLength int
// Width is the rendering width.
Width int
// Options carries face, lineHeight, autoWrap, alignment, tab
// width, etc.
Options *Options
// CommittedTextRange returns committed[start:end). Required when
// CompositionLen > 0; ignored otherwise.
CommittedTextRange func(start, end int) string
// LineByteOffsets is the logical-line layout of the committed text.
// Optional; when nil [TextIndexFromPosition] falls back to an
// O(documentLen) walk of every visual line.
LineByteOffsets *LineByteOffsets
// SelectionStart, SelectionEnd, CompositionLen describe an active
// IME composition: bytes [SelectionStart, SelectionEnd) in the
// committed text are replaced with bytes [SelectionStart,
// SelectionStart+CompositionLen) in the rendering text.
// CompositionLen == 0 means no active composition; the other
// fields are ignored in that case.
SelectionStart int
SelectionEnd int
CompositionLen int
// LogicalLineIndexHint / VisualLineIndexHint are an optional hint
// that tells [TextIndexFromPosition] where to start its per-
// logical-line walk instead of starting from line 0.
// LogicalLineIndexHint is a logical-line index in committed text;
// VisualLineIndexHint is the cumulative number of visual lines
// preceding that logical line in committed text. The walk steps
// forward (or backward) from the hint measuring one logical line
// at a time, so a caller that places the hint inside its viewport
// pays O(visible lines) of typesetting per query instead of
// walking from the document top.
//
// Both fields are optional. The zero value means "start from line
// 0," equivalent to walking from the top of the document — correct
// but O(documentLen) when the click is far down. Used only when
// LineByteOffsets is set.
LogicalLineIndexHint int
VisualLineIndexHint int
}
TextIndexFromPositionParams describes the inputs for TextIndexFromPosition. The first group of fields is always required; the second group is optional state that enables the sidecar-accelerated fast path.
type TextPosition ¶
func TextPositionFromIndex ¶
func TextPositionFromIndex(p *TextPositionFromIndexParams) (position0, position1 TextPosition, count int)
TextPositionFromIndex returns the visual position(s) corresponding to p.Index in the rendering text. When p.LineByteOffsets is supplied, the visual-line walk is localized: it starts from (p.LogicalLineIndexHint, p.VisualLineIndexHint) and steps forward (or backward) one logical line at a time, measuring per-line wrap counts, until it reaches the logical line containing p.Index. With the hint placed inside the viewport the cost is O(visible logical lines) per query, instead of the O(documentLen) full scan the sidecar-less fallback performs.
When an active IME composition splices into the rendering text, the committed-text sidecar is reused: byte/visual-line shifts derived from ComputeCompositionInfo map between committed and rendering coordinates without rebuilding the sidecar. Falls back to the unrestricted whole-document walk when the composition crosses a logical-line boundary, when no sidecar is supplied, or when the document is empty. The fallback is observationally equivalent to the fast path.
func TextPositionFromIndexInLogicalLine ¶
func TextPositionFromIndexInLogicalLine(width int, logicalLine string, index int, options *Options) (position0, position1 TextPosition, count int)
TextPositionFromIndexInLogicalLine returns the visual position(s) within one logical line corresponding to the given byte index inside that line. The Y values are relative to the top of the logical line (so the caller can offset them by the line's origin Y). Counterpart of TextPositionFromIndex.
index is a byte offset in [0, len(logicalLine)]. Out-of-range values yield (TextPosition{}, TextPosition{}, 0).
type TextPositionFromIndexParams ¶
type TextPositionFromIndexParams struct {
// Index is the byte offset in the rendering text to query.
Index int
// RenderingTextRange returns rendering[start:end), where the
// rendering text is the committed text with any active composition
// spliced in. RenderingTextLength is the total byte length of the
// rendering text. Required: all reads of the rendering text — both
// the fast path and the slow-path fallback — go through this
// callback so the caller never has to materialize the full
// document.
RenderingTextRange func(start, end int) string
RenderingTextLength int
// Width is the rendering width.
Width int
// Options carries face, lineHeight, autoWrap, alignment, tab
// width, etc.
Options *Options
// CommittedTextRange returns committed[start:end). Required when
// CompositionLen > 0; ignored otherwise.
CommittedTextRange func(start, end int) string
// LineByteOffsets is the logical-line layout of the committed text.
// Optional; when nil [TextPositionFromIndex] falls back to an
// O(documentLen) walk of every visual line.
LineByteOffsets *LineByteOffsets
// SelectionStart, SelectionEnd, CompositionLen describe an active
// IME composition: bytes [SelectionStart, SelectionEnd) in the
// committed text are replaced with bytes [SelectionStart,
// SelectionStart+CompositionLen) in the rendering text.
// CompositionLen == 0 means no active composition; the other
// fields are ignored in that case.
SelectionStart int
SelectionEnd int
CompositionLen int
// LogicalLineIndexHint / VisualLineIndexHint pin the result's Y
// coordinate system: the function treats the logical line at
// LogicalLineIndexHint as starting at visual-line index
// VisualLineIndexHint, and walks forward (or backward) from there
// to whichever line contains Index. The returned position's Top
// is therefore measured in the caller's coordinate system —
// (0, 0) means "Y is measured from line 0," matching the legacy
// behavior; (firstLogicalLineInViewport, 0) means "Y is measured
// from the first visible line's top," used by virtualized text.
//
// The walk is bounded by the logical-line distance between the
// hint and the line containing Index, so a caller that pins the
// hint inside its viewport pays only O(visible) typesetting per
// query. Used only when LineByteOffsets is set and Options.AutoWrap
// is true.
LogicalLineIndexHint int
VisualLineIndexHint int
}
TextPositionFromIndexParams describes the inputs for TextPositionFromIndex. The first group of fields is always required; the second group is optional state that enables the sidecar-accelerated fast path.
type VerticalAlign ¶
type VerticalAlign int
const ( VerticalAlignTop VerticalAlign = iota VerticalAlignMiddle VerticalAlignBottom )
type VisibleRange ¶
type VisibleRange struct {
// FirstLine and LastLine are the inclusive range of logical-line
// indices the caller should draw.
FirstLine, LastLine int
// StartInBytes and EndInBytes are the byte range of the rendering
// text the caller should draw: rendering[StartInBytes:EndInBytes].
StartInBytes, EndInBytes int
// YShift is added to the drawing-origin Y so the first sliced line
// lands at its original screen Y. Already includes the alignment-
// specific portion of the original Y offset, so the caller forces
// [VerticalAlignTop] when calling [Draw].
YShift int
}
VisibleRange is the result of VisibleRangeInViewport when its ok return is true.
func VisibleRangeInViewport ¶
func VisibleRangeInViewport(p *VisibleRangeInViewportParams) (VisibleRange, bool)
VisibleRangeInViewport returns the byte range and logical-line indices that cover the visible region when the widget is positioned so FirstLogicalLineInViewport sits at widget-local Y=0. The walk steps forward from FirstLogicalLineInViewport, measuring each logical line's wrap count on the fly, so a caller pinned to the topmost visible line pays only O(visible) typesetting per query. Composition splices on lines past the splice are handled by reading rendering-text bytes for the composition's selection line.
ok is false when the document is empty.
VerticalAlign is intentionally not part of the input: when the caller pins the viewport at a non-zero logical line, the document is assumed to overflow the viewport (the case where alignment matters), so YShift is always 0 and the caller's bounds positioning carries any needed offset itself.
type VisibleRangeInViewportParams ¶
type VisibleRangeInViewportParams struct {
// FirstLogicalLineInViewport is the logical line whose top sits
// at the widget-local origin (Y=0). The caller's bounds-positioning
// places this line at the top of the rendered output, so the
// returned VisibleRange.FirstLine is always this index (clamped to
// the document) and YShift is always 0.
FirstLogicalLineInViewport int
// LineByteOffsets is the logical-line layout of the committed
// text. The number of logical lines comes from its LineCount.
LineByteOffsets *LineByteOffsets
// RenderingTextRange returns rendering[start:end). The walker
// reads each measured line through this callback so the caller
// never has to materialize the full rendering text. Required when
// AutoWrap is true (so the walker can shape per-line content); for
// AutoWrap=false only RenderingTextLength is consulted.
RenderingTextRange func(start, end int) string
// RenderingTextLength is the total byte length of the rendering
// text.
RenderingTextLength int
// ViewportSize describes the rendering box the walker operates
// against: X is the wrap width passed through to
// [VisualLineCountForLogicalLine] when AutoWrap is true, and Y is
// the distance below FirstLogicalLineInViewport's top that the
// visible region extends downward. The walk stops once cumulative
// line heights exceed Y, leaving one line of slack so the
// caller's inner Y clip can handle off-by-one rounding.
ViewportSize image.Point
// Face, LineHeight, TabWidth, KeepTailingSpace are passed through
// to [VisualLineCountForLogicalLine] when AutoWrap is true.
Face text.Face
LineHeight float64
TabWidth float64
KeepTailingSpace bool
// AutoWrap toggles between a per-line shaping walk (true) and a
// flat LineHeight*idx arithmetic (false).
AutoWrap bool
// Composition is the splice info from [ComputeCompositionInfo].
// The zero value means "no active composition".
Composition CompositionInfo
}
VisibleRangeInViewportParams describes the inputs for VisibleRangeInViewport. The walk steps forward from FirstLogicalLineInViewport measuring per-line heights via VisualLineCountForLogicalLine until cumulative height covers Height, so the cost is O(visible logical lines) — the prefix [0, FirstLogicalLineInViewport) is never measured.