HashCode

An organizer of symbols

Archive for the ‘Linguagem C’ Category

Distribuição com PL/Ruby e DbLink no Postgresql

Saturday, January 12th, 2008

Depois de uma escravização no fim do ano passado e no inicio deste, estou de volta para “espalhar a maldade”!

Vou falar sobre PL/Ruby ou melhor fazer funcionar.

PL/Ruby é uma linguagem procedural acoplada dentro do Postgresql. Entre as linguagem do Postgres é a mais flexível e fácil.
Demonstrarei inicialmente “o jeitão da coisa” e depois uma implementação distribuída.

Instalação
Instale o banco e o módulo plruby, se o postres já estiver instalado, instale apenas o módulo(claro!)

apt-get install postgresql-8.1 postgresql-8.1-plruby

Agora conecte via psql

psql template1

Criaremos um banco novo para todos os testes deste tutorial, o banco “ruby_test”, então vamos lá

template1=# create database ruby_test;

conecte no banco novo

template1=# \c ruby_test

Crie a função/linguagem via o script plruby.sql que faz parte do pacote postgresql-8.1-plruby, se a sua distro difere de ubuntu, verifique onde o arquivo plruby.sql está e invoque com \i semelhante ao exemplo abaixo.

ruby_test=# \i /usr/share/postgresql-8.1-plruby/plruby.sql

Jeitão
Para familiarizarmos um pouco vamos criar uma função que produz uma seqüencia de números separados por espaço.
Portanto a função abaixo cria um Range do primeiro argumento(args[0]) até o segundo argumento(args[1]), converte o range para Array e depois para String separando os elementos por espaço.

create or replace function string_array(int,int) returns text as $RUBY$


  (args[0]..args[1]).to_a.join " "

$RUBY$ language plruby;

Executando

ruby_test=# select string_array(10,20);

Produzindo

"10 11 12 13 14 15 16 17 18 19 20"

Agora uma função que procura um padrão dentro do um texto, se o padrão for encontrado, o mesmo ficará entre tags <b>.

create or replace function find(varchar,text) returns text as $RUBY$


  args[1].gsub(/(#{args[0]})/,'<b>\1</b>')

$RUBY$ language plruby;
ruby_test=# select find('[tT]he','The method PL#context and PL#context= give the possibility to store information between the call')

Produto

"<b>The</b> method PL#context and PL#context= give <b>the</b> possibility to store information between <b>the</b> call"

Até agora demonstramos duas funções, faz uma lógica e retorna apenas uma tupla.
Ficou bem elegante essa implementação do PL/Ruby para vários registros(setof) sendo que cada registro é um objeto lançado pelo operador yield do Ruby.
Uma função em PL/Ruby para resolver a função(x ao quadrado vezes a raíz de 2)

f(x) = x^2 * raiz(2)

sendo x inteiro variando de x1 a x2.

create or replace function fx(int,int) returns setof float as $RUBY$


  (args[0]..args[1]).each {|x|  yield x.to_i**2 * Math.sqrt(2) }

$RUBY$ language plruby;

Executando com * from pois estamos pegando vários registros

ruby_test=# select * from fx(1,10);

Resultando em

1.4142135623731
5.65685424949238
12.7279220613579
22.6274169979695
35.3553390593274
50.9116882454314
69.2964645562817
90.5096679918781
114.551298552221
141.42135623731

Manipulando dados
Vamos criar uma tabela denominada “users” para povoarmos de dados para os testes.

ruby_test=# CREATE TABLE users(
  id serial not null primary key,
  name varchar(255) not null ,
  login varchar(80),
  email varchar(80),
  password varchar(80)
) ;

Inserindo alguns registros

ruby_test=# insert into users(name,login,email,password) values ('Fulano de tal','fulano','fulano@server.com','fulanopwd');
ruby_test=# insert into users(name,login,email,password) values ('Maria da Silva','maria','maria.silva@server.com','mariapwd');
ruby_test=# insert into users(name,login,email,password) values ('Sem nome','semnome','semnome@server.com','semnomepwd');

Daqui a pouco faremos a distribuição dos dados via trigger para bancos remotos, portanto criaremos uma função para criação dos inserts.
A função make_insert faz engenharia reversa na ddl e nos dados utilizando o módulo PL disponível no plruby

CREATE OR REPLACE FUNCTION make_insert(users) RETURNS text AS $RUBY$

    row=args[0]
    tn=PL.args_type.to_s
    cn=row.keys.join(',')
    values= row.values.map{|m| (m)?"'#{m}'":'NULL' }.join(',')
    "INSERT INTO #{tn}(#{cn}) values (#{values})"

$RUBY$ language plruby;

Veja o exemplo da execução

ruby_test=# select make_insert(users) from users;

Resultado

INSERT INTO users(name,id,password,login,email) values ('Fulano de tal','1','fulanopwd','fulano','fulano@server.com')
INSERT INTO users(name,id,password,login,email) values ('Maria da Silva','2','mariapwd','maria','maria.silva@server.com')
INSERT INTO users(name,id,password,login,email) values ('Sem nome','3','semnomepwd','semnome','semnome@server.com')

Sem aquela concatenação demasiada do plpgsql usando pipes e aspas para todo lado. Ah! a função make_insert é genérica para qualquer tabela mas no argumento de criação a sua tabela. Tipo

CREATE OR REPLACE FUNCTION make_insert(sua_tabela) RETURNS text AS $RUBY$
...

Semelhante aos parâmentos tabela%rowtype do postgres.

Distribuindo os dados
Já vou dizendo logo, isso não é comparável ao Slony-I, isso é uma simples utilização do dblink do postgresql.
As funções de dblink ficam no pacote contrib do postgres, a instalação com apt-get

apt-get install postgresql-contrib-8.1

Crie as funções com \i onde o argumento é o arquivo dblink.sql

ruby_test=#  \i /usr/share/postgresql/8.1/contrib/dblink.sql

Lembrando a assinatura da função dblink_exec é

select dblink_exec("connection string","query")

Exemplo

select dblink_exec('hostaddr=127.0.0.1 dbname=mydb user=shairon password=senha','insert into...');

Partiremos da seguinte extrategia: criaremos uma tabela chamada nodes onde armazenaremos os dados dos bancos de dados, selecionaremos apenas os nodes ativos e enviaremos os registros para os mesmos através da trigger replica_plruby(T) no evento after insert na tabela users master(A).
Veja o diagrama abaixo.
replica_plruby.png

Então temos a tabela nodes.

create table nodes(id serial not null primary key, host varchar(80), username varchar(80), password varchar(80), active boolean )

Dados para teste

insert into nodes(host,dbname,username,password,active) values ('127.0.0.1','mydb1','foo','bar',true);
insert into nodes(host,dbname,username,password,active) values ('192.168.0.7','mydb2','foo','bar',true);
insert into nodes(host,dbname,username,password,active) values ('10.0.0.38','mydb3','foo','bar',true);

Criando a trigger replica_plruby, acompanhe pelos comentários

CREATE OR REPLACE FUNCTION replica_plruby() RETURNS trigger AS  $RUBY$

  #obtendo a quantidade de nós ativos
  count=PL::exec("select count(*) from nodes where active=true",1,"value").to_s.to_i
  if count > 0
     #obtendo os nós ativos
     PL::Plan.new("select * from nodes where active=true").each do |row|
       #montando a conexão
       #cada row é um hash  tipo {"dbname"=>"delme", "username"=>"shairon" ...}
       conn="hostaddr=#{row['host']} dbname=#{row['dbname']} user=#{row['username']} password=#{row['password']}"
       #Nome das culunas via metadata
       keys  = new.keys.join(",")
       #Os valores com tratamento de nulo
       values = new.values.map{|m| (m)?"''#{m}''":'NULL' }.join(',')
       #Query final
       query = "INSERT INTO #{tg['relname']}(#{keys}) values (#{values})"
       #Disparo
       PL.exec("select dblink_exec('#{conn}','#{query}')")
     end
  end
  new
$RUBY$ LANGUAGE plruby;

Pronto! Insira novos dados na tabela users para ver o resultado.

Referências
Nos arquivos de documentação dos pacotes

ruby1.8
postgresql-8.1
postgresql-8.1-plruby
postgresql-contrib-8.1

Shared Object Híbrido, Java e Ruby

Saturday, September 29th, 2007

Pense em dois softwares, ambos monitoram um dispositivo via driver, aparentemente iguais diferindo apenas pela linguagem em que foram concebidos, um em Java e outro em Ruby. Conseqüentemente, duas bibliotecas fazendo interface entre o driver e o ambiente da linguagem de auto nível.
Resolvi me aventurar nessa idéia e criei uma solução híbrida, uma biblioteca compatível com Ruby e Java.
jni_ruby.png
Testei com um SmartCard, como é algo específico(e você não vai comprar um só pra testar isso :)) usarei a função getenv definida em stdlib.h .

Vamos dividir em três estágios:
- Criar um teste jni(java)
- Criar um teste em ruby(Extending)
- Juntar Java e Ruby com C

Ingredientes(debian packages)
- sun-java6-jdk
- ruby1.8
- ruby1.8-dev
- gcc-4.1

Java

Vamos seguir este fluxo

jni_java_flow.png

Crie um diretório “java” para o teste. Entre no diretório.

1. Criar o arquivo fonte java
Primeiramente crie o arquivo EnviromentVariable.java que depois de compilado será o protótipo para implementação em C.

 class EnviromentVariable {  

  public native String get(String varname);  

 }

2. Gerar o bytecode
Compile

 javac EnviromentVariable.java

3. Gerar os headers

 javah -jni EnviromentVariable

O javah criará um arquivo .h com o conteúdo

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class EnviromentVariable */
#ifndef _Included_EnviromentVariable
#define _Included_EnviromentVariable

#ifdef __cplusplus
extern "C" {
#endif
/*

 * Class:     EnviromentVariable
 * Method:    get
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */

JNIEXPORT jstring JNICALL Java_EnviromentVariable_get(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

4. Criar o arquivo fonte C
Agora faremos a lógica da biblioteca, portando é necessário a criação do arquivo enviroment_variable_java.c com o conteúdo


#include  <stdio.h>
#include  <stdlib.h> //getenv vem daqui
#include "EnviromentVariable.h"

JNIEXPORT jstring JNICALL Java_EnviromentVariable_get(JNIEnv *jvm, jobject obj, jstring varname){
  //Convertendo uma String Java em C String
  char * c_varname=(char *) (*jvm)->GetStringUTFChars(jvm, varname,NULL);

  //Obtendo o valor da variável e criando uma string para retorno
  return (*jvm)->NewStringUTF(jvm, getenv(c_varname) );
}

5. Gerar o shared object
O home da JVM na minha máquina está em /usr/lib/jvm/java-6-sun, verifique na sua distro onde foi instalada. Pra facilitar export a variável JAVA_HOME

export JAVA_HOME=/usr/lib/jvm/java-6-sun

Agora vamos gerar o libEnviromentVariable.so

gcc enviroment_variable_java.c \\
-shared -o libEnviromentVariable.so\\
-I. -I$JAVA_HOME/include -I$JAVA_HOME/include/linux

6. Testando
Para testarmos, crie um arquivo Main.java com o conteúdo.

class Main{
 static{
   System.loadLibrary("EnviromentVariable");
 }
 static public void main(String args[]){
  System.out.println(new EnviromentVariable().get("PATH"));
 }
}

Observe a cima vamos pegar o valor da variável PATH do seu sistema. Lembrando que a variável tem que existir.
Compile Main.java

javac Main.java

Execute o programa Main com a opção -Djava.library.path=”O diretório onde está o .so” no nosso caso em ‘.’

java -Djava.library.path=. Main

O output semelhante a

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/local/shairon/bin:/usr/lib/jvm/java-6-sun/bin

Depois teste mudando a variável PATH para uma outra variável válida.

Ruby

Vamos seguir o fluxo
jni_ruby_flowx.png
Crie outro diretório no mesmo nível do java mas com o nome de “ruby”. Entre do diretório.

1. Criar o arquivo fonte ruby

Crie o arquivo enviroment_variable_ruby.c conforme abaixo.

#include  <ruby.h>
#include  <stdlib.h>

static VALUE t_get(VALUE self, VALUE varname){
    //Converte String Ruby em C String
   char *c_varname=STR2CSTR(varname);

   //Obtendo o valor da variável e criando uma string para retorno
   return rb_str_new2( getenv(c_varname)); 

}

VALUE cEnviromentVariable;

//Função de inicialização do arquivo
void Init_libEnviromentVariable() {

  //Cria a classe EnviromentVariable
  cEnviromentVariable = rb_define_class("EnviromentVariable", rb_cObject);

  //Associa o método t_get com EnviromentVariable#get
  rb_define_method(cEnviromentVariable, "get", t_get, 1);
}

2. Criar o arquivo para geração do makefile

Vamos criar o arquivo extconfig.rb para geração do makefile. Abaixo

require "mkmf"
create_makefile("libEnviromentVariable")

Execute

ruby extconfig.rb

3. Compile e instale

make

depois

make install

Abrao irb e digite:

require 'libEnviromentVariable'
puts EnviromentVariable.new.get("PATH")

Java e Ruby estão chamando diretamente o getenv, algo fácil pois é só uma chamada de função. Imagine se fosse algo mais complexo? O desenvolvedor iria reutilizar o código em C via copy-paste, nada elegante.
Daqui em diante vamos fazer Ruby e Java usar uma função comum e gerar apenas um Shared Object.

Solução Híbrida

Antes de mais nada observe que o módulo “mkfm” identifica onde estão os headers necessários para a compilação do .so em ruby, durante a compilação aparece o caminho detectado, no meu caso em /usr/lib/ruby/1.8/i486-linux, portanto declarare um variável de ambiente RUBY_HOME para facilitar o entendimento.

export RUBY_HOME=/usr/lib/ruby/1.8/i486-linux

Agora o arquivo que compartilha a mesma função entre Java e Ruby, o arquivo enviroment_variable.c. Verifique a disposição dos seus arquivos e diretório. A estrutura usada é:
1p.png

O conteúdo de enviroment_variable.c, acompanhe pelos comentários.

#include 
#include "java/EnviromentVariable.h"
#include 
#include 

//Função que será compartilhada

char *var(char *varname){
  return getenv(varname);
}

//JAVA SIDE
JNIEXPORT jstring JNICALL Java_EnviromentVariable_get(JNIEnv *jvm, jobject obj, jstring varname){

  char * c_varname=(char *)(*jvm)->GetStringUTFChars(jvm, varname,NULL);

 //Chama a função var e retorna uma String para JVM
  return (*jvm)->NewStringUTF(jvm, var(c_varname) );
}

//RUBY SIDE

static VALUE t_get(VALUE self, VALUE varname){
  char *c_varname=STR2CSTR(varname);
  //Chama a função var e retorna uma String para JVM
  return rb_str_new2( var(c_varname));
}

VALUE cEnviromentVariable;

void Init_libEnviromentVariable() {
  cEnviromentVariable = rb_define_class("EnviromentVariable", rb_cObject);
  rb_define_method(cEnviromentVariable, "get", t_get, 1);
}

Linkando e Compilando

Lembrando que já definimos as variáveis de ambiente $JAVA_HOME e $RUBY_HOME.

Gerando o objeto

gcc -c enviroment_variable.c \
-fPIC -Wall -g -fno-strict-aliasing -O2 \
-I$RUBY_HOME \
-I$JAVA_HOME/include \
-I$JAVA_HOME/include/linux

depois o Shared Object

gcc -L"/usr/lib" \
-shared -o libEnviromentVariable.so enviroment_variable.o \\
-I$RUBY_HOME \\
-I$JAVA_HOME/include \\
-I$JAVA_HOME/include/linux \\
-lruby1.8 -lpthread -ldl -lcrypt -lm -lc

Pronto! Geramos o bacalhau :) o arquivo libEnviromentVariable.so está pronto para ser usado.
Testando em java

java -Djava.library.path=. -cp ./java Main

em Ruby pelo irb

require 'libEnviromentVariable'
puts  EnviromentVariable.new.get("PATH")

Alguns outputs do irb

rb(main):001:0> require 'libEnviromentVariable'
=> true
irb(main):002:0> envs=EnviromentVariable.new
=> #
irb(main):003:0> envs.get("USERNAME")
=> "shairon"
irb(main):004:0> envs.get("TERM")
=> "xterm"
irb(main):005:0> envs.get("SHELL")
=> "/bin/bash"

obs.: depois troque enviroment por environment. :)

Referências

Extending Ruby
Java Native Interface Specification

Append em arquivo no Postgresql via módulo em C

Friday, August 24th, 2007

Estava desenvolvendo um sistema de distribuição de dados no Postgresql e tive a necessidade de armazenar em um arquivo as inserções caso houvesse erro. Relatarei aqui uma solução desenvolvida um módulo(zinho) em C.

Pacotes necessários para a compilação
Na verdade eu não sei bem se os outros são necessários mais o postgresql-server-dev-8.1 é certeza :)

postgresql-8.1
postgresql-client-8.1
postgresql-contrib-8.1
postgresql-server-dev-8.1

Função

#include <stdio.h>
#include "postgres.h"
#include <string.h>
#include "fmgr.h"

//Registrando a função log_pg no escopo do postgres
PG_FUNCTION_INFO_V1(log_pg);

//Datum é o tipo esperado, cast automático do int de retorno
Datum log_pg(PG_FUNCTION_ARGS){

//PG_GETARG_TEXT_P(0) pega o primeiro argumento
//VARDATA faz cast para char *
  char *filename = VARDATA(PG_GETARG_TEXT_P(0));

  FILE *fp;

  if ((fp = fopen(filename, "a"))==NULL)  {
    printf("Cannot open file \n");
    return 1;
  }else{
   //Pega o segundo argumento já convertido para char *
    fputs(VARDATA(PG_GETARG_TEXT_P(1)),fp);
    fclose(fp);
    return 0;
   }
}

Salve o arquivo como log_pg.c
Compile

gcc -fpic -I/usr/include/postgresql -I/usr/include/postgresql/8.1/server -c log_pg.c

Gere o Shared Object

gcc -shared -o log_pg.so log_pg.o

No meu caso a dynamic_library_path está apontando para /usr/lib/postgresql/8.1/lib/ portanto, o log_pg.so vai pra lá.

install log_pg.so  /usr/lib/postgresql/8.1/lib/log_pg.so

Agora no Postgresql duas funções, uma mapeia o log_pg.so em log_me

CREATE or replace FUNCTION log_me(varchar,varchar) RETURNS int
 AS 'log_pg.so', 'log_pg' LANGUAGE C;

e a outra uma abstração para gerar a data corrente e concatena .log ao fim do nome do arquivo (melhor que usar strcpy).

create or replace function logger(varchar,varchar) returns int as $BODY$

  begin
    return log_me(($1 || current_date::varchar ||'.log')::text,$2::text);
  end
$BODY$ language 'plpgsql';

Testando

select logger('/tmp/teste', 'meu primeiro teste');

Saiba mais … libpq - C Library

RGhost (Beta)

Tuesday, June 5th, 2007

Rghost é uma API de criação e conversão de documento.

Uma forma de criação e vários outputs.
flow2.png
Usa o ghostscript framework para conversão de formatos. Com:

  • Legado de 16 anos de conversão de documento do Ghostscript.
  • Renderização
    • Matriz de dados otimizada
    • Permite configurar a pilha de dados interna
    • Homologado até 5.800 páginas(=~ 464.000 registros) para Linux Intel
    • Homologado até 10.000 páginas(=~ 800.000 registros) para Linux Sparc
  • Unidades
    • Cm, inch e psunit
    • Permite personalização de unidade
  • Fontes
    • 9 fontes internas(pfb)
    • GNU Barcodes(ean,i25, code39, code128)
    • Fontes binárias
    • Distorção de fontes
    • Fontes asiáticas
    • Suporte a Latin1(ISO-8859-1)
  • Cursores
    • Ponto absoluto, ponto relativo, deslocamento da origem, etc
    • Salto de página e controles de faixa dinâmicos
    • Papel
    • Disposição landscape e portrait
    • Papeis pré-definidos
    • Papeis com tamanho personalizado
    • Controle de margem de silêncio
  • Texto
    • Área de texto com tags.
    • Texto em ponto absoluto e relativo
    • Alinhamento ao ponto
    • Alinhamento pela página
    • Avaliação de variáveis internas em postscript
  • Cores
    • CMYK
    • RGB
    • Grayscale
  • Geometrias
    • Arcos, retângulos, linhas, etc
    • Predefinições auxiliares de linhas e backgrounds de faixas
  • Imagens e Templates de entrada
    • GIF e JPEG
    • EPS
  • Grade de dados para
    • Matriz de dados
    • CSV
    • ActiveRecord
    • Personalização de colunas de dados
    • Formatador de dados
    • Estilos predefinidos
  • Document Callbacks
    • :before_page_create, :after_page_create,:odd_pages,:even_pages, :before_document_create,:first_page, last_page e :after_document_create
  • Grid Callbacks
    • :before_row, :after_row, :even_row,:odd_row, :before_column, :after_column, :even_column, :odd_column, :before_table_create e :after_table_create
  • Conversor de PDF
  • Outputs(mais usados)
    • PDF, PS, PNG, EPS, JPG, TIF, laserjet, deskjet e X11

Versão Beta, por favor reporte bugs e sugestões.

RDOC
Manual Ruby Ghost Engine(pdf)

gem install rghost

Ruby & Ghostscript - Um diálogo possível

Friday, April 27th, 2007

Hoje trabalho em um empresa de Telefonia IP que atende outras telefônicas, com uma média de 800 mil minutos/mês. Desenvolvemos nossa própria plataforma, incluindo billing, rota de destinação, interface com cliente, etc., a primeira versão foi desenvolvida em 5 meses utilizando Java. A parte mais delicada do sistema é o tarifador, e o desempenho de atendimento para vários clientes já apresentava QoS baixo mas funcionava.

Tivemos um curso de Design Patterns onde precisaríamos de certificados para uma licitação, o instrutor disse “Vocês conhecem Ruby?”. Eu “What?”, ele disse “depois eu falo..”. No mesmo dia fui pesquisar e encontrei o livro “Programming Ruby The Pragmatic Programmers’ Guide”, apt-get install ruby, abri o Kate e comecei meus “Hello Worlds”, isso era umas 20:30. Aí comecei a exclamar “P.t. que pariu!!”, “Errensga!!” , “Afi!!”, minha esposa me chama para dormir, eu “daqui a pouco”… quando fui ver o as horas 05:48, nem vi as horas passarem. No mesmo dia de manhã eu não fui ao curso(tava quebrado!), fui apenas a tarde. O instrutor “Eai conhecem Ruby”, eu estava anestesiado e só consegui falar PQP! Aí ele me mostrou Rails “The Web Framework”. Nem precisava tanto… só Ruby já bastava.

Após o termino do curso fui “meter as caras” e refazer o tarifador, em uma semana e meia estava pronto e testado, foi para produção no 12º dia de desenvolvimento, mais leve, menos Kloc…

Refizemos a aplicação web de administração/cliente em Rails em 1 mês e 10 dias, ficando apenas os relatórios via web service utilizando a nossa API PsBuilder (PostscriptBuilder gera pdf e ps)

Tentei e teste as ferramentas para relatório do Ruby mas nenhum se adaptava as faturas de 60 mil registros de ligações.

Trabalhei na Xerox(uma escola!) quase 8 anos utilizávamos vários PDL(Page Description Language) em vários OS (SunOS, Solaris, Novell, Linux, AIX, etc). Utilizam muito “Programação Orientada a Estagiário” <-- EU, fui muito "usado"(no bom sentido) tive que aprender XES, PCL, PDL/FDL, HPGL/2 e Postscript, ah! sem saber linguagem de programação, estava no 2º grau ainda, eu era magro nessa época...

Lá na Xerox a última linguagem que aprendi foi VIPP que é a cópia do Postscript que é cópia do InterPress que é a cópia dos scripts JaM. Então qual era a vantagem do VIPP? Funções “helpers” do Postcript. Uma aplicação de gráfico de linhas que demorava uns 5 dias para ser feita em Postcript em VIPP eram duas ou três linhas.

E o interessante que Postscript sempre foi uma ótima linguagem, podemos programar na linguagem ao contrário da maioria dos PDLs que só marcavam as páginas(markup language). Tinha(tem) um grande problema: para usar Postscript nas laser da Xerox tem que pagar royalties altos, elevando o preço dos equipamentos, portanto usávamos PDL’s próprios ou mais baratos como o PCL(da HP).

E o que que isso tem a ver com “Ruby & Ghostscript - Um diálogo possível”?

Do inicio deste mês de abril para cá, venho desenvolvendo uma API para geração de documentos mais profissionais. A estratégia é simples mas trabalhosa:

- Desenvolver funções em Postscript para tarefas mas árduas, callbacks e cursores
- Gerar um código intermediário(em Postscript) originado por Ruby
- Compilar com Ghostscript e gerar os outputs

Posso afirmar agora que teremos uma liberdade e facilidade de criação de documentos profissionais tento para mídia digital ou para arte final em papel como (certificados, faturas, boletos, etc).

Um exemplo mais real:
Após o desenho do template/formulário em aplicações gráficas como CorelDraw, Inkscape, Photoshop, Gimp e outros a API importará o template e o desenvolvedor Ruby apenas posicionará os dados nos campos.

Outro exemplo de flexibilidade:
Adicionar linhas, texto, imagens, vetores, templates, etc no documento ou nos callbacks:
:before_page_create,
:after_page_create,
:odd_pages,
:even_pages,
:before_document_create,
:first_page,
:last_page,
:after_document_create

tendo a possibilidade de condição only ou except . Exemplo:

  d=Document.new

  d.before_page_create :only => [3,4,9] do
    set Text.new "Relatório XXX", :align => :center
  end

  d.even_pages :except => [3] do
     set HorizontalLine.new :bottom, :start_in => '3 cm', :size => '10 cm'
  end

  d.render :pdf

Já terminei as funções em Postscript e criei um módulo via Ruby Extending comunicando com libgs.so, ainda não testei em Windows.
A API será Open Source. Daqui uns 2 meses teremos a primeira versão pública.

Abaixo a lista dos OUTPUTS

Displays

:lvga256 => “Linux vgalib, 256-color VGA modes => [Linux only]“,
:vgalib => “Linux vgalib, 16-color VGA modes => [Linux only]“,
:x11 => “X Windows version 11, release >=4 => [Unix and VMS only]“,
:x11alpha => “X Windows masquerading as a device with alpha capability”,
:x11cmyk => “X Windows masquerading as a 1-bit-per-plane CMYK device”,
:x11cmyk2 => “X Windows as a 2-bit-per-plane CMYK device”,
:x11cmyk4 => “X Windows as a 4-bit-per-plane CMYK device”,
:x11cmyk8 => “X Windows as an 8-bit-per-plane CMYK device”,
:x11gray2 => “X Windows as a 2-bit gray-scale device”,
:x11gray4 => “X Windows as a 4-bit gray-scale device”,
:x11mono => “X Windows masquerading as a black-and-white device”,
:x11rg16x => “X Windows with G5/B5/R6 pixel layout for testing.”,
:x11rg32x => “X Windows with G11/B10/R11 pixel layout for testing.”,

Impressoras

:atx23 => “Practical Automation ATX-23 label printer”,
:atx24 => “Practical Automation ATX-24 label printer”,
:atx38 => “Practical Automation ATX-38 label printer”,
:deskjet => “H-P DeskJet and DeskJet Plus”,
:djet500 => “H-P DeskJet 500; use -r600 for DJ 600 series”,
:fs600 => “Kyocera FS-600 (600 dpi)”,
:laserjet => “H-P LaserJet”,
:ljet2p => “H-P LaserJet IId/IIp/III* with TIFF compression”,
:ljet3 => “H-P LaserJet III* with Delta Row compression”,
:ljet3d => “H-P LaserJet IIID with duplex capability”,
:ljet4 => “H-P LaserJet 4 (defaults to 600 dpi)”,
:ljet4d => “H-P LaserJet 4 (defaults to 600 dpi) with duplex”,
:ljetplus => “H-P LaserJet Plus”,
:lj5mono => “H-P LaserJet 5 & 6 family (PCL XL), bitmap:”,
:lj5gray => “H-P LaserJet 5 & 6 family, gray-scale bitmap;”,
:lp2563 => “H-P 2563B line printer”,
:oce9050 => “OCE 9050 printe”,
:pxlmono => “H-P black-and-white PCL XL printers (LaserJet 5 and 6 family)”,
:pxlcolor => “H-P color PCL XL printers (e.g. Color LaserJet 4500)”,

Formato para Fax

:faxg3 => “Group 3 fax, with EOLs but no header or EOD”,
:faxg32d => “Group 3 2-D fax, with EOLs but no header or EOD”,
:faxg4 => “Group 4 fax, with EOLs but no header or EOD”,
:tiffcrle => “TIFF CCITT RLE 1-dim (= Group 3 fax with no EOLs)”,
:tiffg3 => “TIFF Group 3 fax (with EOLs)”,
:tiffg32d => “TIFF Group 3 2-D fax”,
:tiffg4 => “TIFF Group 4 fax”,

Vetor e Imagem

:epswrite => “EPS output (like PostScript Distillery)”,
:pdfwrite => “PDF output (like Adobe Acrobat Distiller)”,
:pswrite => “PostScript output (like PostScript Distillery)”,
:pxlmono => “Black-and-white PCL XL”,
:pxlcolor => “Color PCL XL”,

Raster file formats and devices

:bit => “Plain bits, monochrome”,
:bitrgb => “Plain bits, RGB”,
:bitcmyk => “Plain bits, CMYK”,
:bmpmono => “Monochrome MS Windows .BMP file format”,
:bmpgray => “8-bit gray .BMP file format”,
:bmpsep1 => “Separated 1-bit CMYK .BMP file format, primarily for testing”,
:bmpsep8 =>”Separated 8-bit CMYK .BMP file format, primarily for testing”,
:bmp16 => “4-bit (EGA/VGA) .BMP file format”,
:bmp256 => “8-bit (256-color) .BMP file format”,
:bmp16m => “24-bit .BMP file format”,
:bmp32b => “32-bit pseudo-.BMP file format”,
:cgmmono => “Monochrome (black-and-white) CGM — LOW LEVEL OUTPUT ONLY”,
:cgm8 => “8-bit (256-color) CGM — DITTO”,
:cgm24 => “24-bit color CGM — DITTO”,
:jpeg => “JPEG format, RGB output”,
:jpeggray => “JPEG format, gray output”,
:miff24 => “ImageMagick MIFF format, 24-bit direct color, RLE compressed”,
:pcxmono=> “PCX file format, monochrome (1-bit black and white)”,
:pcxgray=> “PCX file format, 8-bit gray scale”,
:pcx16 => “PCX file format, 4-bit planar (EGA/VGA) color”,
:pcx256 => “PCX file format, 8-bit chunky color”,
:pcx24b => “PCX file format, 24-bit color (3 8-bit planes)”,
:pcxcmyk => “PCX file format, 4-bit chunky CMYK color”,
:pbm => “Portable Bitmap (plain format)”,
:pbmraw => “Portable Bitmap (raw format)”,
:pgm => “Portable Graymap (plain format)”,
:pgmraw => “Portable Graymap (raw format)”,
:pgnm => “Portable Graymap (plain format), optimizing to PBM if possible”,
:pgnmraw => “Portable Graymap (raw format), optimizing to PBM if possible”,
:pnm => “Portable Pixmap (plain format) (RGB), optimizing to PGM or PBM”,
:pnmraw => “Portable Pixmap (raw format) (RGB), optimizing to PGM or PBM”,
:ppm => “Portable Pixmap (plain format) (RGB)”,
:ppmraw => “Portable Pixmap (raw format) (RGB)”,
:pkm => “Portable inKmap (plain format) (4-bit CMYK => RGB)”,
:pkmraw => “Portable inKmap (raw format) (4-bit CMYK => RGB)”,
:pksm => “Portable Separated map (plain format) (4-bit CMYK => 4 pages)”,
:pksmraw => “Portable Separated map (raw format) (4-bit CMYK => 4 pages)”,
:plan9bm => “Plan 9 bitmap format”,
:pngmono => “Monochrome Portable Network Graphics (PNG)”,
:pnggray => “8-bit gray Portable Network Graphics (PNG)”,
:png16 => “4-bit color Portable Network Graphics (PNG)”,
:png256 => “8-bit color Portable Network Graphics (PNG)”,
:png16m => “24-bit color Portable Network Graphics (PNG)”,
:psmono => “PostScript (Level 1) monochrome image”,
:psgray => “PostScript (Level 1) 8-bit gray image”,
:psrgb => “PostScript (Level 2) 24-bit color image”,
:tiff12nc => “TIFF 12-bit RGB, no compression”,
:tiff24nc => “TIFF 24-bit RGB, no compression (NeXT standard format)”,
:tifflzw => “TIFF LZW (tag = 5) (monochrome)”,
:tiffpack => “TIFF PackBits (tag = 32773) (monochrome)”