forked from 1b-agent/opencadstudio-upstream
feat(ole): render EMF/WMF OLE presentations
OLE2FRAME entities whose compound file carries a metafile presentation (Excel/Word previews, scanned WMF pages) drew only their border frame — the old decoder scanned the raw blob for a contiguous BMP, which the CFB sector layout almost never provides. acadrust now extracts the picture bytes tagged by kind; the new scene/model/gdi player rasterizes EMF and WMF record streams on the CPU (pens/brushes, polygons, paths, DIB blits, clipping, transforms) with text drawn through the existing system-font glyph cache, and image_model feeds the result into the raster pipeline. Rasters (BMP/PNG/JPEG/GIF, CF_DIB) decode as before via the image crate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1cfb856224
commit
e8411c1b6d
10 files changed
+2088
-42
No files matched your search
@@ -182,12 +182,13 @@ impl ImageModel {
|
||||
}
|
||||
|
||||
impl ImageModel {
|
||||
/// Build an ImageModel from an OLE2FRAME's embedded presentation bitmap.
|
||||
/// Returns `None` when the frame is degenerate or carries no decodable
|
||||
/// bitmap (e.g. a metafile-only OLE), so the caller falls back to the frame
|
||||
/// placeholder.
|
||||
/// Build an ImageModel from an OLE2FRAME's embedded presentation.
|
||||
/// The blob's compound file is parsed for the native raster or the cached
|
||||
/// metafile picture (rasterized by the `gdi` player); returns `None` when
|
||||
/// the frame is degenerate or nothing in the blob decodes, so the caller
|
||||
/// falls back to the frame placeholder.
|
||||
pub fn from_ole2frame(ole: &acadrust::entities::Ole2Frame) -> Option<Self> {
|
||||
let (pixels, width, height) = decode_ole_bitmap(&ole.binary_data)?;
|
||||
let (pixels, width, height) = super::ole_pres::decode(&ole.binary_data)?;
|
||||
|
||||
// Frame rectangle in WCS. `upper_left`/`lower_right` name the diagonal;
|
||||
// normalise to left/right/top/bottom so the bitmap sits upright.
|
||||
@@ -235,42 +236,6 @@ impl ImageModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract and decode the presentation BMP embedded in an OLE2FRAME data blob.
|
||||
/// The blob carries the OLE object's cached bitmap (a `BITMAPFILEHEADER` "BM"
|
||||
/// followed by a DIB); scan for a self-consistent one and decode it. Returns
|
||||
/// `None` when no valid BMP is present (a metafile/other-format OLE).
|
||||
fn decode_ole_bitmap(data: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
|
||||
let mut i = 0usize;
|
||||
while i + 54 <= data.len() {
|
||||
if &data[i..i + 2] != b"BM" {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let file_size = u32::from_le_bytes(data[i + 2..i + 6].try_into().unwrap()) as usize;
|
||||
let dib_size = u32::from_le_bytes(data[i + 14..i + 18].try_into().unwrap());
|
||||
// A plausible BITMAPFILEHEADER points at a known DIB-header size.
|
||||
if !matches!(dib_size, 12 | 40 | 52 | 56 | 64 | 108 | 124) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let end = if file_size >= 54 && i + file_size <= data.len() {
|
||||
i + file_size
|
||||
} else {
|
||||
data.len()
|
||||
};
|
||||
if let Ok(img) = image::load_from_memory_with_format(&data[i..end], image::ImageFormat::Bmp)
|
||||
{
|
||||
let rgba = img.to_rgba8();
|
||||
let (w, h) = rgba.dimensions();
|
||||
if w > 0 && h > 0 {
|
||||
return Some((rgba.into_raw(), w, h));
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Decoded RGBA image shared between the raster pipeline and the
|
||||
/// unresolved-reference probe. Cheap to clone — the pixels are `Arc`-shared.
|
||||
#[derive(Clone)]
|
||||
|
||||
Reference in new issue
Block a user