forked from mbk-lab/rui_orig
2
0
Fork 0

The Canvas.TextWidth method replaced by Canvas.TextMetrics

This commit is contained in:
anoshenko 2022-10-30 12:35:22 +03:00
parent 8216ce192a
commit 76413c931a
6 changed files with 186 additions and 156 deletions

View File

@ -1,3 +1,7 @@
# v.10.0
* The Canvas.TextWidth method replaced by Canvas.TextMetrics
# v0.9.0 # v0.9.0
* Requires go 1.18 or higher * Requires go 1.18 or higher

View File

@ -1702,3 +1702,44 @@ function imageError(element, event) {
var message = "imageViewError{session=" + sessionID + ",id=" + element.id + "}"; var message = "imageViewError{session=" + sessionID + ",id=" + element.id + "}";
sendMessage(message); sendMessage(message);
} }
function canvasTextMetrics(answerID, elementId, font, text) {
var w = 0;
var ascent = 0;
var descent = 0;
var left = 0;
var right = 0;
const canvas = document.getElementById(elementId);
if (canvas) {
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.save()
const dpr = window.devicePixelRatio || 1;
ctx.scale(dpr, dpr);
ctx.font = font;
ctx.textBaseline = 'alphabetic';
ctx.textAlign = 'start';
var metrics = ctx.measureText(text)
w = metrics.width;
ascent = metrics.actualBoundingBoxAscent;
descent = metrics.actualBoundingBoxDescent;
left = metrics.actualBoundingBoxLeft;
right = metrics.actualBoundingBoxRight;
ctx.restore();
}
}
sendMessage('answer{answerID=' + answerID + ', width=' + w + ', ascent=' + ascent +
', descent=' + descent + ', left=' + left + ', right=' + right + '}');
}
function getPropertyValue(answerID, elementId, name) {
const element = document.getElementById(elementId);
if (element && element[name]) {
sendMessage('answer{answerID=' + answerID + ', value="' + element[name] + '"}')
return
}
sendMessage('answer{answerID=' + answerID + ', value=""}')
}

134
canvas.go
View File

@ -76,6 +76,21 @@ type FontParams struct {
LineHeight SizeUnit LineHeight SizeUnit
} }
// TextMetrics is the result of the Canvas.TextMetrics function
type TextMetrics struct {
// Width is the calculated width of a segment of inline text in pixels
Width float64
// Ascent is the distance from the horizontal baseline to the top of the bounding rectangle used to render the text, in pixels.
Ascent float64
// Descent is the distance from the horizontal baseline to the bottom of the bounding rectangle used to render the text, in pixels.
Descent float64
// Left is the distance to the left side of the bounding rectangle of the given text, in pixels;
// positive numbers indicating a distance going left from the given alignment point.
Left float64
// Right is the distance to the right side of the bounding rectangle of the given text, CSS pixels.
Right float64
}
// Canvas is a drawing interface // Canvas is a drawing interface
type Canvas interface { type Canvas interface {
// View return the view for the drawing // View return the view for the drawing
@ -195,8 +210,8 @@ type Canvas interface {
// SetFontWithParams sets the current text style to use when drawing text // SetFontWithParams sets the current text style to use when drawing text
SetFontWithParams(name string, size SizeUnit, params FontParams) SetFontWithParams(name string, size SizeUnit, params FontParams)
// TextWidth calculates the width of the text drawn by a given font // TextWidth calculates metrics of the text drawn by a given font
TextWidth(text string, fontName string, fontSize SizeUnit) float64 TextMetrics(text string, fontName string, fontSize SizeUnit, fontParams FontParams) TextMetrics
// SetTextBaseline sets the current text baseline used when drawing text. Valid values: // SetTextBaseline sets the current text baseline used when drawing text. Valid values:
// AlphabeticBaseline (0), TopBaseline (1), MiddleBaseline (2), BottomBaseline (3), // AlphabeticBaseline (0), TopBaseline (1), MiddleBaseline (2), BottomBaseline (3),
@ -596,6 +611,67 @@ func (canvas *canvasData) SetFont(name string, size SizeUnit) {
canvas.writeFont(name, &canvas.script) canvas.writeFont(name, &canvas.script)
} }
func (canvas *canvasData) fontWithParams(name string, size SizeUnit, params FontParams) string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
if params.Italic {
buffer.WriteString("italic ")
}
if params.SmallCaps {
buffer.WriteString("small-caps ")
}
if params.Weight > 0 && params.Weight <= 9 {
switch params.Weight {
case 4:
buffer.WriteString("normal ")
case 7:
buffer.WriteString("bold ")
default:
buffer.WriteString(strconv.Itoa(params.Weight * 100))
buffer.WriteRune(' ')
}
}
buffer.WriteString(size.cssString("1rem", canvas.View().Session()))
switch params.LineHeight.Type {
case Auto:
case SizeInPercent:
if params.LineHeight.Value != 100 {
buffer.WriteString("/")
buffer.WriteString(strconv.FormatFloat(params.LineHeight.Value/100, 'g', -1, 64))
}
case SizeInFraction:
if params.LineHeight.Value != 1 {
buffer.WriteString("/")
buffer.WriteString(strconv.FormatFloat(params.LineHeight.Value, 'g', -1, 64))
}
default:
buffer.WriteString("/")
buffer.WriteString(params.LineHeight.cssString("", canvas.View().Session()))
}
names := strings.Split(name, ",")
lead := " "
for _, font := range names {
font = strings.Trim(font, " \n\"'")
buffer.WriteString(lead)
lead = ","
if strings.Contains(font, " ") {
buffer.WriteRune('"')
buffer.WriteString(font)
buffer.WriteRune('"')
} else {
buffer.WriteString(font)
}
}
return buffer.String()
}
func (canvas *canvasData) setFontWithParams(name string, size SizeUnit, params FontParams, script *strings.Builder) { func (canvas *canvasData) setFontWithParams(name string, size SizeUnit, params FontParams, script *strings.Builder) {
script.WriteString("\nctx.font = '") script.WriteString("\nctx.font = '")
if params.Italic { if params.Italic {
@ -644,57 +720,9 @@ func (canvas *canvasData) SetFontWithParams(name string, size SizeUnit, params F
canvas.setFontWithParams(name, size, params, &canvas.script) canvas.setFontWithParams(name, size, params, &canvas.script)
} }
func (canvas *canvasData) TextWidth(text string, fontName string, fontSize SizeUnit) float64 { func (canvas *canvasData) TextMetrics(text string, fontName string, fontSize SizeUnit, fontParams FontParams) TextMetrics {
buffer := allocStringBuilder() view := canvas.View()
defer freeStringBuilder(buffer) return view.Session().canvasTextMetrics(view.htmlID(), canvas.fontWithParams(fontName, fontSize, fontParams), text)
canvas.setFontWithParams(fontName, fontSize, FontParams{}, buffer)
fontParams := buffer.String()
buffer.Reset()
canvas.writeStringArgs(text, buffer)
str := buffer.String()
script := fmt.Sprintf(`
var w = 0;
const canvas = document.getElementById('%s');
if (canvas) {
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.save()
const dpr = window.devicePixelRatio || 1;
ctx.scale(dpr, dpr);
%s;
w = ctx.measureText('%s').width;
ctx.restore();
}
}
sendMessage('answer{width=' + w + ', answerID=' + answerID + '}');
`, canvas.View().htmlID(), fontParams, str)
result := canvas.View().Session().runGetterScript(script)
switch result.Tag() {
case "answer":
if value, ok := result.PropertyValue("width"); ok {
w, err := strconv.ParseFloat(value, 32)
if err == nil {
return w
}
ErrorLog(err.Error())
}
case "error":
if text, ok := result.PropertyValue("errorText"); ok {
ErrorLog(text)
} else {
ErrorLog("error")
}
default:
ErrorLog("Unknown answer: " + result.Tag())
}
return 0
} }
func (canvas *canvasData) SetTextBaseline(baseline int) { func (canvas *canvasData) SetTextBaseline(baseline int) {

View File

@ -667,48 +667,15 @@ func (player *mediaPlayerData) SetCurrentTime(seconds float64) {
} }
func (player *mediaPlayerData) getFloatPlayerProperty(tag string) (float64, bool) { func (player *mediaPlayerData) getFloatPlayerProperty(tag string) (float64, bool) {
value := player.Session().htmlPropertyValue(player.htmlID(), tag)
script := allocStringBuilder() if value != "" {
defer freeStringBuilder(script) result, err := strconv.ParseFloat(value, 32)
script.WriteString(`const element = document.getElementById('`)
script.WriteString(player.htmlID())
script.WriteString(`');
if (element && element.`)
script.WriteString(tag)
script.WriteString(`) {
sendMessage('answer{answerID=' + answerID + ',`)
script.WriteString(tag)
script.WriteString(`=' + element.`)
script.WriteString(tag)
script.WriteString(` + '}');
} else {
sendMessage('answer{answerID=' + answerID + ',`)
script.WriteString(tag)
script.WriteString(`=0}');
}`)
result := player.Session().runGetterScript(script.String())
switch result.Tag() {
case "answer":
if value, ok := result.PropertyValue(tag); ok {
w, err := strconv.ParseFloat(value, 32)
if err == nil { if err == nil {
return w, true return result, true
} }
ErrorLog(err.Error()) ErrorLog(err.Error())
} }
case "error":
if text, ok := result.PropertyValue("errorText"); ok {
ErrorLog(text)
} else {
ErrorLog("error")
}
default:
ErrorLog("Unknown answer: " + result.Tag())
}
return 0, false return 0, false
} }
@ -751,45 +718,14 @@ func (player *mediaPlayerData) Volume() float64 {
} }
func (player *mediaPlayerData) getBoolPlayerProperty(tag string) (bool, bool) { func (player *mediaPlayerData) getBoolPlayerProperty(tag string) (bool, bool) {
switch value := player.Session().htmlPropertyValue(player.htmlID(), tag); strings.ToLower(value) {
case "0", "false", "off":
return false, true
script := allocStringBuilder() case "1", "true", "on":
defer freeStringBuilder(script)
script.WriteString(`const element = document.getElementById('`)
script.WriteString(player.htmlID())
script.WriteString(`');
if (element && element.`)
script.WriteString(tag)
script.WriteString(`) {
sendMessage('answer{answerID=' + answerID + ',`)
script.WriteString(tag)
script.WriteString(`=1}')
} else {
sendMessage('answer{answerID=' + answerID + ',`)
script.WriteString(tag)
script.WriteString(`=0}')
}`)
result := player.Session().runGetterScript(script.String())
switch result.Tag() {
case "answer":
if value, ok := result.PropertyValue(tag); ok {
if value == "1" {
return true, true
}
return false, true return false, true
} }
case "error":
if text, ok := result.PropertyValue("errorText"); ok {
ErrorLog(text)
} else {
ErrorLog("error")
}
default:
ErrorLog("Unknown answer: " + result.Tag())
}
return false, false return false, false
} }

View File

@ -11,7 +11,8 @@ type webBrige interface {
runFunc(funcName string, args ...any) bool runFunc(funcName string, args ...any) bool
readMessage() (string, bool) readMessage() (string, bool)
writeMessage(text string) bool writeMessage(text string) bool
runGetterScript(script string) DataObject canvasTextMetrics(htmlID, font, text string) TextMetrics
htmlPropertyValue(htmlID, name string) string
answerReceived(answer DataObject) answerReceived(answer DataObject)
close() close()
remoteAddr() string remoteAddr() string
@ -99,7 +100,8 @@ type Session interface {
writeInitScript(writer *strings.Builder) writeInitScript(writer *strings.Builder)
runFunc(funcName string, args ...any) runFunc(funcName string, args ...any)
runScript(script string) runScript(script string)
runGetterScript(script string) DataObject //, answer chan DataObject) canvasTextMetrics(htmlID, font, text string) TextMetrics
htmlPropertyValue(htmlID, name string) string
handleAnswer(data DataObject) handleAnswer(data DataObject)
handleRootSize(data DataObject) handleRootSize(data DataObject)
handleResize(data DataObject) handleResize(data DataObject)
@ -339,15 +341,22 @@ func (session *sessionData) runScript(script string) {
} }
} }
func (session *sessionData) runGetterScript(script string) DataObject { //}, answer chan DataObject) { func (session *sessionData) canvasTextMetrics(htmlID, font, text string) TextMetrics {
if session.brige != nil { if session.brige != nil {
return session.brige.runGetterScript(script) return session.brige.canvasTextMetrics(htmlID, font, text)
} }
ErrorLog("No connection") ErrorLog("No connection")
result := NewDataObject("error") return TextMetrics{Width: 0}
result.SetPropertyValue("text", "No connection") }
return result
func (session *sessionData) htmlPropertyValue(htmlID, name string) string {
if session.brige != nil {
return session.brige.htmlPropertyValue(htmlID, name)
}
ErrorLog("No connection")
return ""
} }
func (session *sessionData) handleAnswer(data DataObject) { func (session *sessionData) handleAnswer(data DataObject) {

View File

@ -151,6 +151,10 @@ func (brige *wsBrige) writeMessage(script string) bool {
DebugLog("Run script:") DebugLog("Run script:")
DebugLog(script) DebugLog(script)
} }
if brige.conn == nil {
ErrorLog("No connection")
return false
}
if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil { if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
ErrorLog(err.Error()) ErrorLog(err.Error())
return false return false
@ -158,7 +162,9 @@ func (brige *wsBrige) writeMessage(script string) bool {
return true return true
} }
func (brige *wsBrige) runGetterScript(script string) DataObject { func (brige *wsBrige) canvasTextMetrics(htmlID, font, text string) TextMetrics {
result := TextMetrics{}
brige.answerMutex.Lock() brige.answerMutex.Lock()
answerID := brige.answerID answerID := brige.answerID
brige.answerID++ brige.answerID++
@ -166,30 +172,36 @@ func (brige *wsBrige) runGetterScript(script string) DataObject {
answer := make(chan DataObject) answer := make(chan DataObject)
brige.answer[answerID] = answer brige.answer[answerID] = answer
errorText := ""
if brige.conn != nil { if brige.runFunc("canvasTextMetrics", answerID, htmlID, font, text) {
script = "var answerID = " + strconv.Itoa(answerID) + ";\n" + script data := <-answer
if ProtocolInDebugLog { result.Width = dataFloatProperty(data, "width")
DebugLog("\n" + script)
}
err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script))
if err == nil {
return <-answer
}
errorText = err.Error()
} else {
if ProtocolInDebugLog {
DebugLog("\n" + script)
}
errorText = "No connection"
} }
result := NewDataObject("error")
result.SetPropertyValue("text", errorText)
delete(brige.answer, answerID) delete(brige.answer, answerID)
return result return result
} }
func (brige *wsBrige) htmlPropertyValue(htmlID, name string) string {
brige.answerMutex.Lock()
answerID := brige.answerID
brige.answerID++
brige.answerMutex.Unlock()
answer := make(chan DataObject)
brige.answer[answerID] = answer
if brige.runFunc("getPropertyValue", answerID, htmlID, name) {
data := <-answer
if value, ok := data.PropertyValue("value"); ok {
return value
}
}
delete(brige.answer, answerID)
return ""
}
func (brige *wsBrige) answerReceived(answer DataObject) { func (brige *wsBrige) answerReceived(answer DataObject) {
if text, ok := answer.PropertyValue("answerID"); ok { if text, ok := answer.PropertyValue("answerID"); ok {
if id, err := strconv.Atoi(text); err == nil { if id, err := strconv.Atoi(text); err == nil {