forked from mbk-lab/rui_orig
The Canvas.TextWidth method replaced by Canvas.TextMetrics
This commit is contained in:
parent
8216ce192a
commit
76413c931a
|
@ -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
|
||||||
|
|
|
@ -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
134
canvas.go
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
session.go
23
session.go
|
@ -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) {
|
||||||
|
|
50
webBrige.go
50
webBrige.go
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue