Insertando imágenes en WXTABLE

Figura
Figura. Imagen en WXTABLE

En WXTABLE insertamos imágenes en la celda con la propiedad de estilo CSS background-image, como se observa en la Figura, donde hemos insertado una imagen PNG de 200×133 píxeles.

El estilo CSS tiene además otras propiedades que afectan a la presentación: el origen o inicio background-origin con valor inicial border-box; la posición background-position con valor inicial top left; el recorte background-clip con valor inicial border-box; la repitición background-repeat con valor inicial no-repeat; el tamaño background-size con valor inicial auto. Todas estas pueden configurarse en WXTABLE. No entraré a explicarlas aquí pudiendo consultar el tema Fondos CSS3.

Además en WXTABLE también tenemos una utilidad para ajustar la celda al tamaño original de la imagen. Se puede ajustar el ancho, el alto y ambos, como hicimos en ese ejemplo usando esta opción de ajustar ambos lados. El código JSON del ejemplo es el siguiente, donde se observa que la celda fue ajustada a width:200px; height:133px.

{
    "sheets": {
        "Tab1": {
            "attributes": {},
            "styles": {
                "1,1": "background-image:var(--img0);height:133px;width:200px;"
            },
            "values": [
                ["1", "2"],
                ["", ""],
                ["", ""]
            ]
        }
    },
    "images": [
        "data:image/png;base64,iVBORw0KGgoAAAANS...RK5CYII="
    ]
}

En este ejemplo la única propiedad CSS que se usa es background-image:var(--img0) pues las otras se aplican con su valor inicial, haciendo que la imagen se posicione en la esquina superior izquierda, sin recorte, sin repetición y su tamaño se ajustará automáticamente. La expresión var(--x) es una función CSS que recupera una variable CSS que se ha definido previamente con algo como --x:rgb(255,0,0). En el Gestor de Tablas se usa para buscar la imagen con índice cero en el array images, índice que se recupera del número al final en --img0.

Aunque CSS permite incluir una URL en la propiedad background-image, por ahora no lo he contemplado en el Gestor de Tablas, forzando a que sólo se permite incluir imágenes como data:image/*;base64,..., donde "*" significa cualquier tipo de imagen. El Gestor de Tablas permite cargar imágenes desde el ordenador y obtener sus datos en formato base64. También puede obtener estos datos con la herramienta Web Tools online: Compositor de imágenes.

El array de imágenes contiene todas las del libro. Así si una imagen se repite en una o más hojas, sus datos sólo estarán disponibles una única vez evitando sobrecargar el archivo, pues las imágenes se guardan con sus datos.

Insertando imágenes en XLSX

Figura
Figura. Imagen exportada a XLSX en GoogleSheet

Si insertar imágenes con CSS puede resultar algo no simple dado que influyen varias propiedades CSS que afectan a la presentación, su exportación a XLSX se complica pues en XLSX también hay muchas cosas que afectarían. Y además el comportamiento en GoogleSheet y EXCEL web no parece que sea el mismo. En principio sólo voy a considerar una imagen insertada en WXTABLE con las propiedades CSS iniciales y además con ajuste del tamaño de la celda al tamaño de la imagen.

En Figura puede ver el resultado de la exportación del ejemplo a GoogleSheet, donde se consigue un resultado similar.

Figura
Figura. Imagen exportada a XLSX en EXCEL

El resultado en EXCEL también es aceptable. Pero aquí empezamos a ver la primera diferencia. En GoogleSheet la imagen está efectivamente insertada en el fondo de la celda. Vea que no aparecen los selectores de objeto. Pero EXCEL no contempla esto y la inserta como un objeto encima de la celda, apareciendo los selectores para cambiar la posición y el tamaño del objeto. Tiene el tamaño vertical de la celda aunque es ligeramente más estrecha.

El comportamiento de EXCEL es insertar las imágenes como objetos XLSX que se ubican en una capa renderizada por encima de las celdas. También pueden insertarse formas, dibujos, gráficas y otras cosas. Esta característica de capas para insertar objetos no se contempla en WXTABLE. Pueden insertarse gráficas pero se ubican en una celda aunque puede extenderse su contenido, tal como se explica en el apartado texto extendido de un tema anterior. En cualquier caso en esta versión del módulo exportador no he contemplado la posibilidad de exportar las gráficas debido a su complejidad.

Figura
Figura. Imagen sobre celdas en GoogleSheet

Si quisiéramos que la imagen en GoogleSheet se ubicara como un objeto, sólo tenemos que abrir el menú y seleccionar Colocar imagen sobre celdas, apareciendo entonces los selectores de objeto como se observa en la Figura. En la parte superior derecha aparece también un menú con tres puntos que nos permite hacer algunas cosas con el objeto, como volver a colocar la imagen en la celda.

Archivos XML para insertar imágenes en XLSX

Figura
Figura. Archivos XML de un XLSX con imagen

En el tema XLSX básico vimos los archivos mínimos que se necesitan para empaquetar el ZIP de un XLSX. Cuando insertamos imágenes necesitaremos más archivos. En la Figura puede ver la lista de archivos del ejemplo que estamos viendo. Se agregan los últimos cuatro archivos que podemos ver en esa imagen.

Empecemos por lo más sencillo. El archivo xl/media/image1.png contiene los datos binarios del archivo de imagen. Se guardan en un ArrayBuffer recuperando los bytes desde el base64 que teníamos en el array de imágenes. Se representa en la herramienta como un array de números 137,80,78,71,13,10,.... Observe que la primera imagen se pone en el archivo con nombre image1.png. Si hubiera una segunda imagen en el array de imágenes y fuera un JPG tendríamos un segundo archivo image2.jpg y así sucesivamente. En el array de imágenes se indexan desde cero y aquí desde uno.

Veamos ahora los dos archivos de relaciones entre las distintas partes de un XLSX. El primero es xl/worksheets/_rels/sheet1.xml.rels:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
    <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
    Target="../drawings/drawing1.xml"/>
</Relationships>

La configuración de la primera imagen se encuentra en el archivo drawing1.xml que veremos con más detalle después. El archivo de relaciones anterior relaciona drawing1.xml con sheet1.xml, de tal forma que esa hoja 1 puede encontrar los datos para presentar una imagen en el archivo drawing1.xml.

El otro archivo de relaciones es xl/drawings/_rels/drawing1.xml.rels:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
    <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
    Target="../media/image1.png"/>
</Relationships>

Es un archivo de relaciones para que drawing1.xml pueda encontrar los datos de la propia imagen en el archivo ../media/image1.png.

Drawing para configurar imagen en XLSX

El elemento <drawing r:id="rId1"/> se inserta en el archivo xl/worksheets/sheet1.xml como se observa para el ejemplo que estamos viendo.

<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
    <dimension ref="A1:B2"/>
    <sheetViews>
        <sheetView workbookViewId="0" ...></sheetView>
    </sheetViews>
    <sheetFormatPr customHeight="1" ..."/>
    <cols count="2">
        ...
    </cols>
    <sheetData>
        <row r="1" hidden="0" customHeight="1" ht="100">
            <c r="A1"/>
            <c r="B1"/>
        </row>
    </sheetData>
    <drawing r:id="rId1"/>
</worksheet>

Si el ejemplo tuviera más de una imagen sólo habría un elemento drawing en esa hoja, pues en el archivo de relaciones xl/drawings/_rels/drawing1.xml.rels se relacionarían las dos imágenes Target="../media/image1.png" para la imagen 1 y otra segunda Target="../media/image2.jpg" si por ejemplo la segunda fuese un JPG.

Cuando la aplicación que abra el XLSX observe la referencia r:id="rId1" podrá resolver en los archivos de relaciones el archivo drawing1.xml con la configuración para presentar la imagen. Y la propia imagen en ../media/image1.png.

Veamos ahora el archivo de configuración de imagen xl/drawings/drawing1.xml.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xdr:wsDr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing">
    <xdr:oneCellAnchor>
        <xdr:from>
            <xdr:col>0</xdr:col>
            <xdr:colOff>0</xdr:colOff>
            <xdr:row>0</xdr:row>
            <xdr:rowOff>0</xdr:rowOff>
        </xdr:from>
        <xdr:ext cx="1838325" cy="1260475"/>
        <xdr:pic>
            <xdr:nvPicPr>
                <xdr:cNvPr name="image1.png" id="0"/>
                <xdr:cNvPicPr preferRelativeResize="0"/>
            </xdr:nvPicPr>
            <xdr:blipFill>
                <a:blip r:embed="rId1" cstate="print"/>
                <a:stretch>
                    <a:fillRect/>
                </a:stretch>
            </xdr:blipFill>
            <xdr:spPr>
                <a:prstGeom prst="rect">
                    <a:avLst/>
                </a:prstGeom>
                <a:noFill/>
            </xdr:spPr>
        </xdr:pic>
        <xdr:clientData fLocksWithSheet="0"/>
    </xdr:oneCellAnchor>
</xdr:wsDr>

En este archivo se incluirán tantos elementos <xdr:oneCellAnchor> como imágenes tenga la hoja. Esto es así en GoogleSheet, pues en EXCEL usará el elemento <xdr:twoCellAnchor> cuando se inserta en esa aplicación una imagen. He optado en la exportación por el primer elemento, pues EXCEL también parece leerlo correctamente. Y además GoogleSheet la inserta por defecto en la celda y no sobre las celdas, comportamiento equivalente al de WXTABLE. Esto es así a excepción del caso de celdas combinadas, donde GoogleSheet la insertaría sobre las celdas, no pudiendo aún conocer cuál es el mótivo.

Antes de seguir he de aclarar que no he invertido más tiempo en entender todos los detalles del drawing. Esto se usa para insertar imágenes y muchos otros objetos como dibujos, formas geométricas, gráficas y otras cosas. Son objetos que se insertan en una capa sobre las celdas. Pero eso no es posible en WXTABLE. Bueno, sería posible con CSS hacerlo, pero el Gestor de Tablas WXTABLE perdería su finalidad que no es otra que crear tablas que se puedan exportar fácilmente a HTML. Las imágenes si entran en ese objetivo dado que se insertan en el fondo de la celda usando la propiedad CSS background-image.

Así que me he limitado a usar los recursos XLSX mínimos para poder exportar una WXTABLE. En el elemento <xdr:oneCellAnchor> sólo me interesó saber que habría de modificar el elemento <xdr:from> con la posición fila y columna donde se insertará la imagen y las dimensiones con el elemento <xdr:ext>. El resto lo incluyo pues parece que es necesario para la exportación, pero realmente no me he puesto a estudiar que significa. Son, obviamente, elementos que configuran el objeto, pero resulta que tendría que ver que significan a pesar de que quizás no podría aplicarlo en WXTABLE. Me he limitado a copiarlo de una hoja con imagen generada en GoogleSheet.

Los elementos <xdr:row>0</xdr:rowl> y <xdr:col>0</xdr:col> definen la celda donde se posiciona la imagen. Se indexan desde cero, así que la fila 0 y la columna 0 es la primera celda superior izquierda.

Los elementos <xdr:rowOff>0</xdr:rowOff> y <xdr:colOff>0</xdr:colOff> definen el desplazamiento (offset) dentro de esa celda. Digamos que el objeto, la imagen en este caso, está en una capa por encima de las celdas. Pero se supone anclada a la celda fila 0 y columna 0 si la esquina superior izquierda se ubica en el área de esa celda. El desplazamiento nos dice cuánto está desplazada desde esa esquina. Esto es algo que aún no he adaptado en la exportación por lo que siempre apareceran como no desplazadas. En una mejora que tendría que hacer habría de intentar usar las propiedades CSS adicionales como background-origin y background-position para traducirlas usando ese desplazamiento.

Las dimensiones de la imagen se definen con <xdr:ext cx="1838325" cy="1260475">. Esos valores son EMU que son las siglas de English Metric Unit. Se define en OOX en el apartado 20.1.2.1 EMU Unit of Measurement, página 2738. Tal como dice ese documento, La unidad EMU se creó para poder dividir uniformemente en unidades métricas e inglesas, con el fin de evitar errores de redondeo durante el cálculo. El uso de EMU también facilita un sistema más fluido de conmutación e interoperabilidad entre diferentes lugares utilizando diferentes unidades de medida. Los EMU definen un sistema de coordenadas de alta precisión basado en números enteros. Se define 1 emu = (1/914400) US inch = (1/36000) cm.

Para exportar desde HTML y CSS hemos de tener en cuenta que las medidas pueden ser puntos pt, píxeles px, EM em, pulgadas (inches) in, milímetros mm y centímetros cm. Para facilitar las conversiones hice una tabla en el módulo exportador incluyendo los EMU emu:

const measurements = {
    pt: {pt: 1, px: 16/12, em: 1/12, in: 1/72, mm: 25.4/72, cm: 2.54/72, emu: 914400/72},
    px: {pt: 12/16, px: 1, em: 1/16, in: 12/(16*72), mm: 12*25.4/(16*72), cm: 12*2.54/(16*72), emu: 914400*12/(72*16)},
    em: {pt: 12, px: 16, em: 1, in: 12/72, mm: 12*25.4/72, cm: 12*2.54/72, emu: 914400*12/72},
    in: {pt: 72, px: 72*16/12, em: 72*1/12, in: 1, mm: 25.4, cm: 2.54, emu: 914400},
    mm: {pt: 72/25.4, px: 72*16/(25.4*12), em: 72/(25.4*12), in: 1/25.4, mm: 1, cm: 0.1, emu: 914400/25.4},
    cm: {pt: 72/2.54, px: 72*16/(2.54*12), em: 72/(25.4*12), in: 1/2.54, mm: 10, cm: 1, emu: 914400/2.54},
    emu: {pt: (1/914400)*72, px: (1/914400)*(72*16/12), em: (1/914400)*(72*1/12), in: 1/914400, mm: 25.4/914400, cm: 2.54/914400, emu: 1}
};

La imagen del ejemplo tiene unas medidas originales de 200 × 133 px. La conversión de px a emu según la tabla anterior produce el resultado 1838325 × 1260475 emu, valores que se usan en el elemento <xdr:ext cx="1838325" cy="1260475">. Los EMU también se usan para definir los desplazamientos <xdr:rowOff>0</xdr:rowOff> y <xdr:colOff>0</xdr:colOff>.

Y hasta aquí todo lo que he podido aprender sobre lo básico para exportar imágenes a XLSX. Queda mucho por aprender, pero quedará para otra ocasión si se presenta.