예제 #1
0
// 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
}
예제 #2
0
// 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
}
예제 #3
0
// 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
}
예제 #4
0
// 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
}
예제 #5
0
// 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
}