// TextMetrics returns the vertical metrics based on the current text style. // Measured values are returned in local coordinate space. func (c *Context) TextMetrics() (float32, float32, float32) { state := c.getState() scale := state.getFontScale() * c.devicePxRatio invScale := 1.0 / scale if state.fontID == fontstashmini.INVALID { return 0, 0, 0 } c.fs.SetSize(state.fontSize * scale) c.fs.SetSpacing(state.letterSpacing * scale) c.fs.SetBlur(state.fontBlur * scale) c.fs.SetAlign(fontstashmini.FONSAlign(state.textAlign)) c.fs.SetFont(state.fontID) ascender, descender, lineH := c.fs.VerticalMetrics() return ascender * invScale, descender * invScale, lineH * invScale }
// TextGlyphPositionsRune is an alternate version of TextGlyphPositions that accepts rune slice func (c *Context) TextGlyphPositionsRune(x, y float32, runes []rune) []GlyphPosition { state := c.getState() scale := state.getFontScale() * c.devicePxRatio invScale := 1.0 / scale if state.fontID == fontstashmini.INVALID { return nil } c.fs.SetSize(state.fontSize * scale) c.fs.SetSpacing(state.letterSpacing * scale) c.fs.SetBlur(state.fontBlur * scale) c.fs.SetAlign(fontstashmini.FONSAlign(state.textAlign)) c.fs.SetFont(state.fontID) positions := make([]GlyphPosition, 0, len(runes)) iter := c.fs.TextIterForRunes(x*scale, y*scale, runes) prevIter := iter for { quad, ok := iter.Next() if !ok { break } if iter.PrevGlyph.Index == -1 && !c.allocTextAtlas() { iter = prevIter quad, _ = iter.Next() // try again } prevIter = iter positions = append(positions, GlyphPosition{ Index: iter.CurrentIndex, Runes: runes, X: iter.X * invScale, MinX: minF(iter.X, quad.X0) * invScale, MaxX: minF(iter.NextX, quad.X1) * invScale, }) } return positions }
// TextBounds measures the specified text string. Parameter bounds should be a pointer to float[4], // if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] // Returns the horizontal advance of the measured text (i.e. where the next character should drawn). // Measured values are returned in local coordinate space. func (c *Context) TextBounds(x, y float32, str string) (float32, []float32) { state := c.getState() scale := state.getFontScale() * c.devicePxRatio invScale := 1.0 / scale if state.fontID == fontstashmini.INVALID { return 0, nil } c.fs.SetSize(state.fontSize * scale) c.fs.SetSpacing(state.letterSpacing * scale) c.fs.SetBlur(state.fontBlur * scale) c.fs.SetAlign(fontstashmini.FONSAlign(state.textAlign)) c.fs.SetFont(state.fontID) width, bounds := c.fs.TextBounds(x*scale, y*scale, str) if bounds != nil { bounds[1], bounds[3] = c.fs.LineBounds(y * scale) bounds[0] *= invScale bounds[1] *= invScale bounds[2] *= invScale bounds[3] *= invScale } return width * invScale, bounds }
// TextRune is an alternate version of Text that accepts rune slice. func (c *Context) TextRune(x, y float32, runes []rune) float32 { state := c.getState() scale := state.getFontScale() * c.devicePxRatio invScale := 1.0 / scale if state.fontID == fontstashmini.INVALID { return 0 } c.fs.SetSize(state.fontSize * scale) c.fs.SetSpacing(state.letterSpacing * scale) c.fs.SetBlur(state.fontBlur * scale) c.fs.SetAlign(fontstashmini.FONSAlign(state.textAlign)) c.fs.SetFont(state.fontID) vertexCount := maxI(2, len(runes)) * 4 // conservative estimate. vertexes := c.cache.allocVertexes(vertexCount) iter := c.fs.TextIterForRunes(x*scale, y*scale, runes) prevIter := iter index := 0 for { quad, ok := iter.Next() if !ok { break } if iter.PrevGlyph == nil || iter.PrevGlyph.Index == -1 { if !c.allocTextAtlas() { break // no memory :( } if index != 0 { c.renderText(vertexes[:index]) index = 0 } iter = prevIter quad, _ = iter.Next() // try again if iter.PrevGlyph == nil || iter.PrevGlyph.Index == -1 { // still can not find glyph? break } } prevIter = iter // Transform corners. c0, c1 := state.xform.TransformPoint(quad.X0*invScale, quad.Y0*invScale) c2, c3 := state.xform.TransformPoint(quad.X1*invScale, quad.Y0*invScale) c4, c5 := state.xform.TransformPoint(quad.X1*invScale, quad.Y1*invScale) c6, c7 := state.xform.TransformPoint(quad.X0*invScale, quad.Y1*invScale) //log.Printf("quad(%c) x0=%d, x1=%d, y0=%d, y1=%d, s0=%d, s1=%d, t0=%d, t1=%d\n", iter.CodePoint, int(quad.X0), int(quad.X1), int(quad.Y0), int(quad.Y1), int(1024*quad.S0), int(quad.S1*1024), int(quad.T0*1024), int(quad.T1*1024)) // Create triangles if index+4 <= vertexCount { (&vertexes[index]).set(c2, c3, quad.S1, quad.T0) (&vertexes[index+1]).set(c0, c1, quad.S0, quad.T0) (&vertexes[index+2]).set(c4, c5, quad.S1, quad.T1) (&vertexes[index+3]).set(c6, c7, quad.S0, quad.T1) index += 4 } } c.flushTextTexture() c.renderText(vertexes[:index]) return iter.X }
// TextBreakLinesRune is an alternate version of TextBreakLines that accepts rune slice func (c *Context) TextBreakLinesRune(runes []rune, breakRowWidth float32) []TextRow { state := c.getState() scale := state.getFontScale() * c.devicePxRatio invScale := 1.0 / scale if state.fontID == fontstashmini.INVALID { return nil } currentType := nvgSPACE prevType := nvgCHAR c.fs.SetSize(state.fontSize * scale) c.fs.SetSpacing(state.letterSpacing * scale) c.fs.SetBlur(state.fontBlur * scale) c.fs.SetAlign(fontstashmini.FONSAlign(state.textAlign)) c.fs.SetFont(state.fontID) breakRowWidth *= scale iter := c.fs.TextIterForRunes(0, 0, runes) prevIter := iter var prevCodePoint rune var rows []TextRow var rowStartX, rowWidth, rowMinX, rowMaxX, wordStartX, wordMinX, breakWidth, breakMaxX float32 rowStart := -1 rowEnd := -1 wordStart := -1 breakEnd := -1 for { quad, ok := iter.Next() if !ok { break } if iter.PrevGlyph == nil || iter.PrevGlyph.Index == -1 && !c.allocTextAtlas() { iter = prevIter quad, _ = iter.Next() // try again } prevIter = iter switch iter.CodePoint { case 9: // \t currentType = nvgSPACE case 11: // \v currentType = nvgSPACE case 12: // \f currentType = nvgSPACE case 0x00a0: // NBSP currentType = nvgSPACE case 10: // \n if prevCodePoint == 13 { currentType = nvgNEWLINE } else { currentType = nvgSPACE } case 13: // \r if prevCodePoint == 13 { currentType = nvgNEWLINE } else { currentType = nvgSPACE } case 0x0085: // NEL currentType = nvgNEWLINE default: currentType = nvgCHAR } if currentType == nvgNEWLINE { // Always handle new lines. tmpRowStart := rowStart if rowStart == -1 { tmpRowStart = iter.CurrentIndex } if rowEnd == -1 { rowEnd = iter.CurrentIndex } rows = append(rows, TextRow{ Runes: runes, StartIndex: tmpRowStart, EndIndex: rowEnd, Width: rowWidth * invScale, MinX: rowMinX * invScale, MaxX: rowMaxX * invScale, NextIndex: iter.NextIndex, }) // Set null break point breakEnd = rowStart breakWidth = 0.0 breakMaxX = 0.0 rowStart = -1 rowEnd = -1 rowMinX = 0 rowMaxX = 0 // Indicate to skip the white space at the beginning of the row. } else { if rowStart == -1 { if currentType == nvgCHAR { // The current char is the row so far rowStartX = iter.X rowStart = iter.CurrentIndex rowEnd = iter.NextIndex rowWidth = iter.NextX - rowStartX // q.x1 - rowStartX; rowMinX = quad.X0 - rowStartX rowMaxX = quad.X1 - rowStartX wordStart = iter.CurrentIndex wordStartX = iter.X wordMinX = quad.X0 - rowStartX // Set null break point breakEnd = rowStart breakWidth = 0.0 breakMaxX = 0.0 } } else { nextWidth := iter.NextX - rowStartX // track last non-white space character if currentType == nvgCHAR { rowEnd = iter.NextIndex rowWidth = iter.NextX - rowStartX rowMaxX = quad.X1 - rowStartX } // track last end of a word if prevType == nvgCHAR && currentType == nvgSPACE { breakEnd = iter.CurrentIndex breakWidth = rowWidth breakMaxX = rowMaxX } // track last beginning of a word if prevType == nvgSPACE && currentType == nvgCHAR { wordStart = iter.CurrentIndex wordStartX = iter.X wordMinX = quad.X0 - rowStartX } // Break to new line when a character is beyond break width. if currentType == nvgCHAR && nextWidth > breakRowWidth { // The run length is too long, need to break to new line. if breakEnd == rowStart { // The current word is longer than the row length, just break it from here. rows = append(rows, TextRow{ Runes: runes, StartIndex: rowStart, EndIndex: iter.CurrentIndex, Width: rowWidth * invScale, MinX: rowMinX * invScale, MaxX: rowMaxX * invScale, NextIndex: iter.CurrentIndex, }) rowStartX = iter.X rowStart = iter.CurrentIndex rowEnd = iter.NextIndex rowWidth = iter.NextX - rowStartX rowMinX = quad.X0 - rowStartX rowMaxX = quad.X1 - rowStartX wordStart = iter.CurrentIndex wordStartX = iter.X wordMinX = quad.X0 - rowStartX } else { // Break the line from the end of the last word, and start new line from the beginning of the new. rows = append(rows, TextRow{ Runes: runes, StartIndex: rowStart, EndIndex: breakEnd, Width: breakWidth * invScale, MinX: rowMinX * invScale, MaxX: breakMaxX * invScale, NextIndex: wordStart, }) rowStartX = wordStartX rowStart = wordStart rowEnd = iter.NextIndex rowWidth = iter.NextX - rowStartX rowMinX = wordMinX rowMaxX = quad.X1 - rowStartX // No change to the word start } // Set null break point breakEnd = rowStart breakWidth = 0.0 breakMaxX = 0.0 } } } prevCodePoint = iter.CodePoint prevType = currentType } if rowStart != -1 { rows = append(rows, TextRow{ Runes: runes, StartIndex: rowStart, EndIndex: rowEnd, Width: rowWidth * invScale, MinX: rowMinX * invScale, MaxX: rowMaxX * invScale, NextIndex: len(runes), }) } return rows }