Como usar em uma tela um elemento de texto com uma fonte descrita em CSS

8

Isso está dentro do projeto Bismon (um software GPLv3 + financiado por projetos europeus do H2020), git commit0e9a8eccc2976f . Este rascunho de relatório descreve o software. Esta questão fornece mais contexto e motivações. Trata -se do arquivo webroot / jscript / bismon-hwroot.js (escrito à mão) , usado em algumas páginas HTML cujo código é gerado pelo Bismon (um servidor da web especializado acima do libonion ).

Eu adicionei alguma classe CSS para extensão, por exemplo span.bmcl_evalprompt(por exemplo, no meu arquivo first-theme.css ).

Como codifico o JavaScript para adicionar um pedaço de texto em uma tela (de preferência usando jcanvas com jquery) com o mesmo estilo (mesma fonte, cor, etc ...) que isso span.bmcl_evalprompt? Preciso criar um elemento de extensão no meu DOM? Isso é simplesmente possível?

Eu me preocupo apenas com um Firefox recente (pelo menos 68) no Linux. JQuery é 3.4. Também estou usando o Jquery UI 1.12.1

A idéia que eu tinha em mente era criar um único <span class='bmcl_evalprompt'>elemento com coordenadas distantes da janela de exibição do navegador (ou janela X11), por exemplo, at x= -10000e y= -10000 (em pixels), em seguida, adicionar esse elemento mal posicionado no documento DOM e usar o tradicional Técnicas de Jquery para obter a família da fonte, o tamanho da fonte e o tamanho do elemento. Mas existe alguma maneira melhor? Ou alguma biblioteca compatível com Jquery está fazendo isso?

Basile Starynkevitch
fonte

Respostas:

5

Fonte DOMs correspondente na tela?

Resposta simples é: "Muito difícil !!" e "Isso nunca será perfeito".

O melhor que você pode fazer é uma aproximação, que está no exemplo na parte inferior da resposta, que também mostrará que a correspondência do estilo visível não está relacionada à qualidade visível.

Estendendo-se apenas de regras CSS.

Se você deseja que a fonte corresponda o mais próximo possível do elemento, existem algumas preocupações adicionais, além de obter o CSS, conforme indicado na resposta da Spark Fountain .

Tamanho da fonte e tamanho dos pixels CSS

  • O tamanho da fonte está relacionado ao tamanho do pixel CSS. O HTMLCanvasElement
  • O tamanho do pixel CSS nem sempre corresponde aos pixels de exibição do dispositivo. Por exemplo, displays HiDPI / Retina. Você pode acessar a proporção de pixels em CSS do dispositivo viadevicePixelRatio
  • O tamanho do pixel CSS não é constante e pode ser alterado por vários motivos. As alterações podem ser monitoradas viaMediaQueryListEvent e ouvindo o changeevento
  • Elementos podem ser transformados. oCanvasRenderingContext2D não é possível fazer transformações em 3D, é o elemento ou a tela tem uma transformação em 3D. Você não poderá corresponder a fonte renderizada da tela com a fonte renderizada dos elementos.

  • A resolução da tela e o tamanho da tela são independentes.

    • Você pode obter a resolução da tela através das propriedades HTMLCanvasElement.width eHTMLCanvasElement.height
    • Você pode obter o tamanho da tela de exibição através das propriedades de estilo width e height, ou através de uma variedade de outros métodos, veja o exemplo.
    • O aspecto de pixel da tela pode não corresponder ao aspecto do pixel CSS e deve ser calculado ao renderizar a fonte na tela.
    • A renderização de fontes Canvas em tamanhos pequenos é terrível. Por exemplo, uma fonte 4px renderizada com 16px de tamanho é ilegível. ctx.font = "4px arial"; ctx.scale(4,4); ctx.fillText("Hello pixels");Você deve usar um tamanho de fonte fixo que tenha resultados de renderização de tela de boa qualidade e reduza a renderização ao usar fontes pequenas.

Cor da fonte

O estilo de cor dos elementos representa apenas a cor renderizada. Não representa a cor real como vista pelo usuário.

Como isso se aplica à tela e ao elemento do qual você está obtendo a cor e a todos os elementos de sobre ou abaixo da camada, a quantidade de trabalho necessária para combinar visualmente a cor é enorme e está muito além do escopo de uma resposta de estouro de pilha (as respostas têm uma comprimento máximo de 30K)

Renderização de fontes

O mecanismo de renderização da fonte da tela é diferente do do DOM. O DOM pode usar uma variedade de técnicas de renderização para melhorar a qualidade aparente das fontes, aproveitando a forma como os sub-pixels RGB físicos dos dispositivos são organizados. Por exemplo, fontes TrueType e dicas relacionadas usadas pelo renderizador e as definições derivadas do ClearType sub pixel secundário com renderização de dicas.

Esses métodos de renderização de fonte PODEM ser combinados na tela , embora, para correspondência em tempo real, você precise usar o WebGL.

O problema é que a renderização da fonte DOMs é determinada por vários fatores, incluindo as configurações do navegador. O JavaScript não pode acessar nenhuma das informações necessárias para determinar como a fonte é renderizada. Na melhor das hipóteses, você pode adivinhar.

Complicações adicionais

Também existem outros fatores que afetam a fonte e como as regras de estilo de fonte CSS se relacionam com o resultado visual da fonte exibida. Por exemplo, unidades CSS, modo de animação, alinhamento, direção, transformações de fonte e peculiaridades.

Pessoalmente, para renderização e cores, não me incomodo. Evento, se eu escrevi um mecanismo de fonte completo usando o WebGL para corresponder a todas as variantes de fonte, filtragem, composição e renderização, elas não fazem parte do padrão e, portanto, estão sujeitas a alterações sem aviso prévio. O projeto seria, portanto, sempre aberto e poderia a qualquer momento falhar no nível de resultados ilegíveis. Só não vale o esforço.


Exemplo

O exemplo tem uma tela de renderização à esquerda. A parte superior central do texto e da fonte. Uma visualização ampliada à direita, que mostra uma visualização ampliada da tela da tela esquerda

O primeiro estilo usado é o padrão das páginas. A resolução da tela é 300by150, mas dimensionada para caber 500 a 500 pixels em CSS. Isso resulta em um texto de tela com MUITO baixa qualidade. O ciclo da resolução da tela mostrará como a tela afeta a qualidade.

As funções

  • drawText(text, x, y, fontCSS, sizeCSSpx, colorStyleCSS)desenha o texto usando valores de propriedade CSS. Dimensionar a fonte para corresponder ao tamanho visual do DOM e à proporção, o mais próximo possível.

  • getFontStyle(element) retorna os estilos de fonte necessários como um objeto de element

Uso da interface do usuário

  • CLIQUE na fonte central para alternar entre estilos de fonte.

  • Clique em tela esquerda para alternar entre as resoluções da tela.

  • Na parte inferior, está a configuração usada para renderizar o texto na tela.

Você verá que a qualidade do texto depende da resolução da tela.

Para ver como o zoom DOM afeta a renderização, você deve ampliar ou reduzir a página. Os monitores HiDPI e retina terão um texto de tela de qualidade muito mais baixa devido ao fato de a tela ter metade dos res dos pixels 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>

Blindman67
fonte
3

Se você simplesmente deseja renderizar o texto de sua extensão em uma tela, poderá acessar os atributos de estilo usando a função window.getComputedStyle . Para tornar a extensão original invisível, defina seu estilo como 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>

Fonte de faísca
fonte