Un libro de Excel puede llevar imágenes EMF y WMF, y la manera convencional de dibujar una es entregar el flujo de bytes al reproductor de metarchivos del sistema operativo. Es una decisión que vale mirar de frente: un metarchivo es un flujo de comandos serializado para una API de gráficos, y reproducirlo significa dejar que un archivo que llegó por correo maneje el controlador de gráficos. HotXLS toma la otra vía. XLSDecodeVectorScene analiza el metarchivo por sí mismo, valida la cabecera, el tamaño de cada registro, el total de registros declarado y la colocación exacta del registro de fin de archivo, rechaza de plano los registros escape y devuelve una TXLSVectorScene de comandos de dibujo primitivos que los backends de Canvas y de SVG reproducen con su propio código. No hay reproducción por el controlador en ningún punto
El intercambio es cobertura por contención. Una lista blanca de comandos orientada a rectángulos no reproducirá todo metarchivo que un diseñador pueda crear, así que la escena reporta cuántos registros de dibujo no pudo representar y quien llama decide qué hacer al respecto. Para un proceso de servidor que renderiza documentos que él no creó, ese intercambio está del lado correcto
Por qué la reproducción de metarchivos pega mal con entrada no confiable
Porque el formato no es una imagen, es un programa. Un flujo de registros EMF manipula una pila de estados de contexto de dispositivo, asigna y selecciona objetos de una tabla de handles, y puede llevar registros escape cuya carga útil se pasa a un controlador de dispositivo. Reproducirlo ejercita caminos de la pila de gráficos de la plataforma que se escribieron asumiendo que el metarchivo venía de una aplicación cooperadora en la misma máquina. Cuando la entrada es un adjunto de hoja de cálculo, ese supuesto desaparece, y ningún cuidado dentro de la biblioteca de hojas de cálculo ayuda porque la biblioteca no es el componente que analiza
Es el mismo razonamiento que gobierna la capa del contenedor. Un libro es un archivo ZIP, y HotXLS valida su directorio central en lugar de confiar en los desplazamientos declarados, como se describe en el artículo de validación del end-of-central-directory ZIP. Las cargas útiles de metarchivo son la siguiente capa del mismo problema
Qué comprueba el decodificador antes de dibujar nada
La validación es estructural y ocurre por adelantado, porque un analizador que empieza a dibujar y valida sobre la marcha ya actuó sobre datos que no verificó. La cabecera debe coincidir de forma estricta y no plausible. Cada registro debe declarar un tamaño que quepa en el búfer restante y sea lo bastante grande para sus campos fijos. El recuento de registros que declara la cabecera debe coincidir con los registros realmente presentes. El registro de fin de archivo debe estar exactamente donde termina el flujo, no apenas cerca, lo que cierra el truco de la basura final que esconde una segunda carga útil detrás de una imagen válida
Más allá de la estructura, el decodificador es fail-closed en la semántica. Los registros escape se rechazan, no se saltan. Un registro que cambia estado y que el decodificador no modela hace que la decodificación falle en lugar de ignorarse, porque ignorar un cambio de estado significa que cada comando de dibujo posterior se ejecuta en un estado que el archivo no pidió, y el resultado es una imagen que está mal de una forma que nadie puede predecir. Los registros de dibujo fuera del conjunto de comandos soportado son otro asunto: esos se cuentan y se saltan, porque una forma ausente es un hueco visible y reportable y no una corrupción silenciosa
Los presupuestos son parte del contrato del formato
Los formatos vectoriales tienen su propia versión de la bomba de descompresión. Unos cuantos kilobytes de registros pueden declarar polilíneas con cientos de millones de puntos, o una imagen cuyas dimensiones declaradas se multiplican en terabytes. Los límites tienen entonces que ser constantes explícitas y no aquello que la máquina logre sobrevivir
// De lxVectorScene: el presupuesto de decodificación, enunciado y no implícito
XL_VECTOR_MAX_RECORDS = 1000000;
XL_VECTOR_MAX_HANDLES = 4096;
XL_VECTOR_MAX_DC_DEPTH = 32;
XL_VECTOR_MAX_COMMANDS = 100000;
XL_VECTOR_MAX_POINTS_PER_RECORD = 100000;
XL_VECTOR_MAX_TOTAL_POINTS = 2000000;
XL_VECTOR_MAX_TEXT_CHARS = 4096;
XL_VECTOR_MAX_TOTAL_TEXT_CHARS = 1000000;
XL_VECTOR_MAX_IMAGE_SIDE = 8192;
XL_VECTOR_MAX_IMAGE_PIXELS = 32 * 1024 * 1024;
XL_VECTOR_MAX_IMAGE_BYTES = 64 * 1024 * 1024;
XL_VECTOR_MAX_COORD = 1000000000;
Dos de estos merecen una nota. El tope de profundidad de contexto de dispositivo de 32 existe porque los registros SaveDC y RestoreDC se anidan, y un flujo desbalanceado puede apilar para siempre; 32 es generoso para metarchivos reales y barato de aplicar. El tope de coordenadas existe porque las coordenadas alimentan una transformación, y un valor cerca de los límites del rango de enteros produce un resultado transformado que es infinito o se desborda, después de lo cual todo cálculo de caja delimitadora aguas abajo es un sinsentido. Acotar coordenadas al momento del análisis es mucho más fácil de razonar que defender a cada consumidor de la geometría
Uso de la escena
El decodificador devuelve un objeto que ustedes poseen, un recuento de comandos, un tamaño nominal y un recuento de registros de dibujo que decidió no representar
uses
lxVectorScene;
var
Scene: TXLSVectorScene;
Error: WideString;
I: Integer;
begin
// Data contiene la carga útil de imagen cruda tomada del libro
if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
begin
// Rechazado: cabecera, límites, totales, colocación del EOF o un presupuesto
LogReject('metafile rejected: ' + Error);
Exit;
end;
try
if Scene.SkippedDrawRecords > 0 then
LogWarning(Format('%d drawing records outside the safe subset',
[Scene.SkippedDrawRecords]));
for I := 0 to Scene.Count - 1 do
case Scene.Commands[I].Kind of
xlsvcRectangle: DrawRect(Scene.Commands[I]);
xlsvcEllipse: DrawEllipse(Scene.Commands[I]);
xlsvcPolyline,
xlsvcPolygon,
xlsvcBezier: DrawPath(Scene.Commands[I]);
xlsvcText: DrawText(Scene.Commands[I]);
xlsvcImage: DrawImage(Scene.Commands[I]);
end;
finally
Scene.Free;
end;
end;
El registro de comandos lleva todo lo que un backend necesita y nada que requiera un dispositivo: presencia, color, ancho y estilo de la pluma; presencia y color del pincel; la geometría; y para el texto la cadena, el nombre de la fuente, el tamaño, los estilos y la alineación. Eso es lo que hace que la misma escena sirva tanto al renderizador de canvas en pantalla como al escritor de SVG, y es la razón de que la vía vectorial no diverja entre vista previa y exportación. El renderizado en pantalla de contenido de hojas en general se cubre en el artículo de renderizado con cuadrícula VCL personalizada
Rechazar una imagen no daña el libro
Una propiedad importante de este diseño es que una decodificación rechazada afecta solo al renderizado. La carga útil original se queda en el modelo, así que un libro que se abre y se vuelve a guardar saca sus imágenes de metarchivo byte a byte, aunque el decodificador seguro pudiera dibujarlas o no. La vía raster acotada existente también sigue disponible como respaldo. Dicho de otro modo, el analizador estricto condiciona lo que se ejecuta, no lo que se conserva, que es la distinción que permite que un cambio motivado por seguridad se publique sin convertirse en un cambio de pérdida de datos
El manejo de objetos de dibujo en general, incluidas las partes del modelo de objetos que sobreviven a las idas y vueltas intactas, se cubre en el artículo de gráficos, imágenes y dibujos
Dónde deja esto a un despliegue de servidor
Si renderizan libros subidos por usuarios en un servicio, la posición práctica ahora es defendible: las imágenes de metarchivo se analizan con código que pueden auditar, acotadas por constantes que pueden leer, y nunca se entregan a un controlador de gráficos. La advertencia honesta es la cobertura. Los metarchivos complejos producidos por herramientas de dibujo tocarán el contador de registros saltados, y la respuesta a eso es hacer visible el contador en lugar de ensanchar la lista blanca en silencio. Una imagen que se renderiza parcialmente y lo dice es una conversación de soporte; una imagen que se renderiza mal y no dice nada es un reporte de error de un cliente
HotXLS maneja XLS, XLSX, ODS y CSV de forma nativa en Delphi y C++Builder sin Excel instalado, y la misma filosofía de análisis acotado atraviesa sus capas de contenedor, fórmulas y dibujo. Los detalles de formato y seguridad están en la página del producto HotXLS Delphi spreadsheet component