Verwendung eines Textelements mit einer in CSS beschriebenen Schriftart in einer Zeichenfläche

8

Dies ist Teil des Bismon- Projekts (eine GPLv3 + -Software, die von europäischen H2020-Projekten finanziert wird), git commit0e9a8eccc2976f . Dieser Berichtsentwurf beschreibt die Software. Diese Frage gibt mehr Kontext und Motivationen. Es handelt sich um die (handgeschriebene) Datei webroot / jscript / bismon-hwroot.js , die in einer HTML-Seite verwendet wird, deren Code von Bismon (einem spezialisierten Webserver über libonion ) generiert wird .

Ich habe eine CSS-Klasse für span hinzugefügt, z. B. span.bmcl_evalprompt(z. B. in meiner Datei first-theme.css ).

Wie codiere ich das JavaScript, um ein Textstück in eine Leinwand einzufügen (vorzugsweise mit jcanvas mit jquery), das denselben Stil (dieselbe Schriftart, Farbe usw.) hat span.bmcl_evalprompt? Muss ich ein solches Span-Element in meinem DOM erstellen? Ist das überhaupt einfach möglich?

Ich interessiere mich nur für einen aktuellen Firefox (mindestens 68) unter Linux. JQuery ist 3.4. Ich verwende auch Jquery UI 1.12.1

Die Idee, die ich im Kopf hatte, war, ein einzelnes <span class='bmcl_evalprompt'>Element mit Koordinaten zu erstellen, die weit vom Browser-Ansichtsfenster (oder X11-Fenster) entfernt sind, z. B. bei x= -10000und y= -10000 (in Pixel), dann dieses einzelne schlecht positionierte Element in das Dokument-DOM einzufügen und dann traditionell zu verwenden Abfragetechniken zum Abrufen der Schriftfamilie, Schriftgröße und Elementgröße. Aber gibt es einen besseren Weg? Oder eine Jquery-kompatible Bibliothek, die das macht?

Basile Starynkevitch
quelle

Antworten:

5

Passende DOMs-Schriftart auf Leinwand?

Die einfache Antwort lautet: "Viel zu schwer !!" und "Es wird niemals perfekt sein."

Das Beste, was Sie tun können, ist eine Annäherung, die im Beispiel unten in der Antwort angegeben ist und die auch zeigt, dass die Übereinstimmung mit dem sichtbaren Stil nicht mit der sichtbaren Qualität zusammenhängt.

Erweiterung nur von CSS-Regeln.

Wenn Sie möchten, dass die Schriftart so genau wie möglich mit dem Element übereinstimmt, gibt es einige zusätzliche Bedenken, als nur das CSS zu erhalten, wie in der Antwort von Spark Fountain dargelegt .

Schriftgröße und CSS-Pixelgröße

  • Die Schriftgröße hängt mit der CSS-Pixelgröße zusammen. Das HTMLCanvasElement
  • Die CSS-Pixelgröße stimmt nicht immer mit den Anzeigepixeln des Geräts überein. ZB HiDPI / Retina-Displays. Sie können über auf die CSS-Pixelration des Geräts zugreifendevicePixelRatio
  • Die CSS-Pixelgröße ist keine Konstante und kann sich aus vielen Gründen ändern. Änderungen können über MediaQueryListEventdas changeEreignis überwacht und abgehört werden
  • Elemente können transformiert werden. Das CanvasRenderingContext2Dkann keine 3D-Transformationen durchführen, daher ist das Element oder die Leinwand hat eine 3D-Transformation. Sie können die auf der Leinwand gerenderte Schriftart nicht mit der auf den Elementen gerenderten Schriftart abgleichen.

  • Die Leinwandauflösung und die Anzeigegröße sind unabhängig voneinander.

    • Sie können die Leinwandauflösung über die Eigenschaften HTMLCanvasElement.widthund erhaltenHTMLCanvasElement.height
    • Sie können die Größe der Leinwandanzeige über die Stileigenschaften width und height oder über eine Vielzahl anderer Methoden abrufen (siehe Beispiel).
    • Der Canvas-Pixelaspekt stimmt möglicherweise nicht mit dem CSS-Pixelaspekt überein und muss beim Rendern der Schriftart auf der Canvas berechnet werden.
    • Das Rendern von Canvas-Schriftarten bei kleinen Schriftgrößen ist schrecklich. Beispielsweise ist eine 4px-Schriftart mit einer Größe von 16px nicht lesbar. ctx.font = "4px arial"; ctx.scale(4,4); ctx.fillText("Hello pixels");Sie sollten eine feste Schriftgröße verwenden, die gute Canvas-Rendering-Ergebnisse liefert, und das Rendering verkleinern, wenn Sie kleine Schriftarten verwenden.

Schriftfarbe

Der Farbstil der Elemente repräsentiert nur die gerenderte Farbe. Es repräsentiert nicht die tatsächliche Farbe, wie sie vom Benutzer gesehen wird.

Da dies sowohl für die Leinwand als auch für das Element gilt, von dem Sie die Farbe erhalten, sowie für alle Über- oder Unterlegelemente, ist der Arbeitsaufwand für die visuelle Anpassung der Farbe enorm und geht weit über den Rahmen einer Stapelüberlaufantwort hinaus (Antworten haben a max 30K Länge)

Schriftwiedergabe

Die Font-Rendering-Engine der Zeichenfläche unterscheidet sich von der des DOM. Das DOM kann eine Vielzahl von Rendering-Techniken verwenden, um die scheinbare Qualität der Schriftarten zu verbessern, indem es ausnutzt, wie die physischen RGB-Subpixel der Geräte angeordnet sind. Zum Beispiel TrueType- Schriftarten und zugehörige Hinweise, die vom Renderer verwendet werden, sowie das abgeleitete ClearType- Subpixel mit Hinweis-Rendering.

Diese Methoden zum Rendern von Schriftarten können auf der Leinwand abgeglichen werden. Für die Echtzeitübereinstimmung müssen Sie jedoch WebGL verwenden.

Das Problem ist, dass das Rendern von DOMs-Schriftarten von vielen Faktoren bestimmt wird, einschließlich der Browsereinstellungen. JavaScript kann nicht auf die Informationen zugreifen, die zum Ermitteln der Schriftart erforderlich sind. Bestenfalls können Sie eine fundierte Vermutung anstellen.

Weitere Komplikationen

Es gibt auch andere Faktoren, die sich auf die Schriftart auswirken und wie sich die Regeln für den CSS-Schriftstil auf das visuelle Ergebnis der angezeigten Schriftart beziehen. Zum Beispiel CSS-Einheiten, Animation, Ausrichtung, Richtung, Schrifttransformationen und Mackenmodus.

Persönlich für Rendering und Farbe störe ich nicht. Wenn ich eine vollständige Schriftarten-Engine mit WebGL geschrieben habe, die zu jeder Schriftart-, Filter-, Compositing- und Rendering-Variante passt, sind sie nicht Teil des Standards und können daher ohne vorherige Ankündigung geändert werden. Das Projekt wäre somit immer offen und könnte jederzeit zu unlesbaren Ergebnissen führen. Nur die Mühe nicht wert.


Beispiel

Das Beispiel hat links eine Renderfläche. Die Text- und Schriftartmitte oben. Eine vergrößerte Ansicht rechts, die eine vergrößerte Ansicht der linken Leinwand zeigt

Der erste verwendete Stil ist der Seitenstandard. Die Canvas-Auflösung beträgt 300 x 150, ist jedoch auf 500 x 500 CSS-Pixel skaliert. Dies führt zu SEHR schlechter Leinwandqualität. Wenn Sie die Leinwandauflösung durchlaufen, wird angezeigt, wie sich die Leinwandauflösung auf die Qualität auswirkt.

Die Funktionen

  • drawText(text, x, y, fontCSS, sizeCSSpx, colorStyleCSS)Zeichnet den Text mithilfe von CSS-Eigenschaftswerten. Skalieren Sie die Schriftart so nah wie möglich an die visuelle Größe und das Seitenverhältnis des DOM.

  • getFontStyle(element) Gibt die erforderlichen Schriftstile als Objekt von zurück element

Verwendung der Benutzeroberfläche

  • KLICKEN Sie auf die mittlere Schriftart, um die Schriftstile zu wechseln.

  • KLICKEN Sie auf die linke Leinwand, um die Leinwandauflösungen zu wechseln.

  • Unten finden Sie die Einstellung, mit der der Text auf der Leinwand gerendert wird.

Sie werden sehen, dass die Qualität des Textes von der Auflösung der Leinwand abhängt.

Um zu sehen, wie sich der DOM-Zoom auf das Rendering auswirkt, müssen Sie die Seite vergrößern oder verkleinern. HiDPI- und Retina-Displays weisen eine weitaus geringere Leinwandqualität auf, da die Leinwand die Hälfte der Auflösung der CSS-Pixel aufweist.

const ZOOM_SIZE = 16;
canvas1.width = ZOOM_SIZE;
canvas1.height = ZOOM_SIZE;
const ctx = canvas.getContext("2d");
const ctx1 = canvas1.getContext("2d");
const mouse = {x:0, y:0};
const CANVAS_FONT_BASE_SIZE = 32; // the size used to render the canvas font.
const TEXT_ROWS = 12;
var currentFontClass = 0;
const fontClasses = "fontA,fontB,fontC,fontD".split(",");

const canvasResolutions = [[canvas.scrollWidth, canvas.scrollHeight],[300,150],[200,600],[600,600],[1200,1200],[canvas.scrollWidth * devicePixelRatio, canvas.scrollHeight * devicePixelRatio]];
var currentCanvasRes = canvasResolutions.length - 1;
var updateText = true;
var updating = false;
setTimeout(updateDisplay, 0, true);

function drawText(text, x, y, fontCSS, sizeCSSpx, colorStyleCSS) { // Using px as the CSS size unit
    ctx.save();
    
    // Set canvas state to default
    ctx.globalAlpha = 1;
    ctx.filter = "none";
    ctx.globalCompositeOperation = "source-over";
    
    const pxSize = Number(sizeCSSpx.toString().trim().replace(/[a-z]/gi,"")) * devicePixelRatio;
    const canvasDisplayWidthCSSpx = ctx.canvas.scrollWidth; // these are integers
    const canvasDisplayHeightCSSpx = ctx.canvas.scrollHeight;
    
    const canvasResWidth = ctx.canvas.width;
    const canvasResHeight = ctx.canvas.height;
    
    const scaleX = canvasResWidth / (canvasDisplayWidthCSSpx * devicePixelRatio);
    const scaleY = canvasResHeight / (canvasDisplayHeightCSSpx * devicePixelRatio);
    const fontScale = pxSize / CANVAS_FONT_BASE_SIZE
    
    ctx.setTransform(scaleX * fontScale, 0, 0, scaleY * fontScale, x, y); // scale and position rendering
    
    ctx.font = CANVAS_FONT_BASE_SIZE + "px " + fontCSS;
    ctx.textBaseline = "hanging";
    ctx.fillStyle = colorStyleCSS;
    ctx.fillText(text, 0, 0);
    
    ctx.restore();
}
    
function getFontStyle(element) {
    const style = getComputedStyle(element);    
    const color = style.color;
    const family = style.fontFamily;
    const size = style.fontSize;    
    styleView.textContent = `Family: ${family} Size: ${size} Color: ${color} Canvas Resolution: ${canvas.width}px by ${canvas.height}px Canvas CSS size 500px by 500px CSS pixel: ${devicePixelRatio} to 1 device pixels`
    
    return {color, family, size};
}

function drawZoomView(x, y) {
    ctx1.clearRect(0, 0, ctx1.canvas.width, ctx1.canvas.height);
    //x -= ZOOM_SIZE / 2;
    //y -= ZOOM_SIZE / 2;
    const canvasDisplayWidthCSSpx = ctx.canvas.scrollWidth; // these are integers
    const canvasDisplayHeightCSSpx = ctx.canvas.scrollHeight;
    
    const canvasResWidth = ctx.canvas.width;
    const canvasResHeight = ctx.canvas.height;
    
    const scaleX = canvasResWidth / (canvasDisplayWidthCSSpx * devicePixelRatio);
    const scaleY = canvasResHeight / (canvasDisplayHeightCSSpx * devicePixelRatio);
    
    x *= scaleX;
    y *= scaleY;
    x -= ZOOM_SIZE / 2;
    y -= ZOOM_SIZE / 2;
    
    ctx1.drawImage(ctx.canvas, -x, -y);
}

displayFont.addEventListener("click", changeFontClass);
function changeFontClass() {
   currentFontClass ++;
   myFontText.className = fontClasses[currentFontClass % fontClasses.length];
   updateDisplay(true);
}
canvas.addEventListener("click", changeCanvasRes);
function changeCanvasRes() {
   currentCanvasRes ++;
   if (devicePixelRatio === 1 && currentCanvasRes === canvasResolutions.length - 1) {
       currentCanvasRes ++;
   }
   updateDisplay(true);
}
   
   

addEventListener("mousemove", mouseEvent);
function mouseEvent(event) {
    const bounds = canvas.getBoundingClientRect();
    mouse.x = event.pageX - scrollX - bounds.left;
    mouse.y = event.pageY - scrollY - bounds.top;    
    updateDisplay();
}

function updateDisplay(andRender = false) {
    if(updating === false) {
        updating = true;
        requestAnimationFrame(render);
    }
    updateText = andRender;
}

function drawTextExamples(text, textStyle) {
    
    var i = TEXT_ROWS;
    const yStep = ctx.canvas.height / (i + 2);
    while (i--) {
        drawText(text, 20, 4 + i * yStep, textStyle.family, textStyle.size, textStyle.color);
    }
}



function render() {
    updating = false;

    const res = canvasResolutions[currentCanvasRes % canvasResolutions.length];
    if (res[0] !== canvas.width || res[1] !== canvas.height) {
        canvas.width = res[0];
        canvas.height = res[1];
        updateText = true;
    }
    if (updateText) {
        ctx.setTransform(1,0,0,1,0,0);
        ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
        updateText = false;
        const textStyle = getFontStyle(myFontText);
        const text = myFontText.textContent;
        drawTextExamples(text, textStyle);
        
    }
    
    
    
    drawZoomView(mouse.x, mouse.y)


}
.fontContainer {
  position: absolute;
  top: 8px;
  left: 35%;
  background: white;
  border: 1px solid black;
  width: 30%;   
  cursor: pointer;
  text-align: center;
}
#styleView {
}
  

.fontA {}
.fontB {
  font-family: arial;
  font-size: 12px;
  color: #F008;
}
.fontC {
  font-family: cursive;
  font-size: 32px;
  color: #0808;
}
.fontD {
  font-family: monospace;
  font-size: 26px;
  color: #000;
}

.layout {
   display: flex;
   width: 100%;
   height: 128px;
}
#container {
   border: 1px solid black;
   width: 49%;
   height: 100%;
   overflow-y: scroll;
}
#container canvas {
   width: 500px;
   height: 500px;
}

#magViewContainer {
   border: 1px solid black;
   display: flex;
   width: 49%;
   height: 100%; 
}

#magViewContainer canvas {
   width: 100%;
   height: 100%;
   image-rendering: pixelated;
}
<div class="fontContainer" id="displayFont"> 
   <span class="fontA" id="myFontText" title="Click to cycle font styles">Hello Pixels</span>
</div>


<div class="layout">
  <div id="container">
      <canvas id="canvas" title="Click to cycle canvas resolution"></canvas>
  </div>
  <div id="magViewContainer">
      <canvas id="canvas1"></canvas>
  </div>
</div>
<code id="styleView"></code>

Blindman67
quelle
3

Wenn Sie den Text einfach aus Ihrem Bereich in einer Zeichenfläche rendern möchten, können Sie über die Funktion window.getComputedStyle auf die Stilattribute zugreifen . Um die ursprüngliche Spanne unsichtbar zu machen, setzen Sie ihren Stil auf display: none.

// get the span element
const span = document.getElementsByClassName('bmcl_evalprompt')[0];

// get the relevant style properties
const font = window.getComputedStyle(span).font;
const color = window.getComputedStyle(span).color;

// get the element's text (if necessary)
const text = span.innerHTML;

// get the canvas element
const canvas = document.getElementById('canvas');

// set the canvas styling
const ctx = canvas.getContext('2d');
ctx.font = font;
ctx.fillStyle = color;

// print the span's content with correct styling
ctx.fillText(text, 35, 110);
#canvas {
  width: 300px;
  height: 200px;
  background: lightgrey;
}

span.bmcl_evalprompt {
  display: none;           // makes the span invisible
  font-family: monospace;  // change this value to see the difference
  font-size: 32px;         // change this value to see the difference
  color: rebeccapurple;    // change this value to see the difference
}
<span class="bmcl_evalprompt">Hello World!</span>
<canvas id="canvas" width="300" height="200"></canvas>

Funkenbrunnen
quelle