A renderização de texto odeia você


Renderização de texto: quão complicado pode ser? Acontece ser incrivelmente desafiador! Até onde eu sei, literalmente, nenhum sistema exibe o texto "perfeitamente". Em algum lugar melhor, em algum lugar pior.

Suponha que você queira texto arbitrário com fontes, cores e estilos arbitrários, com suporte para quebra de linha e destaque de texto. De fato, esses são os requisitos mínimos para a exibição correta de textos complexos, uma janela de terminal, uma página da web etc.

Em geral, digamos imediatamente: não há respostas corretas consecutivas, tudo é muito mais importante do que você pensa e tudo afeta todo o resto.

Discutiremos tópicos que não estão unidos em um único conceito; esses são apenas os problemas que tive que enfrentar durante vários anos trabalhando na renderização de texto no Firefox. Por exemplo, não discutiremos detalhadamente os problemas de segmentação de texto ou o gerenciamento de várias bibliotecas de texto para uma plataforma específica, pois não estou muito interessado nisso.

1. Terminologia


A natureza do texto é complexa e o inglês transmite mal todas as nuances. Neste documento, tentarei aderir aos seguintes termos. Observe que essas palavras não estão "corretas"; apenas as considero úteis para transmitir conceitos-chave a falantes nativos de inglês que não têm experiência em lingüística.

Personagens:

  • Escalar (escalar): escalar Unicode, a "menor unidade" em Unicode (também é um ponto de código).
  • Caractere: um cluster de grafema Unicode estendido (EGC), a “maior unidade” em Unicode (potencialmente composta por vários escalares).
  • Glifo (glifo): a unidade atômica de renderização fornecida em uma fonte. Geralmente, possui um identificador exclusivo na fonte.
  • Ligadura: um glifo que consiste em vários escalares e potencialmente até vários caracteres (os falantes nativos podem representar uma ligadura como vários caracteres, mas para uma fonte é apenas um caractere).
  • Emoji: glifo "colorido".

Fontes

  • Fonte: um documento que mapeia caracteres para glifos.
  • Escrita / escrita (script): um conjunto de glifos que compõem um determinado idioma (as fontes, em regra, implementam certos scripts).
  • Fonte manuscrita (script cursivo): qualquer fonte na qual os glifos se tocam e fluem entre si (por exemplo, árabe).
  • Cor: valores RGB e alfa para fontes (não é necessário em alguns casos de uso, mas isso é interessante).
  • Estilo: modificadores em negrito e itálico para fontes (em implementações práticas, dicas, aliases e outras configurações também são geralmente fornecidas).

2. O estilo, o layout e a forma dependem um do outro?


Aqui está um breve resumo para ter uma idéia de como funciona um pipeline de renderização de texto típico:

  1. Estilização (marcação de análise, sistema de consulta de fontes).
  2. Layout (dividindo o texto em linhas).
  3. Modelagem, modelagem (cálculo de glifos e suas posições).
  4. Rasterização dos glifos necessários para o atlas / cache de textura).
  5. Composição (copiar glifos do atlas para a posição desejada).

Infelizmente, essas etapas não são tão simples quanto parecem.

A maioria das fontes não produz todos os glifos possíveis sob demanda. Existem muitos glifos; portanto, as fontes geralmente implementam apenas uma letra específica. Os usuários finais geralmente não sabem ou não se importam com isso; portanto, um sistema confiável deve cascatear com outras fontes se os caracteres não estiverem disponíveis.

Por exemplo, embora a marcação do texto a seguir não implique várias fontes, é necessário para a renderização adequada em qualquer sistema: hello 好 ب بسم 好. Portanto, estamos nos aproximando perigosamente do fato de que a etapa 1 (estilização) começa a depender da etapa 3 (modelagem)!

(Como alternativa, você pode adotar a abordagem Noto e usar uma única fonte Uber que contém todos os caracteres. Embora os usuários não possam configurá-la e você não pode fornecer uma interface de texto "nativa" para os usuários em todas as plataformas. Mas suponha que você precise de uma interface mais confiável. decisão).

Da mesma forma, para o layout, você precisa saber quanto espaço cada parte do texto ocupa, mas isso só se torna conhecido após a modelagem! A etapa 2 depende dos resultados da etapa 3?

Mas para moldar, você precisa conhecer o layout e o estilo, por isso parecemos estar presos. O que fazer?

Em primeiro lugar, a estilização aplica truques. Embora realmente desejemos obter glifos completos, os escalares são suficientes para modelar. Se a fonte não suportar a escrita corretamente, ela não alegará saber nada sobre as escalares dessa escrita. Assim, você pode encontrar facilmente a fonte "melhor" da seguinte maneira:

Para cada símbolo (EGC) em nosso texto, interrogamos cada fonte da lista (cascata) se todos os escalares que compõem esse símbolo são conhecidos por ele. Se sim, use-os. Se chegarmos ao final da lista sem resultado, obteremos tofu ( , indicador de glifo ausente).

Você provavelmente já viu esse indicador ao encontrar emoji! Como alguns emojis são na verdade ligaduras de vários emojis mais simples, uma fonte pode indicar suporte para um caractere emitindo apenas componentes individuais. Desta maneira pode literalmente parecer se a fonte for "muito antiga" para saber sobre a nova ligadura. Isso também pode acontecer se você tiver uma implementação Unicode "muito antiga" que não conhece o novo caractere, forçando o sistema de estilo a aceitar uma correspondência parcial.

Portanto, agora sabemos exatamente quais fontes usaremos, sem precisar nos referir ao layout ou ao formato (embora o formato possa alterar nossas cores, mais sobre isso nas próximas seções). Da mesma forma, podemos lidar com a interdependência entre layout e forma? Não! Coisas como quebras de parágrafo fornecem uma quebra de linha difícil, mas a única maneira de moldar é através da modelagem iterativa!

É necessário supor que o texto seja colocado em uma linha e forme essa linha até o espaço acabar. Nesse ponto, você pode executar operações de digitação e descobrir onde quebrar o texto e iniciar a próxima linha. Repita até que tudo esteja pronto.

3. O texto não possui caracteres separados


A julgar apenas pelo inglês, você pode pensar que as ligaduras são algum tipo de absurdo bizarro. Quero dizer, quem realmente se importa que "..." está escrito "ae"? Mas acontece que algumas línguas consistem essencialmente em ligaduras. Por exemplo, consists् ب بسم consiste nos caracteres individuais «ب ب س م. Em qualquer sistema avançado de renderização de texto (ou seja, em qualquer um dos principais navegadores), essas duas linhas parecerão muito diferentes.

E não: não se trata da diferença entre escalares Unicode e clusters de grafema estendidos. Se você pedir a um sistema Unicode confiável (por exemplo, Swift) para fornecer grupos de grafemas estendidos dessa linha, ele fornecerá esses cinco caracteres!

A forma do caractere depende de seus vizinhos: o texto não pode exibir corretamente caractere após caractere .

Ou seja, você deve usar uma biblioteca de modelagem. O padrão da indústria aqui é o HarfBuzz , e essas tarefas são extremamente difíceis de resolver por conta própria. Então use o HarfBuzz.

3.1 Sobreposição de texto


Em fontes manuscritas, os glifos geralmente se sobrepõem para evitar costuras, e isso pode causar problemas.

Vamos dar uma outra olhada em मनी م منش. Parece normal? Agora aumente:



Ainda parece bonito, mas vamos tornar o texto parcialmente transparente. Se você estiver no Safari ou Edge, o texto pode ficar bem! Mas no Firefox ou Chrome, a visão é terrível:



O problema é que o Chrome e o Firefox estão tentando trapacear . Eles formaram o texto corretamente, mas assim que encontram esses glifos, ainda estão tentando desenhá-los separadamente. Isso geralmente funciona bem, exceto quando há transparência e sobreposição que produzem esse escurecimento.

Uma implementação “correta” trará o texto para uma superfície temporária sem transparência e depois para a cena com transparência. Firefox e Chrome não fazem isso porque é caro e geralmente não é necessário para os principais idiomas ocidentais. Curiosamente, eles realmente entendem o problema, porque processam especificamente esse script para emoji (mas retornaremos a isso mais tarde).

3.2 O estilo pode mudar a ligadura


Ok, neste exemplo estamos analisando principalmente por curiosidade sobre como a marcação pode ser quebrada, embora eu não conheça nenhum cenário razoável em que possa realmente doer. Aqui estão duas partes de texto com o mesmo conteúdo, mas com cores diferentes:



Aqui está a aparência deles no Safari:



É assim que eles se parecem no Chrome (ao usar sua nova implementação de maquete ):



E aqui estão eles no Firefox:



Em resumo:

  • O Safari é inadequado
  • O Chrome analisa glifos, mas descarta muitas cores
  • O Firefox analisa simultaneamente glifos e exibe cores

Eu acho que todo mundo deveria estar no Firefox, certo? Mas se você ampliar, veremos que ele está fazendo algo muito estranho:



Ele simplesmente dividiu essa ligadura em quatro partes iguais, com cores diferentes!

O problema é que realmente não há uma resposta razoável sobre o que deve ser feito aqui. Dividimos a ligadura em diferentes estilos e, como a ligadura é, de certo modo, uma "unidade" de renderização, faz sentido simplesmente recusar-se a apoiar essa separação (como a maioria).

Por alguma razão, alguém no Firefox estava realmente entusiasmado em fazer uma implementação mais elegante . Sua abordagem é desenhar uma ligadura várias vezes com máscaras ideais e cores diferentes, o que funciona surpreendentemente bem!

algum sentido em tentar apoiar essas “ligaduras parciais”: apenas a modelagem pode saber se uma ligadura específica será exibida, e isso depende das fontes do sistema; portanto, a ligadura pode aparecer onde ninguém esperava! Um exemplo clássico do idioma inglês é uma ligadura æ de uma fonte instalada pelo usuário na borda de um hiperlink.

Também é bastante estranho que o inglês possa mudar no meio de uma palavra, mas não pode fontes manuscritas?

Nem pergunte sobre código que quebra linhas com ligaduras parciais.

4. Emoji quebra cor e estilo


Se você exibir emojis como o sistema nativo, precisará ignorar as configurações de cor do texto (com exceção da transparência):



Normalmente, os emojis têm suas próprias cores nativas e essa cor pode até ter significado semântico, como é o caso dos modificadores de cor da pele. Além disso: eles podem ter várias cores!

Até onde eu sei, não havia esse problema antes dos emojis, então plataformas diferentes têm abordagens diferentes para resolver. Alguns mostram emojis como uma imagem sólida (Apple), outros como uma série de camadas monocromáticas (Microsoft).

A última abordagem não é ruim, porque se integra bem aos pipelines de renderização de texto existentes, “apenas” dividindo o glifo em uma série de glifos monocromáticos com os quais todos estão acostumados a trabalhar.

No entanto, isso significa que, quando você desenha um glifo “único”, seu estilo pode mudar repetidamente . Isso também significa que o glifo "um" pode se sobrepor, o que leva aos problemas de transparência mencionados na seção anterior. Ainda assim, os navegadores realmente combinam corretamente a transparência das camadas nos emojis!

Essa discrepância pode ser explicada de três maneiras:

  • Você já está procurando glifos coloridos para processá-los de uma maneira especial, para que seja fácil escolher um caminho de layout especial.
  • Fontes manuscritas com pouca transparência parecem um pouco feias, mas os emojis se decompõem completamente e se transformam em um conjunto de caracteres ilegíveis, de modo que o trabalho extra é justificado.
  • Os desenvolvedores ocidentais se preocupam mais com emojis do que com idiomas como árabe e marata.

Escolha a opção ao seu gosto.

E, no entanto, como destacar um emoticon em itálico ou negrito? Ignorar esses estilos? Eles devem ser sintetizados? Quem sabe ...

Além disso, esses emojis não parecem estranhamente pequenos?

Sim, por algum motivo, vários sistemas aumentam secretamente o tamanho da fonte dos emojis para torná-los melhores.

5. Alisar é um inferno


Os caracteres no texto são muito pequenos e detalhados. É muito importante que o texto seja fácil de ler. Parece uma tarefa de suavização! Inferno, 480p é realmente de baixa resolução. Mais suavização !!!

Portanto, existem dois tipos principais:

  • Suavização da escala de cinza
  • Suavização de subpixel

A suavização da escala de cinza é uma abordagem "natural". A idéia básica é que pixels parcialmente revestidos obtenham transparência parcial. Durante a composição, isso fará com que o pixel obtenha o tom apropriado, melhorando os detalhes gerais.

O termo "tons de cinza" é usado para cores unidimensionais, assim como nossa transparência unidimensional (caso contrário, os glifos são exibidos em uma cor sólida). Além disso, em uma situação típica de texto em preto sobre fundo branco, a suavização de serrilhado exibe literalmente tons de cinza nas bordas.

O anti-aliasing de subpixel é um truque que abusa do posicionamento normal de pixels nos monitores. É muito mais complicado, portanto, se você estiver realmente interessado, precisará ler uma documentação mais detalhada; aqui está apenas uma breve descrição do conceito de alto nível.

Os pixels do seu monitor são na verdade três pequenas colunas de vermelho, verde e azul. Se você quer ficar vermelho, você meio que diz "preto branco preto". Da mesma forma, se você deseja obter uma cor azul, especifique "preto preto branco". Em outras palavras, se você mexer com flores, poderá triplicar a resolução horizontal e obter muito mais detalhes!

Você pode pensar que esse "arco-íris" seria muito feio, mas na prática o sistema funciona muito bem (embora alguns discordem disso). O cérebro humano adora reconhecer padrões e suavizá-los. No entanto, se você capturar uma captura de tela do texto com suavização de subpixel, verá claramente todas as cores extras se redimensionar a imagem ou apenas olhar para ela no monitor com um layout de subpixel diferente. É por isso que as capturas de tela com texto geralmente parecem muito estranhas e ruins.

(Em geral, esse sistema também significa que a cor do ícone pode mudar acidentalmente seu tamanho e posição percebidos, o que é realmente irritante).

Portanto, o anti-aliasing de subpixel é um truque realmente limpo que pode melhorar significativamente a inteligibilidade do texto, ótimo! Mas, infelizmente, isso também é uma grande lasca na bunda!

Observe que as alterações de glifo de subpixel ocorrem em qualquer sistema de suavização de serrilhado. Você sempre deseja que seus glifos rasterizados sejam ajustados a pixels completos, mas a própria rasterização foi projetada para um deslocamento específico de sub-pixels (um valor entre 0 e 1).

Para entender isso, imagine um quadrado preto 1x1 com suavização da escala de cinza:

  • Se o deslocamento do subpixel for 0, apenas um pixel preto será exibido durante a rasterização.
  • Se o deslocamento do subpixel for 0,5 e, quando rasterizado, dois pixels sairão com 50% de cinza.

5.1 As compensações de subpixel quebram o cache de glifo


A rasterização de glifos requer uma quantidade incrível de computação, por isso é muito melhor armazená-los em cache em um atlas de textura. Mas como armazenar em cache texturas com deslocamentos de subpixel? Cada deslocamento possui sua própria rasterização exclusiva!

Aqui você precisa encontrar um compromisso entre qualidade e desempenho, e isso pode ser feito otimizando as compensações de subpixel. Para o texto em inglês, um equilíbrio razoável seria a falta de precisão do subpixel vertical com o deslocamento horizontal vinculado a um quarto de um número inteiro. Isso deixa apenas quatro posições de sub-pixel, o que ainda melhora muito a qualidade, mantendo um tamanho de cache razoável.

5.2 Os subpixels de suavização não podem ser compostos


Um recurso interessante de suavização de serrilhado em tons de cinza é que você pode brincar livremente com ele, e isso degrada normalmente. Por exemplo, se você converter uma textura com texto (escala, rotação ou transformação), ela poderá ficar um pouco embaçada, mas parecerá normal em geral.

Se você fizer o mesmo com a suavização de serrilhado de subpixel, ela ficará horrível. Sua idéia é manipular os pixels na tela. Se os pixels da tela não corresponderem aos pixels da sua textura, as bordas vermelha e azul serão claramente visíveis!

Você pode pensar que isso é "corrigido" simplesmente por uma nova rasterização de glifos em um novo local. E, de fato, se a conversão for estática, isso pode funcionar. Mas se a transformação for uma animação , ficará ainda pior.Na verdade, esse é um erro muito comum do navegador: se não achar que a animação está acontecendo com o texto, os caracteres tremerão , pois cada glifo salta entre diferentes ligações de subpixel com dicas em cada quadro.

Como resultado, os navegadores contêm várias heurísticas para detectar essas animações, a fim de forçar o anti-aliasing de sub-pixel para esta parte da página (e, idealmente, até para o posicionamento de sub-pixel). Isso é bastante difícil de implementar de maneira confiável, porque uma animação pode ser acionada por um JS arbitrariamente complexo, sem dar ao navegador nenhuma "dica" clara.

Além disso, a suavização de subpixel é difícil de usar na presença de transparência parcial. De fato, aqui configuramos nossos canais R, G e B para codificar três valores de transparência (um para cada subpixel), mas o próprio texto também tem uma cor e um fundo, para que as informações sejam facilmente perdidas.

Ao usar o anti-aliasing em escala de cinza, temos um canal alfa dedicado, para que nada seja perdido. Assim, os navegadores geralmente usam tons de cinza para trabalhar com objetos translúcidos.

... Exceto Firefox. Novamente, nessa organização estranha, alguém realmente se empolgou e fez algo complicado: um componente alfa. Acontece que você pode realmente compor texto corretamente com anti-aliasing de subpixel, mas isso requer três canais de transparência adicionais para R, G e B. Não é de surpreender que esse anti-aliasing dobre o consumo de memória.

Felizmente, ao longo dos anos, a suavização sub-pixel se tornou menos relevante:

  • Os displays de retina não precisam disso.
  • O layout de sub-pixel nos telefones bloqueia esse truque (sem trabalho sério).
  • Nas versões mais recentes do MacOS, o texto do subpixel é desativado por padrão no nível do SO.
  • O Chrome parece ser mais agressivo ao desativar o anti-aliasing de subpixel (não tenho certeza se essa é a política exata).
  • O novo backend gráfico do Firefox (webrender) abandonou o componente Alpha por simplicidade.

6. Esotérico


Esta parte é apenas um monte de pequenas coisas que não merecem muita discussão.

6.1 As fontes podem conter SVG


Isso é péssimo. Essas fontes são fornecidas principalmente pela Adobe, porque há algum tempo elas entraram no SVG muito bem. Às vezes, você pode simplesmente ignorar partes do SVG (acredito que a fonte do Source Code Pro contém tecnicamente alguns glifos SVG, mas na prática eles não são realmente usados ​​por sites), mas em geral você precisará implementar o suporte ao SVG para dar suporte formal a todas as fontes.

E você já ouviu falar sobre fontes animadas SVG ? Não? Bom Eu acho que eles estão quebrados ou não foram implementados em todos os lugares (o Firefox acidentalmente os apoiou por um tempo devido a algum tipo de desenvolvedor entusiasmado).

6.2 Personagens podem ser muito grandes


Se você deseja satisfazer ingenuamente a solicitação de um usuário por uma fonte muito grande (ou um nível de zoom muito grande), encontrará problemas extremos de gerenciamento de memória para um atlas de glifo desse tamanho, pois cada caractere pode ser maior que a tela inteira. Existem várias maneiras de lidar com isso:

  • Recuse-se a desenhar um glifo (usuário triste).
  • Rasterize o glifo em um tamanho menor e aumente a escala durante a composição (isso é fácil, mas as formas ficam borradas ao longo das bordas).
  • Rasterize o glifo diretamente na superfície após a composição (difícil, potencialmente caro).

6.3 A seleção não é um quadro, mas o texto segue em todas as direções


As pessoas geralmente sabem que a direção principal do texto pode ser da esquerda para a direita (inglês), da direita para a esquerda (árabe) ou de cima para baixo (japonês).

Então, aqui está um texto engraçado para você:

Olá a todos بسم الله لا beep beep !!


Se você selecionar texto na área de trabalho com o mouse da esquerda para a direita, a seleção se tornará intermitente e, de alguma maneira, estranhamente se contorce no meio. Isso ocorre porque misturamos o texto em uma linha da esquerda para a direita e da direita para a esquerda, o que acontece o tempo todo.

Primeiro, a seleção à direita aumenta a seleção, mas diminui até que de repente começa a aumentar novamente. Na verdade, isso é bastante correto: a seleção simplesmente permanece contínua na linha real . Assim, você pode copiar corretamente um pedaço de texto.

Você precisa levar isso em consideração no seu código para destacar o texto, bem como no algoritmo de quebra de linha para o layout.

Mas isso não é tudo.


Espero que você não precise lidar com essas coisas.

6.4 Como escrever o que é impossível escrever?


Quando não há caracteres na fonte, seria bom informar o usuário sobre isso. O glifo "tofu" é destinado a isso. Você pode simplesmente desenhar um tofu vazio (retângulo) e limitar-se a isso. Mas se você deseja fornecer informações realmente úteis, pode escrever o valor do caractere ausente para simplificar a depuração.

Mas espere, usamos texto para explicar que não podemos produzir o texto? Hum.

Você pode dizer que deve haver uma fonte básica no sistema que sempre mostre os caracteres de 0 a 9 e AF, mas isso é uma suposição para os fracos. Se o usuário realmente destruiu suas ferramentas com essas ferramentas, o Firefox oferece uma saída: uma micro fonte!

Dentro do Firefox, há uma pequena matriz codificada de pixel art de um bit com um pequeno atlas com exatamente esses 16 caracteres. Portanto, ao desenhar tofu, ele pode encaminhar esses caracteres sem se preocupar com fontes.



6.5 O estilo faz parte da fonte (a menos que não seja)


As fontes de alta qualidade são fornecidas inicialmente com estilos como itálico e negrito , pois não existe uma maneira algorítmica simples de trazer esses efeitos bem.

No entanto, algumas fontes vêm sem esses estilos; portanto, você ainda precisa de uma maneira algorítmica simples para fazer esses efeitos.

A detecção e o processamento exatos dos estilos são altamente dependentes do sistema e fora da minha área de especialização, por isso não posso explicá-lo bem. Gostaria apenas de me aprofundar no código de manipulação de fontes no Webrender .

De qualquer forma, você precisa de um fallback sintético . Felizmente, a implementação é realmente bastante simples:

itálico sintético: incline cada glifo.

Negrito sintético: desenhe cada glifo várias vezes com um leve deslocamento na direção do texto.

Honestamente, essas abordagens se saem muito bem! Mas os usuários podem perceber que tudo parece "errado". Portanto, você pode fazer melhor se fizer um esforço.

6.6 Nenhuma renderização de texto perfeita


Cada plataforma teve seus erros, otimizações e peculiaridades por tanto tempo que se tornaram estética. Portanto, mesmo que você acredite firmemente que certas coisas são ideais ou importantes, sempre haverá um grande grupo de usuários com preferências diferentes. Um sistema robusto de visualização de texto suporta essas várias preferências (ao escolher padrões razoáveis).

Suas configurações devem levar em conta o sistema do usuário, fontes específicas, aplicativos específicos e textos específicos. Você também deve tentar corresponder à "aparência" nativa de cada plataforma (tais peculiaridades).

Isso inclui:

  • Capacidade de desativar o anti-aliasing de sub-pixel (alguns realmente o odeiam).
  • A capacidade de desativar qualquer anti-aliasing (sim, as pessoas fazem isso).
  • Uma tonelada de propriedades específicas de plataforma / formato, como dicas, suavização, variações, gama, etc.

Isso também significa que as bibliotecas de texto nativas devem ser usadas para corresponder à estética de cada sistema (Core Text, DirectWrite e FreeType em suas respectivas plataformas).

7. Links adicionais


Aqui estão mais alguns artigos sobre o pesadelo da renderização de texto:

Source: https://habr.com/ru/post/pt469529/


All Articles