Package limn.graphics

Class ShapedText.Builder

java.lang.Object
limn.graphics.ShapedText.Builder
Enclosing class:
ShapedText

public static final class ShapedText.Builder extends Object
Assembles a ShapedText run by run. Backend-facing: a widget never touches one. Not thread safe, single use, and meant to be filled and dropped inside one TextRuler.shape(String, Font, Direction) call.

Runs go in logically and come out visually. A caller supplies the runs in the order the characters appear in the string, each with its bidi embedding level, and the builder does the reordering (Unicode Bidirectional Algorithm rule L2), lays out each run's glyphs from the advances it was given, and derives the caret stops, the split positions and the cumulative advances. That places the highest-risk arithmetic in a module with no natives in it, where it can be unit-tested with no window, no GPU and no font file — and it means the shaping path and the degraded path come through one door, so the two cannot disagree about where a caret goes.

Cluster offsets are absolute. Every index this builder is given is an offset into the whole string, never into the buffer a shaper was handed for one run. Shapers report clusters relative to what they were given and every run boundary shifts that origin; making the seam demand absolute offsets and check them turns that entire class of mistake from a caret that lands one character from the click into an exception at the call that made it.

  • Method Summary

    Modifier and Type
    Method
    Description
    Freezes the value: reorders the runs by level, resolves the caret stops and their strong and weak positions, sums the advances into the width and the cumulative table, and derives ShapedText.isSimple().
    epoch(long epoch)
    Stamps the epoch the shaping was done under.
    glyph(int glyphId, int cluster, float advance, float xOffset, float yOffset)
    Adds one glyph to the open run, in the order the shaper emitted it — which for a right-to-left run is already that run's visual order.
    lineMetrics(float ascent, float descent, float lineHeight)
    The line's vertical extents, in logical points, as TextMetrics defines them.
    run(int faceId, int charStart, int charEnd, int level)
    Opens a run and implicitly closes the previous one: one face, one direction, one shaping call, covering the characters [charStart, charEnd).

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • lineMetrics

      public ShapedText.Builder lineMetrics(float ascent, float descent, float lineHeight)
      The line's vertical extents, in logical points, as TextMetrics defines them. Required before build(). Width is not among them: it is the extent of the glyphs, so it is derived rather than stated.
      Parameters:
      ascent - baseline to ascender, positive up
      descent - baseline to descender, positive down
      lineHeight - recommended baseline-to-baseline distance
    • epoch

      public ShapedText.Builder epoch(long epoch)
      Stamps the epoch the shaping was done under. A ruler that does not set one produces a value ShapedText.matches(java.lang.String, limn.graphics.Font, limn.graphics.ShapedText.Direction, limn.graphics.TextRuler) treats as always current, which is right for a fake and wrong for anything that resolves a face.
      Parameters:
      epoch - the producing ruler's epoch at the moment of shaping
    • run

      public ShapedText.Builder run(int faceId, int charStart, int charEnd, int level)
      Opens a run and implicitly closes the previous one: one face, one direction, one shaping call, covering the characters [charStart, charEnd).

      Runs are supplied in logical order and must tile the string exactly — no gap, no overlap, the first starting at zero and the last ending at the string's length. A run with no glyphs at all is legal and is what a run of control characters is.

      level is the bidi embedding level, with even levels reading left to right and odd levels right to left, as the Unicode Bidirectional Algorithm numbers them and as java.text.Bidi reports them. It is a level and not a boolean because rule L2 reorders by level and cannot be driven by parity alone once anything nests.

      Parameters:
      faceId - the producing ruler's own identifier for the face this run's glyph ids belong to; opaque here, and only ever compared for equality
      charStart - first char offset this run covers
      charEnd - one past the last, greater than charStart
      level - the bidi embedding level, 0 to 125
      Throws:
      IllegalArgumentException - if the range is empty, reversed, out of bounds, does not begin where the previous run ended, or the level is out of range
    • glyph

      public ShapedText.Builder glyph(int glyphId, int cluster, float advance, float xOffset, float yOffset)
      Adds one glyph to the open run, in the order the shaper emitted it — which for a right-to-left run is already that run's visual order.

      Position is not an argument. The builder places the run and runs the pen, so a caller cannot put a run at the wrong x, and metrics().width() cannot disagree with where the last glyph was drawn.

      Parameters:
      glyphId - the glyph index within the run's face, or ShapedText.NO_GLYPH; not a code point, and for a ligature not derivable from one
      cluster - the char offset into the whole string this glyph belongs to; a shaper reports these relative to the buffer it was handed, so the run's start has to be added back exactly once
      advance - how far the pen moves after this glyph, in logical points; zero for an attached mark
      xOffset - horizontal placement relative to the pen, in logical points
      yOffset - vertical placement relative to the baseline, in logical points, positive down as everywhere else on a Canvas
      Throws:
      IllegalStateException - if no run is open
      IllegalArgumentException - if cluster falls outside the open run, which is the run-origin mistake caught at the call that made it
    • build

      public ShapedText build()
      Freezes the value: reorders the runs by level, resolves the caret stops and their strong and weak positions, sums the advances into the width and the cumulative table, and derives ShapedText.isSimple().
      Throws:
      IllegalStateException - if no vertical metrics were supplied, or if the runs do not tile the text exactly