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
|
||||
|
||||
* Requires go 1.18 or higher
|
||||
|
|
|
@ -1702,3 +1702,44 @@ function imageError(element, event) {
|
|||
var message = "imageViewError{session=" + sessionID + ",id=" + element.id + "}";
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
type Canvas interface {
|
||||
// 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(name string, size SizeUnit, params FontParams)
|
||||
|
||||
// TextWidth calculates the width of the text drawn by a given font
|
||||
TextWidth(text string, fontName string, fontSize SizeUnit) float64
|
||||
// TextWidth calculates metrics of the text drawn by a given font
|
||||
TextMetrics(text string, fontName string, fontSize SizeUnit, fontParams FontParams) TextMetrics
|
||||
|
||||
// SetTextBaseline sets the current text baseline used when drawing text. Valid values:
|
||||
// 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)
|
||||
}
|
||||
|
||||
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) {
|
||||
script.WriteString("\nctx.font = '")
|
||||
if params.Italic {
|
||||
|
@ -644,57 +720,9 @@ func (canvas *canvasData) SetFontWithParams(name string, size SizeUnit, params F
|
|||
canvas.setFontWithParams(name, size, params, &canvas.script)
|
||||
}
|
||||
|
||||
func (canvas *canvasData) TextWidth(text string, fontName string, fontSize SizeUnit) float64 {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
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) TextMetrics(text string, fontName string, fontSize SizeUnit, fontParams FontParams) TextMetrics {
|
||||
view := canvas.View()
|
||||
return view.Session().canvasTextMetrics(view.htmlID(), canvas.fontWithParams(fontName, fontSize, fontParams), text)
|
||||
}
|
||||
|
||||
func (canvas *canvasData) SetTextBaseline(baseline int) {
|
||||
|
|
|
@ -667,48 +667,15 @@ func (player *mediaPlayerData) SetCurrentTime(seconds float64) {
|
|||
}
|
||||
|
||||
func (player *mediaPlayerData) getFloatPlayerProperty(tag string) (float64, bool) {
|
||||
|
||||
script := allocStringBuilder()
|
||||
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(`=' + 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)
|
||||
value := player.Session().htmlPropertyValue(player.htmlID(), tag)
|
||||
if value != "" {
|
||||
result, err := strconv.ParseFloat(value, 32)
|
||||
if err == nil {
|
||||
return w, true
|
||||
return result, true
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -751,45 +718,14 @@ func (player *mediaPlayerData) Volume() float64 {
|
|||
}
|
||||
|
||||
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()
|
||||
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
|
||||
}
|
||||
case "1", "true", "on":
|
||||
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
|
||||
}
|
||||
|
||||
|
|
23
session.go
23
session.go
|
@ -11,7 +11,8 @@ type webBrige interface {
|
|||
runFunc(funcName string, args ...any) bool
|
||||
readMessage() (string, bool)
|
||||
writeMessage(text string) bool
|
||||
runGetterScript(script string) DataObject
|
||||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||||
htmlPropertyValue(htmlID, name string) string
|
||||
answerReceived(answer DataObject)
|
||||
close()
|
||||
remoteAddr() string
|
||||
|
@ -99,7 +100,8 @@ type Session interface {
|
|||
writeInitScript(writer *strings.Builder)
|
||||
runFunc(funcName string, args ...any)
|
||||
runScript(script string)
|
||||
runGetterScript(script string) DataObject //, answer chan DataObject)
|
||||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||||
htmlPropertyValue(htmlID, name string) string
|
||||
handleAnswer(data DataObject)
|
||||
handleRootSize(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 {
|
||||
return session.brige.runGetterScript(script)
|
||||
return session.brige.canvasTextMetrics(htmlID, font, text)
|
||||
}
|
||||
|
||||
ErrorLog("No connection")
|
||||
result := NewDataObject("error")
|
||||
result.SetPropertyValue("text", "No connection")
|
||||
return result
|
||||
return TextMetrics{Width: 0}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
50
webBrige.go
50
webBrige.go
|
@ -151,6 +151,10 @@ func (brige *wsBrige) writeMessage(script string) bool {
|
|||
DebugLog("Run script:")
|
||||
DebugLog(script)
|
||||
}
|
||||
if brige.conn == nil {
|
||||
ErrorLog("No connection")
|
||||
return false
|
||||
}
|
||||
if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
|
||||
ErrorLog(err.Error())
|
||||
return false
|
||||
|
@ -158,7 +162,9 @@ func (brige *wsBrige) writeMessage(script string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (brige *wsBrige) runGetterScript(script string) DataObject {
|
||||
func (brige *wsBrige) canvasTextMetrics(htmlID, font, text string) TextMetrics {
|
||||
result := TextMetrics{}
|
||||
|
||||
brige.answerMutex.Lock()
|
||||
answerID := brige.answerID
|
||||
brige.answerID++
|
||||
|
@ -166,30 +172,36 @@ func (brige *wsBrige) runGetterScript(script string) DataObject {
|
|||
|
||||
answer := make(chan DataObject)
|
||||
brige.answer[answerID] = answer
|
||||
errorText := ""
|
||||
if brige.conn != nil {
|
||||
script = "var answerID = " + strconv.Itoa(answerID) + ";\n" + script
|
||||
if ProtocolInDebugLog {
|
||||
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"
|
||||
|
||||
if brige.runFunc("canvasTextMetrics", answerID, htmlID, font, text) {
|
||||
data := <-answer
|
||||
result.Width = dataFloatProperty(data, "width")
|
||||
}
|
||||
|
||||
result := NewDataObject("error")
|
||||
result.SetPropertyValue("text", errorText)
|
||||
delete(brige.answer, answerID)
|
||||
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) {
|
||||
if text, ok := answer.PropertyValue("answerID"); ok {
|
||||
if id, err := strconv.Atoi(text); err == nil {
|
||||
|
|
Loading…
Reference in New Issue