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 agosto de 2011

Atualização da especificação do C++ ganha aprovação unânime

Já faz algum tempo, o órgão responsável por padronizações internacionais (ISO) tem um comitê para discutir novos recursos para a linguagem de programação C++. Após 8 anos de trabalho, um rascunho das especificações já tinha sido liberado no início deste ano para que a comunidade decidisse se aprovava ou não.

O foco principal das mudanças na especificação foi a adequação do C++ a arquiteturas mais modernas de processamento paralelo, isto é, teve como objetivo facilitar o desenvolvimento de software C++ para esse tipo de ambiente. Além de alterações na STL (Standard Template Library), o novo C++ também ganha tratamento para expressões lambda (veja funções anônimas) e novos tipos de dado para trabalhar com computação paralela. Há ainda mudanças sintáticas para melhorar a performance dos programas.

Um resumo das alterações introduzidas pode ser consultado no artigo da Wikipedia sobre o C++0x - nome pelo qual a versão era conhecida mas que agora foi batizado oficialmente como C++11.

Na semana passada, o rascunho foi finalmente aprovado - por unanimidade - e passa a ser a recomendação oficial para a linguagem C++. Abaixo, segue matéria da InfoWorld que anuncia a aprovação. A matéria original em inglês está neste link.

"Com a recente aprovação do upgrade para a linguagem C++, o ISO (International Organization for Standards) está fornecendo aos desenvolvedores ferramentas que alavancarão o uso de algorítmos paralelos e permitirão uma melhor performance", disse na terça-feira o presidente do comitê responsável pela linguagem, Herb Sutter.

"O C++11 é a primeira grande revisão do ISO C++", disse o presidente. "Foi introduzido no C++11 as funções lambda, que são peças chaves para os emergentes algorítmos paralelos e que revolucionam o uso da Standard Template Library (STL)", disse Sutter, que é também um dos principais arquitetos para linguagens nativas na Microsoft. "O que já existia na biblioteca padrão do C++98 se tornará imediatamente mais fácil de ser usado", disse. Além disso, alterações na semântica do C++11 trazem melhoria na performance. Os novos recursos para tratamento de concorrência incluem tipos de dados para programação portável e livre de locks.

A votação final para o padrão C++ se encerrou na quarta-feira passada e foi unanimemente aprovada, disse Sutter em seu blog. O C++11 era conhecido como C++0x e sua publicação oficial se dará dentro de algumas semanas. "A linguagem", ele diz, "é usada para quase tudo -- o mundo está construído sobre C++."

O C++, incluindo seu subconjunto, o C, é usado para criar compiladores e runtimes de virtualmente todas as linguagens que concorrem com ele, todos os principais navegadores da internet, todos os principais sistemas operacionais, diz Sutter. É também a principal linguagem usada pelo Google e pelo Facebook e a principal linguagem para quase todos os produtos da Microsoft, afirma. "O C++ é a linguagem a se escolher na construção de aplicações de alta performance, qualquer que seja o domínio dessa aplicação", diz Sutter.

"C++ é uma linguagem de uso geral", afirma Henry Skoglund, desenvolvedor C++. "Muito do que foi incluído no novo padrão eu diria que é importante para desenvolvimento de sistemas e de compiladores," ele diz. "Os recursos do Lamdba", assegura Skoglund, "permitem aos desenvolvedores escrever código com menos bugs e em menos linhas."

Também contempladas no C++11 estão as funções auto e decltype, onde a primeira é útil para dedução de tipos e a segunda permite obter o tipo de dado real de qualquer expressão. Ainda, ponteiros smart, tais como o unique_ptr, oferecem um gerenciamento de memória automatizado e padronizado.

O próximo passo para o padrão C++ é ser implementado pelos compiladores nos próximos um ou dois anos, afirma Sutter. "Enquanto isso se desenrola, o comitê de padrões continuará a trabalhar nos recursos já incluídos no C++11, incrementando a biblioteca padrão de modo que uma biblioteca maior, portável (que não requeira novos recursos da linguagem além dos que já foram incluídos), esteja disponível dentro de cada implementação C++." Está sendo cogitada também uma nova rodada de extensões à linguagem, como conceitos de template ou lambdas mais fortes para o C++11.

Mesmo antes de se tornar oficial, alguns compiladores - como o C++ da Embarcadero (C++ Builder XE) e da Microsoft (Visual C++ 2010) - já incorporaram às suas bibliotecas recursos definidos por esse novo padrão.

4 de agosto de 2011

Recuperando emails de uma caixa postal com Delphi

Até pouco tempo atrás, o ERP da ABC71 possuia um recurso que permitia aos usuários enviarem sugestões de melhorias do software por email. Uma aplicação em nossos servidores monitorava a caixa postal específica, interpretava algumas informações incluídas em cada email, vinculava a solicitação a um responsável e respondia automaticamente o email do usuário com uma mensagem pré-formatada preparada pelo atendimento a Clientes.

Hoje esse esquema não existe mais - foi substituído por outros canais com papel similar - mas a ideia da aplicação capaz de monitorar uma caixa postal ainda é válida e pode ser reaproveitada em outros cenários.

A solução adotada por nós é baseada no protocolo chamado POP3 (Post Office Protocol). Com ele é possível recuperar todas as mensagens existentes na caixa de entrada de uma conta de email. A implementação dele que usamos é a do Projeto Indy distribuida junto com o IDE do Delphi;

Da mesma forma que outros protocolos baseados no IP, usar o POP3 exige que se faça a conexão com o servidor antes de realizar qualquer operação. Temos, então, que informar o nome (ou endereço) desse servidor, a porta onde se conectar e um nome de usuário e sua senha, válidos para a operação desejada.
var _IdPOP3 : TIdPOP3;
begin
_IdPOP3 := TIdPOP3.Create (Nil);

_IdPOP3.Host := _ServidorPOP;
_IdPOP3.Port := _PortaPOP;
_IdPOP3.Username := _Username;
_IdPOP3.Password := _Userpwd;
_IdPOP3.AutoLogin := True;

_IdPOP3.Connect ();

A propriedade AutoLogin ajustada com o valor true indica que o componente tentará automaticamente fazer o login no servidor sempre que isso for necessário, evitando que tenhamos que comandar o login manualmente.

Uma vez que temos a conexão estabelecida, podemos iniciar a recuperação das mensagens da caixa de entrada. Há basicamente dois níveis de informação que conseguimos recuperar sobre uma mensagem. Em ambos os casos, as informações são armazenadas numa variável do tipo TIdMessage. No primeiro nível, são obtidos somente os dados de cabeçalho da mensagem - como o assunto, o remetente e os destinatários -, o que reduz o tráfego na rede. O trecho abaixo recupera o cabeçalho da primeira mensagem da caixa de entrada:
var msg : TidMessage;
begin
msg := TidMessage.Create(self);

_IdPOP3.RetrieveHeader (1, msg);
{ ... }
msg.Free;

O segundo nível permite recuperar a mensagem completa, incluindo todas as informações do nível anterior, bem como o corpo da mensagem e todos os anexos dela. Ao utilizar esse método, a caixa postal marca automaticamente a mensagem como lida:
var msg : TidMessage;
begin msg := TidMessage.Create(self);

_IdPOP3.Retrieve (1, msg);
{ ... }
msg.Free;

Com as informações obtidas nesse nível, temos maior flexibilidade para customizar tratamentos para as mensagens. Por exemplo, podemos redirecionar a mensagem para outra pessoa de acordo com o conteúdo ou processar os anexos, salvando-os numa pasta do computador ou num banco de dados.

Não podemos esquecer que o conteúdo de um email pode ser composto de diversas partes distintas, conforme mostrei no post sobre envio de email com Delphi. Então, dependendo de como o email foi composto, recuperar o corpo da mensagem pode ser um simples acesso à propriedade msg.Body.Text ou significará termos que percorrer a lista de partes para determinar qual é o texto contido. Se o texto no corpo está em branco, basta percorrer a lista de partes procurando por uma que seja do tipo texto (TIdText):
var { ... }
parte : TIdMessagePart;
texto : TIdText;
corpo : string;
begin
{ ... }
_IdPOP3.Retrieve(1, msg);

for j := 0 to msg.MessageParts.Count - 1 do begin
parte := msg.MessageParts.Items[j];
{ Verifica se a parte é um texto }
if (parte is TIdText) then begin
texto := parte as TIdText;
corpo := texto.Body.Text;
break;
end;
{ ... }

O tipo do texto contido numa parte fica informado na propriedade ContentType. Como é permitido ter mais que uma parte com texto, pode-se testar esse tipo para recuperar o texto que interessa. É comum enviar uma parte com texto HTML e outra com texto TEXT/PLAIN para que o programa leitor de email decida qual exibir, de acordo com a opção feita pelo usuário.

Da mesma forma que recuperamos o texto lendo as partes, podemos salvar os anexos. Para isso, a parte deve ser do tipo TIdAttachment. Também podemos determinar o tipo do conteúdo do anexo lendo a propriedade ContentType da parte em questão. Salvar o arquivo fica fácil :
{ ... }
for j := 0 to msg.MessageParts.Count - 1 do begin
parte := msg.MessageParts.Items[j];
{ Verifica se a parte é um anexo }
if (parte is TIdAttachment) then begin
anexo := parte as TIdAttachment;
anexo.SaveToFile('c:\temp\' + anexo.FileName);
end;
{ ... }

Mas, como saber quais mensagens podem ser recuperadas ? A quantidade de mensagens existentes é conseguida através da função CheckMessages; podemos recuperar qualquer uma delas usando um número inteiro entre 1 e essa quantidade. A lista é trazida em ordem cronológica, com as mensagens mais antigas da caixa de entrada possuindo os menores números de identificação.
for i := 1 to _IdPOP3.CheckMessages do
begin
msg := TidMessage.Create(self);
_IdPOP3.RetrieveHeader (i, msg);

if DeveRecuperarMsg (msg.MsgId) then
TrataMsg (Msg);
msg.Free;
end;

As mensagens lidas permanecem na caixa até que sejam removidas. O POP3 permite fazer essa remoção mas nem sempre isso é uma opção. Para esses casos, uma sugestão é controlar manualmente as mensagens. Elas possuem uma string de identificação chamada MsgID que é única, como mostra o código acima. A ideia é recuperar apenas o cabeçalho da mensagem, testar se o MsgID atual já foi tratado antes e só então trazer a mensagem completa, se for necessário.

Quando concluir as operações na caixa postal, finalize a conexão com o POP3 usando a função Disconnect.

Como se pode ver pelo exemplo retratado neste post, o protocolo POP3 é bastante simples, limitando-se a poucas funcionalidades básicas. Se for preciso usar recursos mais avançados, tais como restringir as mensagens recuperadas usando filtros ou realizar operações envolvendo "pastas" na caixa postal, teremos que apelar a um protocolo mais completo, como o IMAP4. No post Trabalhando com emails em uma caixa postal usando IMAP4 em Delphi eu mostro como usá-lo.

Mais Informações
Post Office Protocol