33package shaping
44
55import (
6+ "math"
7+
68 "github.com/go-text/typesetting/di"
79 "github.com/go-text/typesetting/font"
810 "golang.org/x/image/math/fixed"
@@ -155,30 +157,30 @@ type Output struct {
155157
156158// ToFontUnit converts a metrics (typically found in [Glyph] fields)
157159// to unscaled font units.
158- func (o * Output ) ToFontUnit (v fixed.Int26_6 ) float32 {
159- return float32 (v ) / float32 (o .Size ) * float32 (o .Face .Upem ())
160+ func (out * Output ) ToFontUnit (v fixed.Int26_6 ) float32 {
161+ return float32 (v ) / float32 (out .Size ) * float32 (out .Face .Upem ())
160162}
161163
162164// FromFontUnit converts an unscaled font value to the current [Size]
163- func (o * Output ) FromFontUnit (v float32 ) fixed.Int26_6 {
164- return fixed .Int26_6 (v * float32 (o .Size ) / float32 (o .Face .Upem ()))
165+ func (out * Output ) FromFontUnit (v float32 ) fixed.Int26_6 {
166+ return fixed .Int26_6 (v * float32 (out .Size ) / float32 (out .Face .Upem ()))
165167}
166168
167169// RecomputeAdvance updates only the Advance field based on the current
168170// contents of the Glyphs field. It is faster than RecalculateAll(),
169171// and can be used to speed up line wrapping logic.
170- func (o * Output ) RecomputeAdvance () {
172+ func (out * Output ) RecomputeAdvance () {
171173 advance := fixed .Int26_6 (0 )
172- if o .Direction .IsVertical () {
173- for _ , g := range o .Glyphs {
174+ if out .Direction .IsVertical () {
175+ for _ , g := range out .Glyphs {
174176 advance += g .YAdvance
175177 }
176178 } else { // horizontal
177- for _ , g := range o .Glyphs {
179+ for _ , g := range out .Glyphs {
178180 advance += g .XAdvance
179181 }
180182 }
181- o .Advance = advance
183+ out .Advance = advance
182184}
183185
184186// advanceSpaceAware adjust the value in [Advance]
@@ -189,45 +191,45 @@ func (o *Output) RecomputeAdvance() {
189191// because the trailing space in this run will always be internal to the paragraph.
190192//
191193// TODO: should we take into account multiple spaces ?
192- func (o * Output ) advanceSpaceAware (paragraphDir di.Direction ) fixed.Int26_6 {
193- L := len (o .Glyphs )
194- if L == 0 || paragraphDir != o .Direction {
195- return o .Advance
194+ func (out * Output ) advanceSpaceAware (paragraphDir di.Direction ) fixed.Int26_6 {
195+ L := len (out .Glyphs )
196+ if L == 0 || paragraphDir != out .Direction {
197+ return out .Advance
196198 }
197199
198200 // adjust the last to account for spaces
199201 var lastG Glyph
200- if o .Direction .Progression () == di .FromTopLeft {
201- lastG = o .Glyphs [L - 1 ]
202+ if out .Direction .Progression () == di .FromTopLeft {
203+ lastG = out .Glyphs [L - 1 ]
202204 } else {
203- lastG = o .Glyphs [0 ]
205+ lastG = out .Glyphs [0 ]
204206 }
205- if o .Direction .IsVertical () {
207+ if out .Direction .IsVertical () {
206208 if lastG .Height == 0 {
207- return o .Advance - lastG .YAdvance
209+ return out .Advance - lastG .YAdvance
208210 }
209211 } else { // horizontal
210212 if lastG .Width == 0 {
211- return o .Advance - lastG .XAdvance
213+ return out .Advance - lastG .XAdvance
212214 }
213215 }
214- return o .Advance - lastG .endLetterSpacing
216+ return out .Advance - lastG .endLetterSpacing
215217}
216218
217219// RecalculateAll updates the all other fields of the Output
218220// to match the current contents of the Glyphs field.
219221// This method will fail with UnimplementedDirectionError if the Output
220222// direction is unimplemented.
221- func (o * Output ) RecalculateAll () {
223+ func (out * Output ) RecalculateAll () {
222224 var (
223225 advance fixed.Int26_6
224226 ascent fixed.Int26_6
225227 descent fixed.Int26_6
226228 )
227229
228- if o .Direction .IsVertical () {
229- for i := range o .Glyphs {
230- g := & o .Glyphs [i ]
230+ if out .Direction .IsVertical () {
231+ for i := range out .Glyphs {
232+ g := & out .Glyphs [i ]
231233 advance += g .YAdvance
232234 depth := g .XOffset + g .XBearing // start of the glyph
233235 if depth < descent {
@@ -239,8 +241,8 @@ func (o *Output) RecalculateAll() {
239241 }
240242 }
241243 } else { // horizontal
242- for i := range o .Glyphs {
243- g := & o .Glyphs [i ]
244+ for i := range out .Glyphs {
245+ g := & out .Glyphs [i ]
244246 advance += g .XAdvance
245247 height := g .YBearing + g .YOffset
246248 if height > ascent {
@@ -252,8 +254,8 @@ func (o *Output) RecalculateAll() {
252254 }
253255 }
254256 }
255- o .Advance = advance
256- o .GlyphBounds = Bounds {
257+ out .Advance = advance
258+ out .GlyphBounds = Bounds {
257259 Ascent : ascent ,
258260 Descent : descent ,
259261 }
@@ -303,6 +305,40 @@ func (out *Output) moveCrossAxis(d fixed.Int26_6) {
303305 out .GlyphBounds .Descent += d
304306}
305307
308+ func (out * Output ) applyTabs (text []rune , columnWidth , runStart fixed.Int26_6 ) {
309+ isVertical := out .Direction .IsVertical ()
310+ columnWidthF := float64 (columnWidth ) / 64
311+ var advance fixed.Int26_6
312+ for i , g := range out .Glyphs {
313+ gAdvance := g .XAdvance
314+ if isVertical {
315+ gAdvance = g .YAdvance
316+ }
317+ isTab := g .RuneCount == 1 && g .GlyphCount == 1 && text [g .ClusterIndex ] == '\t'
318+ if ! isTab {
319+ advance += gAdvance
320+ continue
321+ }
322+ // update the advance of the glyph so that the next glyph is "tab-aligned" :
323+ // we want the "end" of the tab to be a multiple of columnWidth, that is :
324+ // (runStart + advance + updatedTabAdvance) % columnWith == 0
325+ glyphStartF := float64 (runStart + advance ) / 64
326+ remainder := math .Mod (glyphStartF , columnWidthF )
327+ updatedTabAdvance := fixed .Int26_6 ((columnWidthF - remainder ) * 64 )
328+
329+ if isVertical {
330+ out .Glyphs [i ].YAdvance = updatedTabAdvance
331+ } else {
332+ out .Glyphs [i ].XAdvance = updatedTabAdvance
333+ }
334+
335+ advance += updatedTabAdvance
336+ }
337+
338+ // no need to call RecomputeAdvance
339+ out .Advance = advance
340+ }
341+
306342// AdjustBaselines aligns runs with different baselines.
307343//
308344// For vertical text, it centralizes 'sideways' runs, so
@@ -349,3 +385,13 @@ func (l Line) AdjustBaselines() {
349385 l [i ].moveCrossAxis (- middle )
350386 }
351387}
388+
389+ // AlignTabs updates the advance of glyphs mapped to '\t' runes,
390+ // so that tabs are aligned on columns defined by [columnWidth].
391+ func (l Line ) AlignTabs (text []rune , columnWidth fixed.Int26_6 ) {
392+ var runsAdvance fixed.Int26_6 // the position of the start of the current run
393+ for i := range l {
394+ l [i ].applyTabs (text , columnWidth , runsAdvance )
395+ runsAdvance += l [i ].Advance
396+ }
397+ }
0 commit comments