Mostrando postagens com marcador HTML. Mostrar todas as postagens
Mostrando postagens com marcador HTML. Mostrar todas as postagens

19 de dezembro de 2012

Personalizando a apresentação de mapas com o Google Maps

Na maioria das vezes, as informações apresentadas num mapa produzido através da API do Google Maps são suficientes para que o usuário rapidamente consiga ver um endereço e seu entorno ou a rota a ser seguida para ir de um ponto a outro. Em certas situações, no entanto, pode ser necessário adicionar ao mapa outras informações para melhor transmitir a ideia correta que se deseja.

Esse tipo de necessidade foi exemplificado quando falei sobre o uso de marcações num mapa para apontar locais de interesse nas proximidades de um determinado endereço. O exemplo pode ser encontrado neste link.

A API do Google Maps nos permite acrescentar informações extras aos mapas através do conceito de camadas que são sobrepostas ao mapa, chamadas de overlays. Para isso, a API disponibiliza um conjunto de classes javascript. Além da já citada Marker, há classes para traçar linhas, delimitar áreas circulares ou retangulares e até mesmo para projetar uma imagem personalizada sobre uma área do mapa.

O overlay mais simples é o Polyline, usado para traçar linhas por sobre o mapa. O quadro abaixo traz código javascript que une com uma linha reta 2 pontos do mapa: a sede da ABC71 e a praça da Sé, em São Paulo:
var mapOptions = {zoom: 10,mapTypeId: google.maps.MapTypeId.ROADMAP};
map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
geocoder = new google.maps.Geocoder();

var endDe = new google.maps.LatLng(-23.566146,-46.652579);
var endAte = new google.maps.LatLng(-23.550563,-46.633101);
var request = { location: endDe, region: 'BR'};

geocoder.geocode (request, function(response, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.fitBounds (new google.maps.LatLngBounds(endDe, endAte));
/* ... */
var pontos = new google.maps.MVCArray ();
pontos.push (endDe);
pontos.push (endAte);

var rect = new google.maps.Polyline ({
path: pontos,
map:map,
strokeWeight: 5,
strokeColor: 'blue',
visible: true
});
}
});
Após as inicializações de praxe para o ambiente do Maps, o código solicita a geocodificação do endereço da ABC71 (na verdade, sua latitude/longitude).
Em resposta à conclusão da geocodificação, isto é, assim que o mapa com o endereço solicitado está pronto, eu crio uma lista MVCArray e adiciono a ela os dois pontos que definem a reta desejada.

Em seguida, instancio a classe Polyline propriamente dita, fornecendo-lhe a lista de pontos. Também indico o mapa onde a linha será traçada (map), a espessura da linha em pixels (strokeWeight) e a cor dela (strokeColor). As cores podem ser informadas tanto pelo nome HTML quanto pela notação RGB hexadecimal.

Posso, por exemplo, demarcar um bairro inteiro com esse recurso? Sim, mas você terá que montar a lista de pontos o mais completa possível para que o desenho resultante tenha uma precisão adequada. O Polyline produz linhas abertas; para traçar linhas fechadas com uma quantidade arbitrária de pontos use a classe Polygon.

A função fitBounds, usada no início da resposta, apenas aplica um nível de zoom apropriado para garantir que ambos os endereços sejam exibidos num único quadro, sem que o usuário precise interagir com ele.

O código analisado produz o mapa a seguir:
Imagine agora o caso de uma pizzaria que queira divulgar num mapa o raio de ação onde ela faz entregas. O overlay produzido pela classe Circle permite dar destaque a uma área circular, dado um centro e um raio em metros. Veja o exemplo:
var endDe = new google.maps.LatLng(-23.566146,-46.652579);
var request = { location: endDe, region: 'BR'};

geocoder.geocode (request, function(response, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(endDe);
/* ... */
var circleOpts = ({
map: map,
center: endDe,
editable: false,
fillColor: 'yellow',
fillOpacity: 0.4,
radius: 3500,
strokeColor: '#0099ff',
strokeWeight: 4,
visible: true });
var circle = new google.maps.Circle (circleOpts);
}
});
Como no caso da linha, para o círculo também valem as propriedades que indicam o mapa, a cor do traço e sua espessura. Mas, ao invés de uma lista de pontos, informamos apenas o centro; no código anterior, usei o próprio endereço da ABC71 para isso. A definição do círculo fica completa com a propriedade radius - o raio, fornecido em metros; no exemplo, o raio terá 3500 metros (ou 3,5 Km).

Outra configuração disponível diz respeito ao preenchimento da área delimitada pelo círculo. A propriedade fillColor determina a cor do preenchimento enquanto a fillOpacity controla o grau de transparência, sendo que o valor 1 significa totalmente opaco (o mapa sob o círculo não é visto) e o valor 0 suspende o preenchimento.

A propriedade editable, quando ativa, permite ao usuário redimensionar o círculo. Eu a desliguei pois ela não faz sentido no contexto do exemplo. O resultado é o mapa exibido no quadro abaixo:

Os mesmos conceitos valem para a classe Rectangle. Além disso, é permitido trabalhar com vários overlays num mesmo mapa, se for necessário. Os exemplos publicados nesse post podem ser acessados respectivamente nos seguintes links : Overlay com Linha e Overlay com Círculo.

2 de outubro de 2012

Trabalhando com marcações em mapas com a versão 3 da API do Google Maps

Além dos tradicionais recursos de exibir o mapa para um determinado endereço ou traçar rotas entre endereços fornecidos, a API do Google Maps disponibliza um outro recurso bastante interessante que permite incluir num mapa marcações customizadas. Com isso, você pode marcar pontos de interesse de acordo com as necessidades de sua aplicação. Um exemplo de aplicabilidade desse conceito seria indicar num mapa a localização de estabelecimentos comerciais nas proximidades de um endereço, tais como Caixas 24 Horas, Supermercados, escolas de inglês ou outro qualquer. Obviamente, isso demandará saber de antemão a posição de cada um dos locais passíveis de serem marcados no mapa.

O processo de marcação em si é bastante simples. Após termos ajustado a parametrização inicial do ambiente do Maps e requisitado a exibição de um endereço (veja post Usando a versão 3 da API do Google Maps), basta criar uma instância da classe google.maps.Marker, como no quadro abaixo:
function mostraEnderecoABC71 () {
var lRequest = { address: 'Alameda Santos, 1000, São Paulo - SP, 01418-902', region: 'BR'};
geocoder.geocode( lRequest, trataLocais);
}

function trataLocais (results, status){
if (status == google.maps.GeocoderStatus.OK)
{
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker(
{map: map,
position: results[0].geometry.location,
animation: google.maps.Animation.DROP,
icon: 'http://maps.google.com/mapfiles/kml/pal3/icon18.png',
shadow: 'http://maps.google.com/mapfiles/kml/pal3/icon18s.png'
});
/* ... */
}
}

A função trataLocais reproduzida acima é o callback passado para o Geocoder junto com a requisição do endereço a ser apresentado no mapa. No exemplo, a marcação é inserida no próprio endereço requisitado, razão pela qual foi usado o retorno da API results[0].geometry.location como local para fixar a marcação.

Dos parâmetros para criar o marcador, apenas 2 são obrigatórios : o map (representando a instância de Map que gerencia a apresentação do mapa na página) e o position (um par latitude/longitude onde o marcador deverá ser fixado). Os demais parâmetros são auxiliares, úteis para melhorar a apresentação. No exemplo, forneço outros 3 parâmetros : animation (uma animação dinâmica usada para cravar o marcador no mapa; o padrão é o marcador simplesmente aparecer), icon (uma URL apontando a imagem que será usada como marcador; o padrão é ) e shadow (URL com a imagem que representa uma sombra do marcador). Há uma lista com imagens padronizadas e suas respectivas sombras neste link mas pode-se trabalhar com qualquer URL personalisada que se queira.

O mapa abaixo usa esses conceitos pra incluir algumas marcações nas proximidades da sede da ABC71 em São Paulo:

A API prevê ainda a possibilidade de se abrir uma janela para apresentar outros detalhes a respeito de um ponto no mapa. A flexibilidade desse tipo de recurso é grande já que é permitido incluir código HTML para formatar as informações que são apresentadas - embora isso não seja obrigatório.

Do ponto de vista da programação, criar uma janela com informações adicionais sobre um ponto também é simples. Consiste em instanciar a classe google.maps.InfoWindow, providenciando um conteúdo para essa janela. Uma vez instanciada, podemos solicitar que a janela seja aberta num ponto específico do mapa. Complementando a função trataLocais :
function trataLocais (results, status){
if (status == google.maps.GeocoderStatus.OK)
{
/* ... */
google.maps.event.addListener(marker, 'click',
function () {
var infowindow = new google.maps.InfoWindow(
{
content: '<div style="text-align:justify;"><b>Ponto de Interesse</b><br/><br/>ABC71 Soluções em Informática.</div>'
});
infowindow.open(map, marker);
});
}
}
No código javascript do quadro anterior, a abertura da janela de informação é vinculada a um evento de "clique" no marcador. Isto é, ela só aparece se o usuário clicar no marcador em questão. Veja que o conteúdo (content) da janela foi formatado com tags HTML, possibilitando adequar a apresentação ao propósito de sua aplicação. A função open - que exibe a janela - aceita como parâmetros o mapa que estamos usando e o marcador que servirá de referência para a exibição da janela.

No mapa que aparece nesse post, os marcadores podem ser clicados, ação que cria uma janela simples com outras informações sobre o ponto marcado.

O arquivo HTML encontrado neste endereço traz o exemplo completo mostrado aqui no post. Ele simula a recepção em formato XML de informações sobre locais que devem ser marcados no mapa. Para isso, o exemplo utiliza também recursos da biblioteca JQuery.

11 de abril de 2012

Novo IPad complica a vida dos desenvolvedores HTML5

Se você já achava dura a vida de desenvolvedor HTML, com as muitas diferenças de implementação nos diversos navegadores e a infinidade de tamanhos de telas disponíveis em desktops, tablets e celulares, veja só essa matéria publicada na InfoWorld sobre os teste feitos com os recursos do novo tablet da Apple - o iPad 3.
O novo iPad da Apple, já um sucesso entre os consumidores com sua tela de alta resolução, está, no entanto, decepcionando alguns desenvolvedores HTML5. O sistema operacional do tablet - iOS 5.1 - complica o armazenamento de dados do HTML5, não oferece novos suportes ao HTML5 e a performance na navegação web é, na melhor das hipóteses, similar à do iPad 2.

É demais classificar isso de "retrocesso" e ninguém está dizendo que a Apple está recuando de seu apoio agressivo aos padrões na web, o que eventualmente deixará aplicações web com comportamento próximo ao das aplicações nativas. Mas, para alguns, as decisões da Apple comprometem e poderiam viver sem elas.

A Sencha, fornecedora de ferramentas para HTML5, postou na semana passada uma "Avaliação HTML5' para o novo iPad e o iOS 5.1, classificando os resultados como um "saco misto" para a Apple. A avaliação do fornecedor pesou dois critérios: completude –- quanto dos vários elementos do HTML5 estão presentes –- e correção –- se o suporte a esses elementos está bem implementado, diz Aditya Bansod, diretor senior e gerente de produto da empresa de software sediada em Redwood City, Califórnia. O post também inclui resultados de comparativos com outros produtos em relação à performance do tablet na navegação.

"Ainda é a melhor plataforma HTML5 no mercado", diz Bansod. "Mas esperávamos um avanço maior que este (no novo iPad). Ao invés disso, estamos pisando em terreno pantanoso e até mesmo retrocedemos um pouco. É um tanto desapontador por parte da Apple."

Complicando o armazenamento de dados Web
Uma mudança, introduzida primeiro em 2011 com uma distribuição beta do iOS 5.1, limita alguns aspectos do armazenamento de dados local do HTML5. Dados armazenados localmente usando o LocalStorage do HTML5 não são mais tratados como persistentes pelo sistema operacional. Isto introduz um problema para desenvolvedores que usam este recurso para armazenar dados locais ou o WebSQL como mecanismo de armazenamento. Como o sistema não enxerga mais esses dados como persistentes e sim como temporários, "o iOS pode removê-los a qualquer momento, sem aviso, até em cenários onde o sistema está com pouca memória disponível", anotou Bansod em seu post.

Os desenvolvedores web rapidamente captaram a mudança em janeiro, discutindo-a em vários foruns online, como o forum Phonegap no Google Groups.

O problema afeta um sub grupo de aplicações do iOS, às vezes chamadas de aplicações híbridas, que utilizam uma WebView embutida. "WebViews são a força das aplicações HTML5 inseridas em pacotes nativos, como os do PhoneGap ou do Sencha Touch," escreveu Bansod. "Elas embutem um navegador web em aplicações nativas, o que permite a distribuição de aplicações web através das App Stores nativas. WebViews estão presentes em todos os sistemas operacionais mobile modernos."

Até o iOS 5.1, aplicações WebView podiam armazenar dados localmente e persistí-los usando o armazenamento do HTML5. "Especificamente, se sua aplicação usava LocalStorage ou WebSQL, isso era considerado parte dos dados da aplicação," diz Bansod. Se uma nova versão da aplicação fosse lançada, os dados ainda estariam presentes.

Este não é mais o caso. "É possível que isso se dê porque a Apple não conseguiu fazer de forma confiável a sincronização de dados pelo iCloud quando esses dados não estão armazenados no sistema de armazenamento nativo do iOS." especula Bansod. Um desenvolvedor disse no forum do Phonegap que ele foi informado por alguém da Apple que a razão para a mudança era "para economizar espaço, pois aplicações carregando muito conteúdo numa UIWebView (como o Twitter) ocuparia espaço demais [na sincronização com o serviço iCloud da Apple]... Mas eles esqueceram completamente de nós, pobres desenvolvedores Phonegap, que contavam com o LocalStorage ou o WebSQL para armazenar os dados dos usuários."

"Para os desenvolvedores que contavam com o LocalStorage ou o WebSQL como mecanismo para armazenar dados de suas aplicações, interrompê-lo é um grande problema," disse Bansod em seu post. Não é impeditivo: "Há vários meios de contornar o problema, como usar o SQLPlugin do PhoneGap que usa o SQLite padrão, or escrever seu próprio JavaScript para acessar diretamente o CoreData do iOS." Para muitos, afirma ele, isso significa recodificar suas aplicações.

De fato, aplicações que não forem alteradas "esquecerão" os dados. Os usuários também podem perder dados já que aplicações nas quais eles costumavam armazenar dados relevantes, de repente deixaram de fazer esse armazenamento, por exemplo.

Ao menos alguns desenvolvedores têm a esperança de que esta mudança seja apenas um bug que a Apple corrigirá. Em 7 de Março, com o anúncio do novo iPad e o iOS 5.1, eles descobriram que a Apple os colocou num novo território. "Eles fizeram isto. A Apple lançou o programa com aquele bug. Eu já tenho usuários furiosos porque perderam seus trabalhos com minha aplicação. :-/," postou Sam no forum do Phonegap.

As formas de contornar o problema não são simples, como se vê ao acompanhar as discussões para um plugin do Phonegap, criado por Shazron Abdullah na Apache Software Foundation.

Sem avanços nos recursos do HTML5
A avaliação da Sencha também revelou a ausência de quaisquer novas funções HTML5 no iOS 5.1 e na novíssima versão mobile do navegador Safari da Apple. "Nenhum novo recurso apareceu entre as versões iOS 5.0 e iOS 5.1," escreveu. "O iOS ainda apresenta um dos melhores suportes ao HTML5 entre os navegadores mobile, mas esta última encarnação não aprofundou o suporte do Safari aos padrões."

O Safari 6 no Mac, por exemplo, suporta um recurso chamado Regiões de Cascading Style Sheets (CSS), um modo simples de criar e modificar o layout de revistas digitais. Mas está faltando na versão atual do Safari para dispositivos com iOS 5.1.

"Queríamos ver também se o WebGL [uma API JavaScript para desenhar gráficos em 3D sem a necessidade de um plugin], que atualmente é suportado somente no Apple iAds, está disponível no navegador," Bansod escreveu. "haz.io [um site que avalia se seu navegador suporta padrões web emergentes] reporta que o WebGL é suportado pela versão mobile do Safari, mas, quando usamos o repositório de exemplos Khronos para testar, foi impossível fazer com que algum dos exemplos funcionasse."

Performance web
Para avaliar a performance web do novo iPad, a Sencha executou um conjunto de testes comparativos específicos da web usando um novo iPad, um iPad 2 (ambos com iOS 5.1), um tablet Motorola Xoom com Android 3.0, e um RIM PlayBook com Tablet OS 1.0. A equipe de Bansod executou o teste SunSpider e o V8 Benchmark Suite para medir o poder de processamento de códigos JavaScript.

Como é sabido, o novo iPad usa uma versão do chip dual-core A5 da Apple, com um novo processador gráfico quadcore.

No geral, o novo iPad (apelidado "Retina iPad" nos resultados dos testes da Sencha) foi um pouco mais lento em 6 dos 9 testes SunSpider. Nos sete testes do V8, o novo iPad empatou com o iPad 2 mas ambos perderam do tablet da Motorola.

Segundo Bansod, em muito da navegação pela internet, os usuários do novo iPad não verão problemas. Mas a diferença entre os dois iPads é perceptível quando se trata de desenhar páginas complexas. Por exemplo, o novo iPad estava visivelmente carregando novos blocos na parte de baixo de uma página de exemplo enquanto a página era rolada, coisa que raramente ocorria com o iPad 2.

Ele especula que uma razão para o patamar de desempenho do novo iPad é que a Apple acrescentou o processador gráfico quad-core e mais memória, mas não tornaram a memória mais rápida. Isso significaria, diz ele, que jogar imagens e outros recursos gráficos para a GPU está consumindo mais tempo e banda do que o dispositivo pode manipular em tempo real.

A matéria original em inglês pode ser acessada neste link.

31 de janeiro de 2012

Informações sobre rotas com a versão 3 da API do Google Maps

No ano passado eu publiquei dois posts mostrando como traçar rotas usando a versão 3 da API do Google Maps. O primeiro tratava de um cenário básico onde consideramos um endereço de origem e outro de destino; o segundo avançava um passo ao permitir a inclusão de um ou mais pontos intermediários ao trajeto. Por razões distintas, várias pessoas me perguntaram como é possível obter a distância total entre a origem e o destino em uma rota, independendo se vamos efetivamente exibir a rota traçada.

Pra refrescar a memória, o quadro a seguir ilustra os comandos e estruturas básicos necessários pra trabalharmos com uma rota.
var directionsService, directionsRenderer;
var enderDe, enderAte;

directionsService = new google.maps.DirectionsService();
directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);

enderDe = 'ALAMEDA SANTOS, 1000, SÃO PAULO - SP, 01418-9028';
enderAte =
'AVENIDA NAÇÕES UNIDAS, 17-17, BAURU - SP, 17013-035';

var request = {
origin:endDe,
destination:endPara,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};

directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsRenderer.setDirections(response);
}
});)

A linha directionsRenderer.setDirections (response) usa o objeto response para traçar automaticamente a rota no mapa. Esse objeto é do tipo DirectionsResult e é montado pela função route antes de ser repassado como response à nossa função de callback. No entanto, você pode usar manualmente as informações contidas em response sem que a rota precise ser exibida.

De acordo com a documentação, o DirectionsResult possui como única propriedade um Array chamado routes contendo a lista de rotas encontradas pelo serviço para atender a requisição feita. Cada elemento dessa lista traz detalhes de uma rota específica, descrita na forma de um objeto DirectionsRoute, o que inclui detalhes de cada trecho do trajeto, como a distância de cada um (em metros) e o tempo estimado usando o tipo de locomoção escolhido.

Com isso, podemos facilmente obter a distância a ser percorrida para chegar ao destino usando a rota em questão:
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distancia;
var rota = response.routes[0]; /* Primeira rota */
var etapa = rota.legs[0]; /* única etapa dessa rota */

distancia = etapa.distance.value;

alert ('Distância Total => ' + distancia.toString() + ' metros.');
}
});)

A propriedade legs destrincha cada etapa do trajeto usando objetos do tipo DirectionsLeg para descrevê-las. Por etapa entende-se o deslocamento necessário entre dois endereços. Assim, se você estipulou apenas a origem e o destino, haverá uma única etapa e, por conseguinte, uma única posição em legs. Por outro lado, se você adicionou pontos de referência usando o waypoints na requisição (veja esse post para mais detalhes), haverá uma nova etapa descrevendo como chegar a cada ponto. Nesse caso, será preciso percorrer todas as posições, somando a distância de cada etapa para se obter a distância total da rota:
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distancia = 0;
var rota = response.routes[0]; /* Primeira rota */
var etapa, i;

for (i = 0; i < rota.legs.length; i++)
{
/* Obtem cada etapa */
etapa = rota.legs[i];
distancia += etapa.distance.value;
}

alert ('Distância Total => ' + distancia.toString() + ' metros.');
}
});)

O detalhamento do caminho a ser seguido dentro de cada etapa do trajeto pode ser acessado através da propriedade steps. Cada passo também possui sua distância mas a soma delas já está consolidada na própria etapa (leg). Tenha em mente que, para certos trajetos, pode acontecer de as informações de distância e duração não estarem disponíveis. Por questão de simplicidade, os scripts acima não levam isso em conta. Um exemplo completo dos recursos mostrados até agora pode ser visto na página abaixo, cujo código fonte está neste link:

Um outro ponto importante a ser lembrado é que a função route é assíncrona. Isto significa que a função de callback que passamos a ela poderá ser executada depois da linha de código imediatamente seguinte ao route. Assim, se tiver que comparar as distâncias de várias rotas, terá que montar uma forma de sincronizar os cálculos para que a comparação só ocorra depois da execução de todos os callbacks envolvidos. Esse efeito normalmente é conseguido através da função javascript setinterval, verificando a cada segundo, por exemplo, se as rotas já estão prontas.

Mais Informações
Traçando rotas com a versão 3 da API do Google Maps - Parte I e Parte II, Google Maps JavaScript API V3

21 de outubro de 2011

Trabalhando com a tag Canvas do HTML5 - parte IV

Até agora , nos posts que escrevi sobre o uso do canvas do HTML5, quando precisei me referir a uma cor nos scripts de exemplo, eu sempre usei a representação tradicional para cores estabelecida pelo CSS (Cascading Style Sheet). Isto é, as cores sempre foram especificadas como uma mistura da intensidade de vermelho, verde e azul (o código RGB) que resultasse na cor desejada. Neste post, mostro que o Canvas permite aplicar um grau de transparência às cores e também trabalhar com gradientes de cores.

As regras para cores no canvas são as mesmas definidas para o CSS. Portanto, podemos usar aqui também a função rgba(), que introduz um quarto parâmetro para controlar o nível de transparência de cores. Os 3 primeiros parâmetros são os conhecidos níveis de intensidade do vermelho, verde e azul (representados por números de 0 a 255). O último valor – chamado de alpha – corresponde ao grau de transparência a ser aplicado aos desenhos subsequentes. Ele pode assumir valores entre 0 (completamente transparente) e 1 (desenha a imagem opaca, sobrepondo totalmente o fundo, sendo esse o efeito padrão obtido até aqui).
var myCanvas = document.getElementById("cnvCores2");
var ctx = myCanvas.getContext("2d");
var incrY = myCanvas.height / 10;
var grausFin = 360;
var radFin = (Math.PI/180)* grausFin;
var meioCompr = myCanvas.width / 2;

ctx.font = "11px Arial";

/* Primeiro, preenche com cor do fundo opaca */
ctx.fillStyle = 'rgb(20,230,20)';
ctx.fillRect(0, 0, meioCompr, myCanvas.height / 2);
ctx.fillRect(meioCompr, myCanvas.height / 2, meioCompr, myCanvas.height / 2);

for (var i = 0; i < 10;i++) { /* agora, pinta por cima usando branco mas variando a transparência */
ctx.fillStyle = 'rgba(255,255,255,' + (1.0 - (i / 10.0)) + ')';
ctx.fillRect(0, i*incrY, meioCompr, incrY);

ctx.fillStyle = "navy";
ctx.fillText (Math.ceil (10.0 - i) / 10.0, 8, (i+1)*incrY - 4);
}

/* preenche no centro um círculo marrom com alto grau de transparência */
ctx.beginPath();
ctx.fillStyle = 'rgba(128,0,0,0.4)';
ctx.arc(meioCompr, myCanvas.height / 4, meioCompr, 0, radFin, false);
ctx.fill();
ctx.closePath();

/* Como seria sem o parâmetro de transparência */
ctx.beginPath();
ctx.fillStyle = 'rgb(128,0,0)';
ctx.arc(meioCompr, 3 * myCanvas.height / 4, meioCompr, 0, radFin, false);
ctx.fill();
ctx.closePath();
Seu navegador (ou leitor de RSS) não suporta canvas ...
Parte do código é dedicada a escrever o grau de transparência de cada uma das faixas horizontais. Como o desenho de texto também é afetado pelo valor de fillStyle - incluindo a transparência configurada nele -, eu atribuo a ela uma outra cor sólida para que o texto esteja plenamente visível, sem qualquer interferência do fundo previamente desenhado.

Há uma propriedade do contexto de pintura chamada globalAlpha que permite mudar o grau de transparência dos desenhos, influenciando preenchimentos e contornos num único comando. Essa característica tem sua utilidade, mas é menos flexível do que controlar separadamente pelo tipo de desenho.

Uma outra forma de trabalhar com cores prevista no canvas do HTML5 é o preenchimento de áreas com um gradiente, isto é, a pintura é iniciada com uma cor e vai gradualmente se aproximando de uma segunda cor, que é finalmente usada para concluir a pintura. Estão previstos dois tipos de gradiente : um linear e outro radial. Para usar um deles, basta criar o tipo desejado, alimentar as cores que comporão o gradiente e então associá-lo à propriedade fillStyle do contexto do Canvas, exatamente como fizemos com cores e transparências.

O tipo linear é instanciado através da função createLinearGradient enquanto para o gradiente radial usamos createRadialGradient. Ambos os métodos criam objetos que respeitam a especificação da interface CanvasGradient, atualmente composta por apenas uma função cujo objetivo é adicionar pontos de parada para as cores no gradiente. Ou seja, essa função define quantas cores básicas haverão no gradiente resultante e quanto espaço cada uma ocupará nele. As cores intermediárias são calculadas automaticamente de modo a preencher toda a área definida pela função de criação.
/* Gradiente linear simples com 2 cores: uma vai do início do retângulo até o meio e a outra do meio até o fim */
var lineargradient = ctx.createLinearGradient(0,0,120,120);
lineargradient.addColorStop (0, 'navy');
lineargradient.addColorStop (0.5, 'rgb(50,50,180)');
lineargradient.addColorStop (1, 'rgb(120,120,220)');

/* Preenche o retângulo real com o padrão gradiente criado acima */
ctx.fillStyle = lineargradient
ctx.fillRect (0,0,180,180);

/* Outro gradiente, agora com mais cores ... */
lineargradient = ctx.createLinearGradient(0,200,180,280);
lineargradient.addColorStop (0, 'navy');
lineargradient.addColorStop (0.2, 'red');
lineargradient.addColorStop (0.8, 'rgb(50,50,180)');
lineargradient.addColorStop (1, 'green');

ctx.fillStyle = lineargradient
ctx.fillRect (0,200,180, 280);
Seu navegador (ou leitor de RSS) não suporta canvas ...
A função createLinearGradient cria uma área retangular - definida por pontos que indicam o canto superior esquerdo e o inferior direito. O gradiente é criado nesse retângulo virtual e ele então é usado para preencher o retângulo real no canvas. Como o retângulo real pode ter tamanho diferente daquele usado para criar o gradiente, o canvas faz alguns ajustes para que caiba. Se o retângulo real for maior, a última cor de parada é usada para preencher o resto da área.

Veja que para adicionarmos uma cor de parada temos que informar um valor entre 0 e 1. Esse valor é chamado de offset e indica a que distância do início do retângulo a nova cor deve começar a aparecer. Uma cor prevalece até que a próxima cor de parada seja encontrada. Então, o tanto de espaço ocupado por cada uma é definido pelo offset da parada seguinte.

Para o gradiente radial, a principal diferença é que, ao invés do efeito ser aplicado num retângulo, ele é aplicado numa área delimitada por dois círculos Os círculos são definidos na criação do gradiente, sendo que os 3 primeiros parâmetros correspondem ao ponto central e raio do círculo inicial e os 3 parâmetros seguintes correspondem ao ponto central e raio do círculo final. A área fora dos círculos é mantida com a cor original da área preenchida.
/* Círculo com gradiente */ var radgrad = ctx.createRadialGradient (105, 115, 20, 112, 130, 50);
radgrad.addColorStop(0, '#FF5F98');
radgrad.addColorStop(0.75, '#FF0188');
radgrad.addColorStop(1, 'rgba(255,1,136,0)');
/* Ao preencher retângulo, somente a área com o gradiente circular é considerada !*/
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,250,500);

/* Outro círculo com gradiente */
radgrad = ctx.createRadialGradient(95,35,15,102,40,40);
radgrad.addColorStop(0, '#00C9FF');
radgrad.addColorStop(0.8, '#00B5E2');
radgrad.addColorStop(1, 'rgba(0,201,255,0)');
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,250,500);

/* Aqui , a área circular resultante é maior que o retângulo; então, o retângulo todo é preenchido */
radgrad = ctx.createRadialGradient (200, 350, 65, 200, 350, 140);
radgrad.addColorStop (0, 'navy');
radgrad.addColorStop (0.5, 'rgb(50,50,180)');
radgrad.addColorStop (1, 'rgb(120,120,220)');
ctx.fillStyle = radgrad
ctx.fillRect (50,200,150,150);
Seu navegador (ou leitor de RSS) não suporta canvas ...
Parte do exemplo acima foi retirada do tutorial de canvas da Fundação Mozilla. Veja que a criação do gradiente aceita a função rgba(), significando que podemos aplicar transparência neste momento também.

Mais Informações
Trabalhando com a tag Canvas do HTML5 - parte I, parte II e parte III, Tutorial de Canvas na Fundação Mozilla

23 de setembro de 2011

Trabalhando com a tag Canvas do HTML5 - parte III

Dando continuidade aos posts sobre uso da tag Canvas (veja parte I e parte II), falo aqui sobre o uso de textos no canvas e a manipulação de imagens.

As propriedades básicas disponíveis para formatarmos a exibição de textos no Canvas são o tipo de fonte e a cor para o desenho. O tipo do fonte pode ser modificado através da propriedade font. Como a interpretação do valor contido nessa propriedade é idêntica ao de folhas de estilo CSS, podemos configurar com ela a família do fonte (Arial, Tahoma, Courier, etc.), o tamanho das letras e estilos especiais (negrito, itálico, etc.).

Quando trabalhamos com figuras geométricas no outro post, vimos que há duas formas de comandar um desenho : traçando apenas a borda ou preenchendo também o interior da figura, sendo que as cores para um ou outro são configuradas em propriedades separadas. O mesmo vale para o desenho de textos.

Assim, a propriedade fillStyle conterá a cor para preencher o texto quando este for desenhado usando a função fillText. Por outro lado, a propriedade strokeStyle define a cor das bordas do texto vazado a ser desenhado usando a função strokeText. Ambas as propriedades aceitam valores que estejam em conformidade com as regras de definição de cores estabelecidas pelo padrão CSS. Veja um exemplo :
var myCanvas = document.getElementById("cnvText2");
var ctx = myCanvas.getContext("2d");

ctx.fillText ("Fonte e cores padrões", 5, 20);

ctx.font = "bold 16px Verdana";
ctx.fillStyle = "blue";
ctx.fillText ("Verdana 16px negrito", 5, 40);

ctx.font = "20px Courier";
ctx.strokeStyle = "#0FAF0F";
ctx.strokeText ("Courier 20px stroke", 5, 65);

ctx.font = "36px Arial";
ctx.strokeStyle = "maroon";
ctx.strokeText ("Arial 36px stroke", 5, 100);
Seu navegador (ou leitor de RSS) não suporta canvas ...

Usando fontes pequenas, o efeito de desenhar somente a borda acaba diluído, dando a impressão que o texto está todo preenchido. Portanto, ele funciona melhor com fontes maiores.

Conforme vemos no quadro anterior, as funções de desenho de texto aceitam 3 parâmetros : o próprio texto que será escrito e as coordenadas da posição no canvas onde ele deve ser renderizado. Normalmente, a posição indica o canto superior esquerdo de um retângulo virtual que conterá o texto. O significado dessa posição, no entanto, pode variar de acordo com o valor das propriedades textAlign e textBaseline. A primeira altera a interpretação do posicionamento horizontal do texto enquanto a segunda controla a coordenada vertical. Não é muito comum ter que alterar esses valores pois, no geral, os valores originais dão conta do recado.

Poderíamos ainda ter passado como 4o parâmetro o comprimento máximo que o texto deve ocupar - caso o tamanho real exceda esse valor, o tamanho do fonte é alterado automaticamente para que o texto caiba.

Um outro aspecto para incrementar a renderização de textos é a criação do efeito de sombra. Essa característica é controlada pelas propriedades shadowColor - que corresponde à cor da sombra - e o par shadowOffsetX e shadowOffsetY - respectivamente, o deslocamento horizontal e vertical da sombra a ser projetada em relação ao texto. É possível também esfumaçar a sombra, deixando o efeito mais sutil. Consegue-se isso usando a propriedade shadowBlur, cujo valor indica o quão borrada deve ser a sombra projetada. Seguem alguns exemplos:
ctx.font = "18pt Arial";
ctx.fillStyle = "#0F0FA0";
ctx.shadowColor = "gray";
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillText ("Texto com sombra comum", 5, 40);

ctx.fillStyle = "#7F0FA0";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 2;
ctx.fillText ("Texto com sombra borrada", 5, 70);

ctx.fillStyle = "green";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 8;
ctx.fillText ("Sombra muito borrada", 5, 100);
Seu navegador (ou leitor de RSS) não suporta canvas ...

De acordo com a padronização da W3C, para desenharmos uma imagem no canvas devemos usar a função drawImage. Com ela, podemos transferir para o canvas uma imagem inteira ou uma parte retangular dela. Também podemos modificar a proporção da imagem renderizada no canvas, indicando se ela é cópia exata da imagem original ou se será ajustada para um novo comprimento e altura. Neste link publicado pela W3C há uma explicação e um gráfico que ilustram o funcionamento dos parâmetros envolvidos na cópia da imagem original para o Canvas.

Em sua versão mais simples, o drawImage aceita como parâmetros a imagem em si e a coordenada do canto superior esquerdo no canvas a partir de onde a imagem será renderizada. Neste contexto, uma imagem pode ser tanto uma tag IMG quanto um outro canvas ou até mesmo a tag VIDEO, introduzida no HTML5. No caso da IMG, podemos recuperar uma referência à imagem usando a função getElementById do documento HTML (se a imagem já estiver embutida na página HTML) ou criar uma nova referência do zero, instanciando o objeto Image:
var img = new Image();
img.onload = function () {
ctx.drawImage (img, 10, 10);
ctx.drawImage (img, 110, 10, 70, 110);
ctx.drawImage (img, 10, 15, 45, 60, 220, 20, 45, 60);
}
img.src = '40anos.gif';

ctx.font = "10pt Tahoma";
ctx.fillText ("Normal", 30, 130);
ctx.fillText ("Novo tamanho", 110, 130);
ctx.fillText ("Cópia parcial",210, 130);
Seu navegador (ou leitor de RSS) não suporta canvas ...
É importante ressaltar que a carga da imagem é disparada de modo assíncrono, logo após a URL para a imagem ter sido informada. Isto significa que o script continua a executar enquanto o navegador localiza e carrega a imagem. Por isso, no script acima a imagem é desenhada no canvas dentro do evento onload da tag da imagem. Ou seja, o desenho só é feito quando efetivamente a imagem está disponível.

As propriedades para sombreamento de texto discutidas neste post também são aplicáveis ao desenho de imagens. Assim, podemos incrementar o resultado adicionando esse efeito conforme a necessidade:
var img = new Image();
img.onload = function () {
/* Remove o recurso de sombreamento para a 1a imagem */
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
ctx.drawImage (img, 10, 10);

/* Aplica o recurso de sombreamento para as demais imagens */
ctx.shadowColor = "#2D90E0";
ctx.shadowOffsetX = 7;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 8;

/* Copia a imagem completa para um local no canvas */
ctx.drawImage (img, 110, 10);
/* Copia parte da imagem para um local no canvas */
ctx.drawImage (img, 10, 15, 45, 60, 220, 20, 45, 60);
}
img.src = '40anos.gif';

/* Para os textos, remove os efeitos de sombreamento */
ctx.font = "10pt Tahoma";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
ctx.fillText ("Normal", 30, 130);
ctx.fillText ("Com Sombra", 110, 130);
ctx.fillText ("Cópia parcial", 210, 123);
ctx.fillText ("com sombra", 210, 137);
Seu navegador (ou leitor de RSS) não suporta canvas ...
Note pelo exemplo anterior que mesmo na cópia de apenas parte de uma imagem o efeito de sombreamento se aplica.

Em outra oportunidade mostrarei mais recursos do canvas associado a cores e imagens (como rotação e criação de gradientes) e como misturar isso tudo.

6 de setembro de 2011

Trabalhando com a tag Canvas do HTML5 - parte II

No último post, apresentei o funcionamento básico da tag Canvas do HMTL 5, sua inserção numa página HTML e código JavaScript simples para traçar linhas na área do Canvas. Avançando nesse assunto, gostaria de apresentar aqui outras funções de desenho e como lidar com cores.

Além de linhas, a lista de funções para traçar figuras geométricas básicas inclui também funções para criar retângulos e arcos. No caso dos retângulos, há 2 funções : uma para desenhar apenas a borda, sem preencher o interior - strokeRect - e outra para desenhá-lo preenchido - fillRect.
/* ... */
ctx.beginPath();
ctx.fillRect (10,10,60,100);
ctx.strokeRect (75,10,60,100);
ctx.clearRect (30, 50, 25, 25);
ctx.closePath();

var grausFin = 360;
var radFin = (Math.PI/180) * grausFin;

ctx.beginPath();
ctx.arc(180, myCanvas.height / 2, 25, 0, radFin, false);
ctx.stroke();
ctx.closePath();

ctx.beginPath();
ctx.arc(230, myCanvas.height / 2, 25, 0, radFin, false);
ctx.fill();
ctx.closePath();
Seu navegador não suporta canvas ...

Já a função clearRect que aparece no código acima serve para limpar uma área retangular do canvas, tornando-a transparente. Isto é, a área é totalmente preenchida com a cor de fundo da tag onde o Canvas está inserido, substituindo os pixels contidos na área. A utilidade desse recurso está em preparar o canvas para receber novos desenhos sem sofrer interferência daqueles que existiam anteriormente.

Os parâmetros das funções de retângulo são sempre o ponto superior esquerdo (x, y) seu comprimento e sua altura.

Como mostra o exemplo anterior, o desenho de um círculo é feito através da função arc. O que determina se ele será preenchido ou não é a função chamada depois : use fill para preenchê-lo ou stroke para desenhar apenas o seu contorno.

Os parâmetros da função para desenhar arco são o ponto central (x, y), seu raio, o ângulo na circunferência onde o desenho será iniciado, o ângulo final e um booleano indicando se o desenho será feito no sentido anti-horário. Como diz o nome da função, ela traça um arco entre o ângulo inicial e final, formando parte de um círculo com centro em (x, y) e o raio dado. Isso implica que temos que informar os ângulos 0 (zero) e 2pi se quisermos desenhar um círculo completo. Observe que os ângulos são informados em radianos e não em graus. A fórmula (PI * 180 / graus) que aparece no exemplo faz a conversão de graus para radianos.

As cores aplicadas em linhas e nos preenchimentos são controladas de forma independente entre si. Ao traçar linhas – aí incluindo a função para desenhar somente as bordas de um retângulo ou arco – a cor é ditada pela propriedade strokeStyle do canvas, enquanto para fazer qualquer preenchimento, a cor usada é aquela atribuída ao fillStyle.
/* ... */
ctx.beginPath();
ctx.fillStyle = "rgb(45,80,150)";
ctx.strokeStyle = "maroon";
ctx.fillRect (10,10,60,100);
ctx.strokeRect (75,10,60,100);
ctx.closePath();

var grausFin = 360;
var radFin = (Math.PI/180)* grausFin;

ctx.fillStyle = "#FFA500";
ctx.strokeStyle = "green";

ctx.beginPath();

ctx.arc(180, myCanvas.height / 2, 25, 0, radFin, false);
ctx.stroke();
ctx.closePath();

ctx.beginPath();
ctx.arc(230, myCanvas.height / 2, 25, 0, radFin, false);
ctx.fill();
ctx.closePath();
Seu navegador não suporta canvas ...
No exemplo acima, utilizei cores especificando diretamente seu nome (com em “green”) ou sua composição RGB (como em "rgb(45,80,150)"). Também poderia ter usado um código hexadecimal (como em "#FFA500"). Ou seja: são os mesmos mecanismos aceitos pelo CSS3.

Um outro aspecto importante para traçar linhas e desenhar bordas é a espessura que a linha deve ter. Podemos modificar o valor contido na propriedade lineWidth do canvas para obter linhas mais grossas. O valor padrão dela que corresponde a 1 unidade, sendo que ela aceita valores positivos.
/* ... */
var angIni = 3 * Math.PI / 2;
var angFin = Math.PI / 2, x, i;

/* Linhas e arcos */
x = 5;
for (i = 0; i < 10; i ++)
{
ctx.lineWidth = i + 1;
ctx.lineCap = "round";

ctx.beginPath();
ctx.moveTo (x, 5);
ctx.lineTo (x, 35);
ctx.stroke();
ctx.closePath();

ctx.beginPath();
ctx.arc(x, 75, 20, angIni, angFin, false);
ctx.stroke();
ctx.closePath();

x = x + 15;
};

/* Bordas de retângulos */
ctx.beginPath();
ctx.lineWidth = 8;
ctx.strokeRect(180, 5, 100, 100);
ctx.lineWidth = 6;
ctx.strokeRect(190, 15, 80, 80);
ctx.lineWidth = 4;
ctx.strokeRect(200, 25, 60, 60);
ctx.lineWidth = 2;
ctx.strokeRect(210, 35, 40, 40);
ctx.closePath();
Seu navegador não suporta canvas ...

No exemplo acima, modifiquei também a propriedade lineCap, que determina como o desenho de linhas será arrematado. Ao usar o valor "round", instruo o canvas a arredondar as pontas das linhas ao invés de deixá-las retas, como no comportamento padrão.

No próximo post, falo sobre uso de textos, imagens e como modificar o modo que os desenhos aplicados ao canvas interagem entre si, criando composições entre eles.

29 de agosto de 2011

Trabalhando com a tag Canvas do HTML5 - parte I

Dentre as inovações introduzidas pelo HTML5, certamente a que causou maior impacto foi a adoção da tag Canvas. A discussão em torno desse recurso acabou ampliada pelo potencial que ele tem de eliminar a necessidade de outras tecnologias bem estabelecidas, como o Adobe Flash, usado para criar animações em páginas da Internet.

Mas, o que é a tag Canvas, como ela funciona e pra que exatamente ela serve ? A grosso modo, essa tag cria uma área retangular dentro de uma página HTML onde é possível desenhar livremente. Ela faz isso franqueando ao desenvolvedor acesso aos pontos (pixels) que compõem a área, o que pode ser combinado com eventos para criar animações. É um comportamento análogo ao disponível através da classe TCanvas que há no Delphi. Tal analogia é reforçada pelo fato que a tag Canvas em si apenas demarca a região na página HTML, sendo que quaisquer desenhos ou interações que se queira implementar têm que ser obtidos através de programação, usando os métodos e propriedades disponibilizados para uso com JavaScript.

Para começar, então, devemos incluir no corpo do texto HTML a marcação do lugar onde o Canvas será renderizado, exatamente do mesmo modo que fazemos com outras tags. O exemplo abaixo cria uma página HTML que possui apenas um Canvas, com comprimento de 400 pontos e altura de 200 pontos:
<body>
<canvas id="myCanvas" width="400" height="200"
style="border:1px solid #c3c3c3;">
Seu navegador não suporta canvas ...
</canvas>
/* ... */
</body>
O texto inserido na tag será exibido apenas se a página for aberta por um navegador que não implementa o recurso de Canvas. Neste caso, para obter um resultado mais profissional, o texto pode ser substituído por um conteúdo HTML apropriado - como uma imagem, por exemplo.

Daqui em diante, todo o trabalho em cima do Canvas tem que ser feito por JavasScript. Primeiro, precisamos recuperar o contexto para desenho pois é nele que o conteúdo apresentado pelo Canvas é criado e manipulado:
function desenha () {
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
/* ... */
}

Todas as funções disponíveis no contexto que recuperamos deverão informar coordenadas, isto é, pares de valores (x,y) para se referir a um ponto dentro do plano delimitado pela área do Canvas. A diferença em relação a um plano cartesiano é que no canvas o canto superior esquerdo é sempre (0,0), com os valores de X crescendo para a direita e os de Y crescendo para baixo, como mostra essa imagem da fundação Mozilla :
Coordenadas na tag Canvas

O exemplo abaixo é bastante básico : move a posição atual do cursor de desenho para o centro do Canvas, traça uma linha até o canto inferior esquerdo, outra até o canto inferior direito e uma terceira até o ponto original. Por fim, a figura fechada é preenchida com a cor corrente :
/* ... */
ctx.beginPath ();

ctx.moveTo (myCanvas.width / 2,
myCanvas.height / 2);
ctx.lineTo (0,
myCanvas.height);
ctx.lineTo (myCanvas.width, myCanvas.height);
ctx.lineTo (myCanvas.width / 2,
myCanvas.height / 2);

ctx.stroke();
ctx.fill();

ctx.closePath();
/* ... */
Seu navegador não suporta canvas ...
Vamos por partes ! As funções beginPath() e closePath() do contexto delimitam passos que constituirão um único desenho para o Canvas, não importando quantos passos são necessários para montá-lo. Com isso, os passos entre essas funções compartilham estilos para traçar linhas e preencher áreas, por exemplo. Então, se há partes com cores diferentes, elas deverão ser traçadas como desenhos diferentes dentro do Canvas.

No exemplo, o primeiro passo é uma chamada à função moveTo(). Ela em si não faz qualquer desenho, servindo apenas para estabelecer um ponto de partida para passos seguintes. Ou seja, é o ponto onde o pincel primeiro tocará a área de pintura. Assim, ao chamarmos a função lineTo(), traçamos uma linha reta a partir daquele ponto até o ponto indicado nos parâmetros do lineTo. Esse novo ponto, então, passa a ser a posição atual do pincel. Se fosse preciso, poderíamos mover novamente sua posição para qualquer outro ponto sem afetar visualmente o canvas.

A função stroke() efetiva todos os comandos dados antes, forçando as linhas a serem traçadas com os estilos estabelecidos. Como não comandamos nada diferente, o canvas usará o estilo padrão de cor, espessura e tracejado.

Para encerrar, a função fill() é chamada para preencher a área interna da figura - também usando estilos padronizados.

Há muitas outras funções para desenhar retângulos, arcos e até curvas mais elaboradas, além da capacidade de incluir textos e imagens externas, o que permite combinar vários recursos para compor figuras bastante complexas. Em outro post, mostra mais desses recursos.

Os exemplos mostrados neste post usam o contexto para desenhos 2D mas já há uma versão experimental de contexto 3D, baseado no WebGL. Veja um exemplo de uso neste link.

18 de abril de 2011

Criando um editor de HTML com o TWebBrowser - parte III

Um problema clássico enfrentado por quem cria um editor HTML com o TWebBrowser está em como relatar ao usuário informações a respeito das formatações existentes no ponto do editor onde o cursor está atualmente ou sobre alterações introduzidas no documento editado. Isto porque o componente TWebBrowser não disponibiliza um evento apropriado que possa ser interceptado para exibir tais informações. Não há, por exemplo, eventos específicos para mudanças do documento ou que reflita mudanças na posição atual do cursor.

O problema, portanto, pode ser desmembrado em dois: como descobrir que houve uma mudança no texto ou que houve uma alteração na posição do cursor e como recuperar a formatação existente na posição atual do cursor.

Para a primeira questão, podemos usar um evento definido na interface IHtmlDocument2. Apenas para relembrar: a propriedade Document do TWebBrowser implementa essa interface, bastando fazer um cast para ter acesso às propriedades, métodos e eventos dela:
function TForm1.DOMInterface: IHtmlDocument2;
begin
Result := WebBrowser1.Document As IHtmlDocument2;
end;

Um bom evento do IHtmlDocument2 para obtermos notificações das manutenções que precisamos é o onkeydown. Ele é disparado sempre que uma tecla é pressionada quando o componente está com o foco, não importa se é uma letra, número ou tecla de controle. Portanto, isso inclui as setas de navegação, page-up/page-down, as combinações de tecla, etc.

Para recebermos as notificações, temos que alimentar a propriedade onkeydown com a instância de uma classe que implemente a interface básica IDispatch. Quando o evento ocorre, a função Invoke definida por esta interface é executada, permitindo-nos particularizar uma resposta ao evento. Conforme eu disse no post sobre uso de interfaces em Delphi, podemos criar uma herança de TInterfacedObject e implementar nela os métodos introduzidos pela IDispatch:
TWMSHTMLEvent = procedure(Sender: TObject; Event: IHTMLEventObj) of object;
TWMSHTMLEventConnector = class(TInterfacedObject, IDispatch)
private
FDoc: IHtmlDocument2;
FOnEvent: TMSHTMLEvent;

{ Métodos do IDispatch }
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;

public
constructor Create(ADoc: IHtmlDocument2; Handler: TWMSHTMLEvent);
end;

{ ... }

function TWMSHTMLEventConnector.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
begin
Result := S_OK;

if Assigned(FOnEvent) then
FOnEvent(Self, FDoc.parentWindow.event);
end;

No trecho de código acima, criei também um tipo chamado TWMSHTMLEvent para representar um evento externo. Este tipo enviará como parâmetros o Sender (componente que gerou o evento) e uma instância de IHTMLEventObj, interface que traz as informações de um evento HTML, tais como o código da tecla pressionada e o elemento HTML existente no ponto onde o evento ocorreu.

Embora sejam obrigatórias para o IDispatch, as demais funções não são relevantes no contexto de um evento HTML, podendo ser implementadas apenas ajustando o valor de retorno como E_NOTIMPL.

Com a classe implementadora do IDispatch definida como mostrado acima, podemos manter a resposta ao evento HTML dentro do próprio Form onde o TWebBrowser está inserido, e, portanto, mantendo separadas as responsabilidades de cada classe. Note também que o Document é passado no construtor. Precisamos dele para obter as informações do último evento ocorrido, dado que é repassado para o Form através do parâmetro que criamos no nosso próprio evento.

Na verdade, nossa classe pode ser utilizada para responder a qualquer um dos eventos definidos em IHtmlDocument2, incluindo aqueles associados aos movimentos do mouse e a cliques em qualquer ponto da página HTML.

E qual é o melhor lugar para fazer a associação entre a instância de nossa classe e o evento onkeydown ? Nós só teremos um Document montado depois que ao menos uma página HTML tenha sido completamente carregada. Portanto, o evento OnNavigateComplete2 do componente TWebBrowser é o local mais apropriado:
procedure TForm1.FormCreate(Sender: TObject);
begin
_KeyDownEvt := Nil;
WebBrowser1.Navigate('http://balaiotecnologico.blogspot.com/');
end;

procedure TForm1.WebBrowser1NavigateComplete2(ASender: TObject; const pDisp: IDispatch; var URL: OleVariant);
begin
if (_KeyDownEvt = Nil) then
_KeyDownEvt :=
TWMSHTMLEventConnector.Create (DomInterface, DoOnWebKeyDown);
DomInterface.onkeydown := _KeyDownEvt As IDispatch;
end;

Veja que a criação da instância da classe TWMSHTMLEventConnector pra conectar os eventos ocorre um única vez. Já a atribuição ao onkeydown acontece toda vez que uma nova página for carregada. Isso se dá por que o documento é criado uma única vez mas suas propriedades são sempre reajustadas para refletir a configuração da nova página. A função DoOnWebKeyDown passada ao construtor é implementada no próprio Form e é ela a responsável por responder ao evento em questão.

Agora, vamos à segunda parte do problema: obter informações sobre a tag HTML existente na posição atual do cursor no editor. Para isso, vamos usar a propriedade selection do IHtmlDocument2, que pode conter tanto o atual ponto de inserção do editor quanto todo o texto selecionado, se houver uma seleção.
function TForm1.GetHtmlAtCurPos: string;
var Element, Rng: OleVariant;
begin
Result := '';
try
Rng := DOMInterface.selection.createRange;

if (DOMInterface.selection.Type_ = 'None')
or (DOMInterface.selection.Type_ = 'Text') then
Element := Rng.parentElement
else if (DOMInterface.selection.Type_ = 'Control') then
Element := Rng.commonParentElement;
except
end;

if (VarType(Element) <> varEmpty) then
begin
Result := Element.innerHtml;
if Length(Result) = 0 then
Result := Element.outerHtml;
end else
Result := '';
end; end;

Basicamente, a função acima recupera a seleção atual (ou o ponto de inserção), descobre quem é o elemento HTML onde ele está inserido e retorna o texto HTML correspondente a este elemento. De posse do elemento HTML, é possível criar rotinas mais sofisticadas para capturar detalhes da formatação, como uso de negrito ou cores.

Embora seja estranho usar um OleVariant para ter acesso a propriedades do elemento HTML, este é um recurso bastante usado quando se trabalha com COM; ainda mais quando o tipo exato retornado depende do contexto em que uma função é chamada. Isso não dá erro de compilação no Delphi porque a ligação com a função real é feita somente em tempo de execução. Neste caso, se tentar usar uma função que não existe, um erro será reportado na execução do programa.

Agora, podemos implementar no Form uma resposta ao evento de tecla pressionada:
procedure TForm1.DoOnWebKeyDown(Sender: TObject; EventObj: IHTMLEventObj);
var code : integer;
begin
EventObj.cancelBubble := True;

{ Recupera a tecla que foi pressionada }
code := EventObj.keyCode;

if not (code in [VK_PRIOR, VK_NEXT, VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN])
then _Modified := True;

_Painel.Text := GetHtmlAtCurPos;
end;

A assinatura da função retratada no quadro acima tem que ser exatamente igual à do evento externo criado por nós pois ela é passada para a classe que implementa o IDispatch, através de seu construtor .

Mais Informações
Criando um editor de HTML com o TWebBrowser: conceitos básicos, disponibilizando interações para o usuário, Interface IHtmlDocument2.