{"id":1364,"date":"2020-02-20T03:58:19","date_gmt":"2020-02-20T02:58:19","guid":{"rendered":"https:\/\/lab.fawno.com\/?p=1364"},"modified":"2022-03-06T19:37:33","modified_gmt":"2022-03-06T18:37:33","slug":"","status":"publish","type":"post","link":"https:\/\/lab.fawno.com\/en\/2020\/02\/20\/renderizar-html-con-php-ffi-y-wkhtmltox\/","title":{"rendered":"","raw":""},"content":{"rendered":"","protected":false,"raw":""},"excerpt":{"rendered":"","protected":false,"raw":""},"author":1,"featured_media":1365,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"Georgia","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","_editorskit_title_hidden":false,"_editorskit_reading_time":5,"_editorskit_typography_data":[],"_editorskit_blocks_typography":"","_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","_es_post_content":"\n
Ando con un tema de conversi\u00f3n de contenidos para una web, es tema complicado del que no quisiera contar mucho. Implica un equipo de personas trabajando en remoto y publicaci\u00f3n autom\u00e1tica.<\/p>\n\n\n\n
Como no puede ser de otra manera, yo estoy a cargo de la publicaci\u00f3n autom\u00e1tica, y nos ha surgido un peque\u00f1o problema: el sitio web para pruebas s\u00f3lo es accesible en una de las oficinas, por lo que la herramienta que permite lanzar los contenidos contra el sitio de prueba para previsualizar los resultados no funciona en remoto.<\/p>\n\n\n\n
Entre las muchas soluciones que podr\u00edamos aplicar la que nos gustaba era la de renderizar el resultado del sitio de prueba y mostrar la imagen resultante a los usuarios de la herramienta.<\/p>\n\n\n\n
Existen muchas maneras de hacer un renderizado de html en PHP, bien con ImageMagick<\/a>, mediante Imagick<\/a>, bien convirtiendo el html en pdf... pero todas adolecen del mismo problema: no renderizan CSS o si lo hacen este tiene que ser incorporado, no enlazado.<\/p>\n\n\n\n Al final encontr\u00e9 wkhtmltopdf<\/a>, una maravilla de la linea de comando. No ser\u00eda la primera vez que ejecuto un comando desde PHP porque no he encontrado nada mejor.<\/p>\n\n\n\n Como dec\u00eda, wkhtmltopdf es una maravilla porque:<\/p>\n\n\n\n B\u00e1sicamente es un navegador en l\u00ednea de comandos como Links<\/a>, pero en lugar de ser en modo texto te renderiza la web en un pdf o imagen.<\/p>\n\n\n\n Con la primera prueba ya me convenci\u00f3. Sin embargo con este proyecto he tenido la ocasi\u00f3n de probar e implementar una serie de t\u00e9cnicas e ideas a las que le tenia ganas desde hac\u00eda mucho tiempo y que ya ir\u00e9 contando. Y resulta que el wk <\/em>me daba pie para probar otra t\u00e9cnica que ten\u00eda ganas de probar.<\/p>\n\n\n\n Se trata de la extensi\u00f3n FFI de PHP<\/a>. Mola porque entre otras cosas la documentaci\u00f3n est\u00e1 plagada de advertencias tipo:<\/p>\n\n\n\n Esta extensi\u00f3n es EXPERIMENTAL, all\u00e1 tu con lo que hagas con ella.<\/strong> Esta extensi\u00f3n es peligrosa, permite el domino del mundo. <\/strong><\/p>Documentaci\u00f3n de PHP\/FFI<\/cite><\/blockquote>\n\n\n\n As\u00ed que si es algo experimental y peligroso... \u00bfqu\u00e9 puede salir mal?.<\/p>\n\n\n\n \u00bfQue es la extensi\u00f3n FFI de PHP? pues otra maravilla porque te permite acceder a librer\u00edas escritas en C desde PHP sin necesidad de crear una extensi\u00f3n.<\/p>\n\n\n\n En este caso concreto wk <\/em>provee una librer\u00eda llamada wkhtmltox <\/em>para programar en C. Simplificando las cabeceras (los ficheros .h de C) y con una buena dosis de paciencia y de recordar c\u00f3mo demonios funcionaba C con las cadenas de caracteres y los punteros he podido hacer lo que me propon\u00eda.<\/p>\n\n\n\n Vamos, acceder a la librer\u00eda y hacer que te renderice una web en una imagen no es dif\u00edcil, pero yo no quer\u00eda crear un archivo, quiero crear una imagen que cargo en memoria y luego se la lanzo al cliente... o al menos esa es la idea por ahora.<\/p>\n\n\n\n Cuando al wk <\/em>no le das un nombre de fichero para que guarde la imagen, almacena esta en un buffer <\/em>interno. Mediante la funci\u00f3n En C no todas las cadenas de caracteres son iguales. Tienes cadenas de caracteres, strings <\/em>y cadenas binarias. Las dos primeras son muy similares, y para nuestro caso podemos considerarlas iguales... pero las cadenas binarias son otro asunto.<\/p>\n\n\n\n Una cadena binaria en C es un Para recuperar el buffer<\/em> interno lo m\u00e1s sensato, al menos en C, es hacer lo siguiente:<\/p>\n\n\n\n El punto dos es lo que se denomina pasar un par\u00e1metro por referencia, y en PHP\/FFI hay que pasar dicho par\u00e1metro as\u00ed: El punto tres es una de las grandes ventajas y justificaciones de los punteros, hay mucho tras ellos y su comprensi\u00f3n nunca ha sido sencilla, pero si los comprendes has alcanzado la iluminaci\u00f3n.<\/p>\n\n\n\n Bueno, tenemos nuestra cadena binaria en una variable que s\u00f3lo entiende FFI, \u00bfc\u00f3mo la convierto a una variable de PHP normal?<\/strong><\/p>\n\n\n\n Esa pregunta, estimado lector, es lo que me cost\u00f3 unas horas entender. Tantos a\u00f1os programando el lenguajes de alto nivel hacen que se te olviden los rudimentos de un lenguaje de niveles inferiores, y recordemos: C est\u00e1 s\u00f3lo un paso por encima de ensamblador, s\u00f3lo un paso. PHP, Java, Node y muchos otros lenguajes comunes hoy en d\u00eda est\u00e1n varios niveles por encima de C. Como si fuera la primera vez que me pasa<\/a>.<\/p>\n\n\n\n Lo que entend\u00ed r\u00e1pidamente es que para PHP una variable de tipo string es un puntero de tipo cadena Una vez que sabemos c\u00f3mo podemos hacer pasar una cadena binaria por una cadena normal podemos recuperar el contenido de la misma en una variable normal de PHP: Esta no es toda la historia ya que lo que consegu\u00eda no era la imagen completa, s\u00f3lo me recuperaba los 8 primeros bytes de la imagen. Como es l\u00f3gico estuve desconcertado un buen rato hasta que comprend\u00ed que la funci\u00f3n Me explico, las cadenas en C son muy sencillas: se ponen todos los caracteres uno detr\u00e1s de otro, lo normal, y se sabe donde termina porque a\u00f1ade un 0 (\\x00) al final. Era t\u00edpico que las cadenas tuvieran un l\u00edmite de 255 caracteres: el puntero tiene 8 bits, con ocho bits direccionamos 256 posiciones de memoria pero una la ocupa siempre el terminador de cadena (\\x00), por lo que nos quedan 255 que podemos usar para lo que queramos.<\/p>\n\n\n\n El funcionamiento anterior es muy pr\u00e1ctico y funciona muy bien... pero en una cadena binaria el 0 tambi\u00e9n es informaci\u00f3n v\u00e1lida, por lo que la idea del terminador de cadena ya no sirve y tenemos que almacenar la longitud de la cadena en otro sitio.<\/p>\n\n\n\nwkhtmltoimage_get_output<\/code> puedes obtener dicho buffer <\/em>interno.<\/p>\n\n\n\n
Recordemos C<\/h3>\n\n\n\n
unsigned char<\/code>, mientras que la cadena normal es
char<\/code>. Esto tiene su importancia porque PHP\/FFI considera strings <\/em>\u00fanicamente las cadenas de caracteres (
char<\/code>) y los string<\/em>, las cadenas binarias no son strings<\/em>. Pero me estoy saltando pasos.<\/p>\n\n\n\n
unsigned char *<\/code><\/li>
wkhtmltoimage_get_output<\/code> le pasas un puntero a la variable anterior: un puntero a un puntero,
unsigned char **<\/code><\/li>
FFI::addr($buffer)<\/code>. Dicho de otra forma, hay que llamar a la funci\u00f3n d\u00e1ndole la direcci\u00f3n a nuestro puntero.<\/p>\n\n\n\n
char *<\/code>, y una cadena binaria no es lo mismo, aunque
unsigned char *<\/code> se parezca. As\u00ed que el primer paso es hacer pasar una cadena binaria por una cadena normal: un casting de tipos<\/a> que decimos los programadores. Esto en c\u00f3digo es:
FFI::cast('char *', $buffer)<\/code>.<\/p>\n\n\n\n
FFI::string(FFI::cast('char *', $buffer))<\/code>.<\/p>\n\n\n\n
FFI::string<\/code> lo que hace es copiar un bloque de memoria de un lado a otro y el problema es que en las cadenas binarias no sabemos d\u00f3nde termina el bloque a copiar si no le decimos exactamente cuanta informaci\u00f3n hay.<\/p>\n\n\n\n