Ya estamos haciendo servir Google Fonts en VIC Services

Ya estoy haciendo servir Google Fonts en VIC Services. En concreto el sitio está haciendo servir la fuente «Patrick Hand» para el nombre del sitio (VIC Services), el eslogan (desarrollo web) y en las opciones del menú principal.

En vez de incluir un link a la hoja de estilos como indicaba en una entrada anterior:

<link href="http://fonts.googleapis.com/css?family=Patrick+Hand&v2" rel="stylesheet" type="text/css">

He incluido directamente en una hoja de estilos del sitio el código contenido en ese css remoto:

@font-face {
  font-family: 'Patrick Hand';
  font-style: normal;
  font-weight: normal;
  src: local('PatrickHand'), local('PatrickHand-Regular'),
         url('http://themes.googleusercontent.com/font?kit=9BG3JJgt_HlF3NpEUehL0HhCUOGz7vYGh680lGh-uXM')
         format('woff');
}

El W3C tiene un detallado documento sobre fuentes en CSS3. Aquí pueden leerlo si tienen curiosidad sobre la nueva propiedad «src». La verdad es que esto lo he hecho muy rápido, cuando tenga más tiempo incluiré en mi servidor el fichero .ttf para que no deba ir a buscarlo a los servidores de Google. Si en su navegador no se visualizan correctamente los nuevos textos por favor comuníquemelo a través del formulario de contacto o dejando un comentario en esta entrada.

— Editado el 06/02/2012 —

Este artículo data de cuando este sitio web estaba en Drupal y ya no es válido para la actual implementación en WordPress.

Servicios web para fuentes

Algo que da algún que otro dolor de cabeza cuando implementamos el aspecto visual de una web es la maquetación, es muy difícil conseguir que la misma web se visualice igual en todas las posibles combinaciones de navegadores, sistemas operativos y resoluciones de pantalla. A solucionar uno de estos puntos oscuros vienen las fuentes web. Como no todos los sistemas operativos disponen de las mismas fuentes, podemos indicar al navegador mediante los estilos fuentes alternativas para que, en caso de que el sistema operativo no disponga de una, pueda recurrir a otra:

font-family: "Lucida Grande","Lucida Sans Unicode", sans-serif

Con esta indicación el sistema primero intentará usar Lucida Grande, frecuente en los Mac, en caso de no disponer de ella, como ocurre en los Linux y Unix, recurrirá a Lucida Sans Unicode, si tampoco dispone de ella recurrirá a Sans-serif. En caso de no disponder de ninguna de las fuentes indicadas recurrirá a la que el sistema tenga por defecto.

Un problema derivado de esto es que el tamaño de cada familia de fuente es distinto así que el mismo texto podría ocupar más o menos espacio en función de la fuente escogida, pudiendo llegar incluso a que el texto se desborde y «quiebre» la página o una parte de ella. Una solución a esto sería usar sólo fuentes «seguras», pero un sitio hecho exclusivamente en Arial y Serif sería algo aburrido, ¿verdad?

Por suerte, disponemos de servicios web que nos proporcionan fuentes para que estas se vean igual en todos los sistemas, aquí pueden ver una tabla bastante completa de empresas que ofrecen estos servicios.

El único servicio gratuito que conozco a día de hoy es el de Google. He aquí un ejemplo para ilustrar el sencillo uso de Google Fonts:

<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Tangerine" />

Incluimos en nuestra web esta hoja de estilos generada en un servidor de Google y pasamos como parámetro la fuente que queremos importar: Tangerine. A partir de ahora es una fuente más que podemos usar en nuestra web, por ejemplo creamos un estilo llamado «googleFont»:

.googleFont {
font-family: 'Tangerine', serif;
font-size: 40px;
}

Y lo aplicamos donde deseemos:
Ejemplo de la Google Font Tangerine
En la documentación de Google tenemos toda la información sobre la importación de fuentes.

Estoy esperando poder empezarlas a usar en mi próximo proyecto. Será en el «duro» mundo de los entornos de producción donde realmente podré poner a prueba el sistema.

Actualización 29/06/2011:

Hablando sobre Google Fonts con otros desarrolladores me han dicho que no les gusta la idea de alojar contenido que el sitio web necesita en servidores ajenos: ¿qué pasaría si el servidor se cayera? Bien, que los server racks de Google se caigan es algo que pocas veces se ha visto pero por si desconfiamos Google nos permite descargarnos los .ttf y alojarlos en nuestro servidor. En las fases iniciales del desarrollo podemos trabajar con los ficheros alojados en los servidores de Google y una vez finalizado el sitio los movemos a nuestro servidor.

Google nos da otra razón a favor: han aumentado las fuentes. Aquí está la versión 2 de la librería.

Guía Cirugía

Colaboro conjuntamente con Net Midas, en el aspecto técnico (me encargo de la programación e implementación) del interesante proyecto «Guía Cirugía». Guía Cirugía será un directorio donde usuarios de todo el mundo podrán ponerse en contacto con cirujanos plásticos de toda América Latina y España. Entre otros aspectos, el portal también será un punto de encuentro entre usuarios, un lugar donde los cirujanos publicarán artículos especializados, donde los usuarios podrán plantear sus dudas a los cirujanos, incluirá una sección para cirujanos y más funcionalidades y apartados de los cuales les iremos informando aquí en el blog de VIC Services.

Si bien la colaboración será inicialmente en el desarrollo de todo el apartado técnico pero espero poder aportar también ideas y conocimientos en otros aspectos.

¡Les mantendré informados!

Mis primeros 4 scripts en Python.

Os presento mis primeros 4 scripts en Python. Me parece un lenguaje muy interesante con unas librerías muy potentes, creo que en parte es debido a que es fácil portar librerías de C a Python y en este lenguaje se ha programado de todo. Desde luego que no son para un manual de programación en Python, os los presento solo para que veáis la potencia del lenguaje.

Lo que hace éste script está comentado en el mismo código:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

# El script espera dos argumentos:
# 1 - Una fecha en formato '%d-%m-%Y %H:%M', por ejemplo '21-01-2011 11:40'
# Mostrará sólo los ficheros que se hayan modificado después de esta fecha.
# 2 - Un path en formato *nix, por ejemplo '/home/vic/python'. No acepta '~' para referirse a 'home'.
# Buscará recursivamente, subdirectorios incluidos, sin profundizar en enlaces simbólicos, todos los ficheros a partir de ese path.
#
# os.path.getmtime -> Da el número de segundos desde epoch (medianoche UTC desde el 1 de enero de 1970).
# time.strptime devuelve una estructura time (struct_time).
# time.mktime(struct_time) devuelve el nº de segundos desde epoch. time.localtime(segundos_desde_epoch) hace la función inversa.
# El nº de segundos desde epoch se expresa en coma flotante.
#
#Para la fecha de creación de un fichero o dir: stat -c %w nombre
#Para saber la fecha de la última molificación: stat -c %y nombre
#Cuando se crea un fichero dentro de un directorio se modifica solo la fecha de modificacion del dir y no la de creación.
#Cuando se modifica un fichero dentro de un directorio no se altera la fecha de modificación ni de creación del dir.

import os, time, sys
from sys import argv

stModDesde = time.strptime(argv[1], '%d-%m-%Y %H:%M')
fModDesde = time.mktime(stModDesde)

top = argv[2]
for root, dirs, files in os.walk(top, topdown=False):
    for name in files:
        elemPath = os.path.join(root, name)
        fModificado = os.path.getmtime(elemPath)
        if (fModificado > fModDesde):
            print 'Fichero ' + elemPath + ' Modificado:' + time.ctime(fModificado)
#     for name in dirs:
#         elemPath = os.path.join(root, name)
#         Si queremos trabajar también con los directorios descomentar este for / in

¡Daros cuenta de que tan solo son 11 líneas! Aquí una función en Object Pascal que hice hará unos 7 años que aun teniendo que hacer algo más sencillo (buscar un fichero en el directorio actual y subdirectorios) era más compleja: recursiva y con más líneas de código.

unit Llibreria;

interface
uses
SysUtils, Classes, Forms;

procedure busca(path: string; carpet: string; coincidencias: TStrings);

implementation

procedure busca(path: string; carpet: string; coincidencias: TStrings);
    var
    sr: TSearchRec;
    res: byte;
    lg: integer;
    begin
        lg:=strLen(PChar(path)); //lg:=length(path);
        path:=path+carpet;
        res:=FindFirst(path, faAnyFile, sr);
        if (res=0) then coincidencias.Add(copy(path,1,lg) + sr.Name);
        FindClose(sr);
        path:=copy(path,1,lg) + '*.*';
        res:=FindFirst(path, faAnyFile, sr);
        while (res=0) do begin
            if (((sr.Attr and faDirectory)<>0) and (sr.Name[1]<>'.')) then begin
                path:=copy(path,1,lg);
                path:=path + sr.Name + '\';
                application.ProcessMessages;
                busca(path,carpet,coincidencias);
            end;
            res:=FindNext(sr);
        end;
    FindClose(sr);
    path:=copy(path,1,lg);
    end;
end.

Aquí un script para criptografía simétrica, usa el algoritmo Blowfish. El modo de cifrado es CTR, pero no le hagáis mucho caso a la función del contador. Una vez más creo que los comentarios ya explican bastante. (Python es el primer lenguaje en que me veo obligado a usar más líneas de comentarios que de código)

#! /usr/bin/env python
# -*- coding: latin-1 -*-

# El script espera 3 argumentos:
# 1º: Encriptar / Desencriptar: e / d
# 2º y 3º: paths a ficheros.
#     Con 'e' el 2º parámetro es el fichero a encriptar y el 3º el del encriptado (el resultado).
#     Con 'd' el 2º parámetro es el fichero encriptado y el 3º el desencriptado (el resultado).
#     ATENCIÓN: ¡en ambos casos el fichero resultante se sobreescribirá si ya existe!
#
# A continuación pedirá la clave.

from Crypto.Cipher import Blowfish
from os import path
from sys import argv, exit
import getpass

if not path.isfile(argv[2]):
    exit(argv[2] + " no existe o no es un fichero.")

# Añadir 'b' (binary) lo hace portable a Windows que sí distingue entre ficheros de texto y binarios,
# permitiendo encriptar tanto ficheros de texto como binarios.
f1 = open(argv[2], 'rb')

passw = getpass.getpass("Contraseña:")

class contador(object):
    def __init__(self, inicio):
        self.cont = inicio
    def next(self):
        self.cont = self.cont + 1
        sCont = str(self.cont)
        while len(sCont) < 8:
            sCont = sCont + '0'
        return sCont

c = contador(0)

oBlow = Blowfish.new(passw, Blowfish.MODE_CTR, counter=c.next)
# El primer param. es la clave. El segundo es el modo.
# Parece que el modo ECB es el más inseguro y CTR el más seguro. Ver http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
# El tercer parámetro no es opcional en el caso de CTR: requiere el contador. Debe ser una cadena de 8 carácteres.
# Se instancia el objeto antes del if pues el contador debe ser el mismo para la encriptación y la desencriptación.
# Usar algo como "sAleatoria = os.urandom(8)" tiene el inconveniente de tener que almacenar en el fichero ese contador para luego poder desencriptar.

if argv[1] == 'e':
    texto = f1.read() # Texto a encriptar.

    # La cadena a encriptar debe tener una longitud que sea múltiple de 8. Independientemente de la extensión de la clave.
    while (len(texto) % 8 != 0):
        texto = texto + chr(3) # El ASCII 3 es End of TeXt (ETX).

        textoEncriptado = oBlow.encrypt(texto)
        #print "Texto encriptado: " + textoEncriptado
        #textoDesencriptado = oBlow.decrypt(textoEncriptado)
        #print "Texto desencriptado: " + textoDesencriptado
        f2 = open(argv[3], 'wb') # Sobreescribe el fichero si ya existe.
        f2.write(textoEncriptado)
        f1.close()
        f2.close()
elif argv[1] == 'd':
    textoEncriptado = f1.read()
    texto = oBlow.decrypt(textoEncriptado)
    f2 = open(argv[3], 'wb')
    f2.write(texto)
    f1.close()
    f2.close()
else:
    print "Opción desconocida. 'e' para encriptar y 'd' para desencriptar"

¿Y Python para la web? He aquí un pequeño script que lee todas las referencias de productos de un fichero csv para después buscar en la web de la empresa las imágenes que les corresponden y guardarlas en el disco duro.

Creo que con las librerías «mechanize» y «BeautifulSoup» podemos construir fantásticas aplicaciones de testeo web, de una manera más rápida, eficiente y consumiendo muchos menos recursos que con herramientas como Selenium. Aquí el código:

#! /usr/bin/env python
# coding=utf-8

import mechanize
import cookielib
from BeautifulSoup import BeautifulSoup
import urllib

br = mechanize.Browser()

cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)
#Inicio configuración browser
br.set_handle_equiv(True)
br.set_handle_gzip(False)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)

#Para debug:
#br.set_debug_http(True)
#br.set_debug_redirects(True)
#br.set_debug_responses(True)

# User-Agent (no es Linux! pero lo engañamos O:-)
br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]

br.set_proxies({'http' : '192.168.4.4:3128'})

# Fin configuración browser
# Abre fichero de referencias:
f = open('/cygdrive/c/users/vic/documents/Empresa/Empresa.csv', 'r')
fContent = f.readlines()
for ref in fContent:
    ref = ref.strip()
    print "'" + ref + "'"
    #Busca por cada referencia su imagen:
    response1 = br.open("http://www.empresa.com/en/search/node/" + urllib.quote(ref))
    #print br.title() Título de la página
    #print response1.info() # Cabeceras
    #print response1.read() # HTML
    # Si no obtenemos HTML da un error:
    if not br.viewing_html():
        continue
    sHtml = response1.read()
    if sHtml.find('http://www.empresa.com/en/products/') == -1:
        continue
    #Sigue el link del resultado de la búsqueda:
    response1 = br.follow_link(url_regex = "http://www.empresa.com/en/products/")
    if not response1:
        continue;
    sHtml = response1.read()
    #Pone el HTML en la sopa para procesarlo:
    soup = BeautifulSoup(sHtml)
    #Busca la imagen y obtiene su valor src
    bsTagImg = soup.find(id='productimage')
    src = bsTagImg['src']
    #Descarga la imagen en /tmp
    fTemp = br.retrieve(src)[0]
    #La copiamos al directorio que queramos:
    fh = open(fTemp, 'rb')
    fContentImg = fh.read()
    f2 = open('/home/vic/python/ref_' + ref + '.jpg', 'wb')
    f2.write(fContentImg)
    fh.close()
    f2.close()
f.close()

Realmente un lenguaje potente, ¿verdad?

Sitio web para un amigo

Acabo de realizar un sitio web para que un amigo dé a conocer los Yorkshires que cría y pone a la venta. Aquí pueden ver el sitio. Dispone de un sencillo back office a modo de gestor de contenidos para añadir, modificar y borrar los contenidos de la web. Debido a su sencillez y también para experimentar, quise desarrollarlo íntegramente en PHP y MySQL partiendo de cero.