¿Fuente de DOMs coincidente en lienzo?
La respuesta simple es: "¡Muy difícil!" y "Nunca será perfecto".
Lo mejor que puede hacer es una aproximación que se encuentra en el ejemplo al final de la respuesta, que también mostrará que la coincidencia del estilo visible no está relacionada con la calidad visible.
Extendiéndose desde solo reglas CSS.
Si desea que la fuente coincida lo más posible con el elemento, existen algunas preocupaciones adicionales además de obtener el CSS como se indica en la respuesta de Spark Fountain .
Tamaño de fuente y tamaño de píxeles CSS
- El tamaño de fuente está relacionado con el tamaño de píxel CSS. El elemento HTMLCanvasElement
- El tamaño de píxel CSS no siempre coincide con los píxeles de visualización del dispositivo. Por ejemplo, pantallas HiDPI / Retina. Puede acceder a la ración de píxeles CSS del dispositivo a través de
devicePixelRatio
- El tamaño de píxel CSS no es una constante y puede cambiar por muchas razones. Los cambios se pueden monitorear a través
MediaQueryListEvent
del change
evento y escucharlo
Los elementos pueden ser transformados. No se CanvasRenderingContext2D
pueden realizar transformaciones en 3D, por lo tanto, es el elemento o el lienzo tiene una transformación en 3D. No podrá hacer coincidir la fuente renderizada del lienzo con los elementos representados.
La resolución del lienzo y el tamaño de la pantalla son independientes.
- Puede obtener la resolución del lienzo a través de las propiedades
HTMLCanvasElement.width
, yHTMLCanvasElement.height
- Puede obtener el tamaño de visualización del lienzo a través de las propiedades de estilo ancho y alto, o mediante una variedad de otros métodos, vea el ejemplo.
- El aspecto del píxel del lienzo puede no coincidir con el aspecto del píxel CSS y debe calcularse al representar la fuente en el lienzo.
- La representación de fuentes de lienzo en tamaños de fuente pequeños es terrible. Por ejemplo, una fuente de 4px con un tamaño de 16px no se puede leer.
ctx.font = "4px arial"; ctx.scale(4,4); ctx.fillText("Hello pixels");
Debe usar un tamaño de fuente fijo que tenga resultados de representación de lienzo de buena calidad y reducir la representación al usar fuentes pequeñas.
Color de fuente
El estilo de color de los elementos solo representa el color representado. No representa el color real visto por el usuario.
Como esto se aplica tanto al lienzo como al elemento del que está obteniendo el color y cualquier elemento sobre o debajo, la cantidad de trabajo requerida para igualar visualmente el color es enorme y está más allá del alcance de una respuesta de desbordamiento de pila (las respuestas tienen un longitud máxima de 30K)
Renderizado de fuentes
El motor de representación de fuentes del lienzo es diferente al del DOM. El DOM puede usar una variedad de técnicas de renderizado para mejorar la calidad aparente de las fuentes aprovechando la forma en que están dispuestos los subpíxeles RGB físicos del dispositivo. Por ejemplo, fuentes TrueType y sugerencias relacionadas utilizadas por el renderizador, y el subpíxel del ClearType derivado con representación de sugerencias.
Estos métodos de representación de fuentes PUEDEN coincidir en el lienzo , aunque para la coincidencia en tiempo real tendrá que usar WebGL.
El problema es que la representación de la fuente DOM está determinada por muchos factores, incluida la configuración del navegador. JavaScript no puede acceder a ninguna de la información necesaria para determinar cómo se representa la fuente. En el mejor de los casos, puede hacer una suposición educada.
Complicaciones adicionales
También hay otros factores que afectan la fuente y cómo las reglas de estilo de fuente CSS se relacionan con el resultado visual de la fuente mostrada. Por ejemplo, unidades CSS, animación, alineación, dirección, transformaciones de fuente y modo peculiar.
Personalmente para renderizar y colorear no me molesto. Evento si escribí un motor de fuente completo usando WebGL para que coincida con cada fuente, filtrado, composición y variante de representación, no son parte del estándar y, por lo tanto, están sujetas a cambios sin previo aviso. Por lo tanto, el proyecto siempre estaría abierto y, en cualquier momento, podría fallar al nivel de resultados ilegibles. Simplemente no vale la pena el esfuerzo.
Ejemplo
El ejemplo tiene un lienzo de renderizado a la izquierda. El texto y la fuente se centran en la parte superior. Una vista ampliada a la derecha, que muestra una vista ampliada del lienzo del lienzo izquierdo
El primer estilo utilizado son las páginas predeterminadas. La resolución del lienzo es de 300by150, pero se ajusta a 500 por 500 píxeles CSS. Esto da como resultado un texto de lienzo de MUY mala calidad. Al ciclar la resolución del lienzo, se mostrará cómo la resolución del lienzo afecta la calidad.
Las funciones
drawText(text, x, y, fontCSS, sizeCSSpx, colorStyleCSS)
dibuja el texto usando valores de propiedad CSS. Escalando la fuente para que coincida con el tamaño visual DOM y la relación de aspecto lo más cerca posible.
getFontStyle(element)
devuelve los estilos de fuente necesarios como un objeto de element
Uso de UI
HAGA CLIC en la fuente central para ciclos de estilos de fuente.
HAGA CLIC en el lienzo izquierdo para alternar las resoluciones del lienzo.
En la parte inferior se encuentra la configuración utilizada para representar el texto en el lienzo.
Verá que la calidad del texto depende de la resolución del lienzo.
Para ver cómo el zoom DOM afecta la representación, debe acercar o alejar la página. Las pantallas HiDPI y retina tendrán un texto de lienzo de calidad mucho menor debido al hecho de que el lienzo es la mitad de la resolución de los píxeles CSS.
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>