mirror of
https://github.com/storytold/winprint.rs.git
synced 2026-11-11 12:55:22 +00:00
feat: add Chromium-style safe EMF playback
This commit is contained in:
5 files changed
+518
-65
No files matched your search
+1
-25
@@ -1,6 +1,7 @@
|
||||
use crate::printer::PrinterDevice;
|
||||
use crate::ticket::PrintTicket;
|
||||
use crate::ticket::ToDevModeError;
|
||||
use crate::utils::com::ComInitializer;
|
||||
use crate::utils::print_completion_source::PrintCompletionSource;
|
||||
use crate::utils::print_completion_source::PrintCompletionWaiter;
|
||||
use crate::utils::wchar;
|
||||
@@ -35,11 +36,8 @@ use windows::Win32::Storage::Xps::Printing::IPrintDocumentPackageTarget;
|
||||
use windows::Win32::Storage::Xps::Printing::IPrintDocumentPackageTargetFactory;
|
||||
use windows::Win32::Storage::Xps::Printing::PrintDocumentPackageTargetFactory;
|
||||
use windows::Win32::System::Com::CoCreateInstance;
|
||||
use windows::Win32::System::Com::CoInitializeEx;
|
||||
use windows::Win32::System::Com::CoUninitialize;
|
||||
use windows::Win32::System::Com::IConnectionPointContainer;
|
||||
use windows::Win32::System::Com::CLSCTX_ALL;
|
||||
use windows::Win32::System::Com::COINIT_MULTITHREADED;
|
||||
use windows::Win32::UI::Shell::SHCreateMemStream;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -85,28 +83,6 @@ pub struct DxgiPrintContext {
|
||||
_com_initializer: ComInitializer,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
struct ComInitializer {
|
||||
// Do not use empty struct.
|
||||
// Empty struct is easy to misuse.
|
||||
_dummy: (),
|
||||
}
|
||||
impl ComInitializer {
|
||||
fn new() -> ComInitializer {
|
||||
unsafe {
|
||||
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
||||
}
|
||||
ComInitializer { _dummy: () }
|
||||
}
|
||||
}
|
||||
impl Drop for ComInitializer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DxgiPrintContext {
|
||||
/// Create a new print context, used for printing via DXGI.
|
||||
pub fn new(
|
||||
|
||||
+130
-40
@@ -3,22 +3,27 @@ use crate::printer::FilePrinter;
|
||||
use crate::printer::PrinterDevice;
|
||||
use crate::ticket::PrintTicket;
|
||||
use crate::ticket::ToDevModeError;
|
||||
use crate::utils::emf::Emf;
|
||||
use crate::utils::pdfium::PdfiumCustomDocument;
|
||||
use crate::utils::pdfium::PdfiumGuard;
|
||||
use crate::utils::wchar;
|
||||
use scopeguard::defer;
|
||||
use std::cell::Cell;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::{fs::File, mem};
|
||||
use thiserror::Error;
|
||||
use windows::Win32::Foundation::RECT;
|
||||
use windows::{
|
||||
core::PCWSTR,
|
||||
Win32::Graphics::Gdi::{
|
||||
CreateDCW, DeleteDC, GetDeviceCaps, SetBrushOrgEx, SetGraphicsMode, SetStretchBltMode,
|
||||
SetViewportOrgEx, GET_DEVICE_CAPS_INDEX, GM_ADVANCED, HALFTONE, LOGPIXELSX, LOGPIXELSY,
|
||||
PHYSICALHEIGHT, PHYSICALOFFSETX, PHYSICALOFFSETY, PHYSICALWIDTH,
|
||||
Win32::{
|
||||
Graphics::Gdi::{
|
||||
CreateDCW, DeleteDC, GetDeviceCaps, SetBrushOrgEx, SetGraphicsMode, SetStretchBltMode,
|
||||
GET_DEVICE_CAPS_INDEX, GM_ADVANCED, HALFTONE, LOGPIXELSX, LOGPIXELSY, PHYSICALHEIGHT,
|
||||
PHYSICALOFFSETX, PHYSICALOFFSETY, PHYSICALWIDTH,
|
||||
},
|
||||
Storage::Xps::{AbortDoc, EndDoc, EndPage, StartDocW, StartPage, DOCINFOW},
|
||||
},
|
||||
Win32::Storage::Xps::*,
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -33,6 +38,27 @@ pub enum PdfiumPrinterError {
|
||||
/// Print ticket error.
|
||||
#[error("Print Ticker Error")]
|
||||
PrintTicketError(#[source] ToDevModeError),
|
||||
/// StartDoc failed.
|
||||
#[error("StartDocW failed (returned {0})")]
|
||||
StartDocFailed(i32),
|
||||
/// StartPage failed.
|
||||
#[error("StartPage failed for page {0} (returned {1})")]
|
||||
StartPageFailed(i32, i32),
|
||||
/// EndPage failed.
|
||||
#[error("EndPage failed for page {0} (returned {1})")]
|
||||
EndPageFailed(i32, i32),
|
||||
/// EndDoc failed.
|
||||
#[error("EndDoc failed (returned {0})")]
|
||||
EndDocFailed(i32),
|
||||
/// PDFium failed to load the document.
|
||||
#[error("PDFium failed to load the document (error {0})")]
|
||||
PdfiumLoadFailed(u32),
|
||||
/// EMF creation failed for a page.
|
||||
#[error("Failed to create EMF for page {0}")]
|
||||
EmfCreateFailed(i32),
|
||||
/// EMF playback failed for a page.
|
||||
#[error("Failed to playback EMF for page {0}")]
|
||||
EmfPlaybackFailed(i32),
|
||||
}
|
||||
|
||||
/// A printer that uses Pdfium to print PDF documents.
|
||||
@@ -58,6 +84,7 @@ const PRINT_DRIVER: PCWSTR = PCWSTR(
|
||||
impl FilePrinter for PdfiumPrinter {
|
||||
type Options = PrintTicket;
|
||||
type Error = PdfiumPrinterError;
|
||||
|
||||
fn print(
|
||||
&self,
|
||||
path: &Path,
|
||||
@@ -87,12 +114,14 @@ impl FilePrinter for PdfiumPrinter {
|
||||
defer! {
|
||||
let _ = DeleteDC(hdc_print);
|
||||
}
|
||||
|
||||
SetGraphicsMode(hdc_print, GM_ADVANCED);
|
||||
SetStretchBltMode(hdc_print, HALFTONE);
|
||||
// After setting the HALFTONE stretching mode,
|
||||
// an application must call the SetBrushOrgEx function to set the brush origin.
|
||||
// If it fails to do so, brush misalignment occurs.
|
||||
let _ = SetBrushOrgEx(hdc_print, 0, 0, None);
|
||||
|
||||
let mut doc_name = wchar::to_wide_chars(path.file_name().unwrap_or(path.as_ref()));
|
||||
let doc_info = DOCINFOW {
|
||||
cbSize: mem::size_of::<DOCINFOW>() as i32,
|
||||
@@ -101,43 +130,104 @@ impl FilePrinter for PdfiumPrinter {
|
||||
lpszOutput: PCWSTR::null(),
|
||||
lpszDatatype: PCWSTR::null(),
|
||||
};
|
||||
StartDocW(hdc_print, &doc_info);
|
||||
{
|
||||
let _pdfium_guard = PdfiumGuard::guard();
|
||||
let mut file = File::open(path).map_err(PdfiumPrinterError::FileIOError)?;
|
||||
let mut file_delegation = PdfiumCustomDocument::new(&mut file)
|
||||
.map_err(PdfiumPrinterError::FileIOError)?;
|
||||
let document = FPDF_LoadCustomDocument(file_delegation.as_mut(), ptr::null());
|
||||
defer! {
|
||||
FPDF_CloseDocument(document);
|
||||
}
|
||||
let page_count = FPDF_GetPageCount(document);
|
||||
for page_index in 0..page_count {
|
||||
let page = FPDF_LoadPage(document, page_index);
|
||||
defer! {
|
||||
FPDF_ClosePage(page);
|
||||
}
|
||||
StartPage(hdc_print);
|
||||
let get_attr =
|
||||
|kind: GET_DEVICE_CAPS_INDEX| -> i32 { GetDeviceCaps(hdc_print, kind) };
|
||||
let dpi_x = get_attr(LOGPIXELSX);
|
||||
let dpi_y = get_attr(LOGPIXELSY);
|
||||
let page_width = FPDF_GetPageWidth(page) * dpi_x as f64 / 72.0;
|
||||
let page_height = FPDF_GetPageHeight(page) * dpi_y as f64 / 72.0;
|
||||
let physical_width = get_attr(PHYSICALWIDTH) as f64;
|
||||
let physical_height = get_attr(PHYSICALHEIGHT) as f64;
|
||||
let scale =
|
||||
f64::min(physical_width / page_width, physical_height / page_height);
|
||||
let w = page_width * scale;
|
||||
let h = page_height * scale;
|
||||
let org_x = -get_attr(PHYSICALOFFSETX);
|
||||
let org_y = -get_attr(PHYSICALOFFSETY);
|
||||
let _ = SetViewportOrgEx(hdc_print, org_x, org_y, None);
|
||||
FPDF_RenderPage(hdc_print, page, 0, 0, w as i32, h as i32, 0, FPDF_PRINTING);
|
||||
EndPage(hdc_print);
|
||||
|
||||
let start_doc_ret = StartDocW(hdc_print, &doc_info);
|
||||
if start_doc_ret <= 0 {
|
||||
return Err(PdfiumPrinterError::StartDocFailed(start_doc_ret));
|
||||
}
|
||||
|
||||
let document_completed = Cell::new(false);
|
||||
defer! {
|
||||
if !document_completed.get() {
|
||||
let _ = AbortDoc(hdc_print);
|
||||
}
|
||||
}
|
||||
EndDoc(hdc_print);
|
||||
|
||||
let _pdfium_guard = PdfiumGuard::guard();
|
||||
let mut file = File::open(path).map_err(PdfiumPrinterError::FileIOError)?;
|
||||
let mut file_delegation =
|
||||
PdfiumCustomDocument::new(&mut file).map_err(PdfiumPrinterError::FileIOError)?;
|
||||
let document = FPDF_LoadCustomDocument(file_delegation.as_mut(), ptr::null());
|
||||
if document.is_null() {
|
||||
return Err(PdfiumPrinterError::PdfiumLoadFailed(FPDF_GetLastError()));
|
||||
}
|
||||
defer! {
|
||||
FPDF_CloseDocument(document);
|
||||
}
|
||||
|
||||
let get_attr = |kind: GET_DEVICE_CAPS_INDEX| -> i32 { GetDeviceCaps(hdc_print, kind) };
|
||||
let page_count = FPDF_GetPageCount(document);
|
||||
for page_index in 0..page_count {
|
||||
let page = FPDF_LoadPage(document, page_index);
|
||||
defer! {
|
||||
FPDF_ClosePage(page);
|
||||
}
|
||||
|
||||
let start_page_ret = StartPage(hdc_print);
|
||||
if start_page_ret <= 0 {
|
||||
return Err(PdfiumPrinterError::StartPageFailed(
|
||||
page_index,
|
||||
start_page_ret,
|
||||
));
|
||||
}
|
||||
let dpi_x = get_attr(LOGPIXELSX);
|
||||
let dpi_y = get_attr(LOGPIXELSY);
|
||||
let page_std_width = FPDF_GetPageWidth(page);
|
||||
let page_std_height = FPDF_GetPageHeight(page);
|
||||
let page_width = (page_std_width * dpi_x as f64 / 72.0).round() as i32;
|
||||
let page_height = (page_std_height * dpi_y as f64 / 72.0).round() as i32;
|
||||
let emf = Emf::new(
|
||||
hdc_print,
|
||||
(page_std_width * 2540.0 / 72.0).round() as i32,
|
||||
(page_std_height * 2540.0 / 72.0).round() as i32,
|
||||
|hdc_emf| {
|
||||
SetGraphicsMode(hdc_emf, GM_ADVANCED);
|
||||
FPDF_RenderPage(
|
||||
hdc_emf,
|
||||
page,
|
||||
0,
|
||||
0,
|
||||
page_width,
|
||||
page_height,
|
||||
0,
|
||||
FPDF_PRINTING,
|
||||
);
|
||||
true
|
||||
},
|
||||
)
|
||||
.map_err(|_| PdfiumPrinterError::EmfCreateFailed(page_index))?;
|
||||
|
||||
let paper_width = get_attr(PHYSICALWIDTH);
|
||||
let paper_height = get_attr(PHYSICALHEIGHT);
|
||||
let scale = f64::min(
|
||||
paper_width as f64 / page_width as f64,
|
||||
paper_height as f64 / page_height as f64,
|
||||
);
|
||||
let actual_width = (page_width as f64 * scale).round() as i32;
|
||||
let actual_height = (page_height as f64 * scale).round() as i32;
|
||||
let left = -get_attr(PHYSICALOFFSETX) + (paper_width - actual_width) / 2;
|
||||
let top = -get_attr(PHYSICALOFFSETY) + (paper_height - actual_height) / 2;
|
||||
let target_rect = RECT {
|
||||
left,
|
||||
top,
|
||||
right: actual_width + left,
|
||||
bottom: actual_height + top,
|
||||
};
|
||||
let page_result = emf
|
||||
.playback(hdc_print, target_rect)
|
||||
.map_err(|_| PdfiumPrinterError::EmfPlaybackFailed(page_index));
|
||||
let end_page_ret = EndPage(hdc_print);
|
||||
page_result?;
|
||||
if end_page_ret <= 0 {
|
||||
return Err(PdfiumPrinterError::EndPageFailed(page_index, end_page_ret));
|
||||
}
|
||||
}
|
||||
|
||||
let end_doc_ret = EndDoc(hdc_print);
|
||||
if end_doc_ret <= 0 {
|
||||
return Err(PdfiumPrinterError::EndDocFailed(end_doc_ret));
|
||||
}
|
||||
document_completed.set(true);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use windows::Win32::System::Com::{CoInitializeEx, CoUninitialize, COINIT_MULTITHREADED};
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct ComInitializer {
|
||||
// Do not use empty struct.
|
||||
// Empty struct is easy to misuse.
|
||||
_dummy: (),
|
||||
}
|
||||
impl ComInitializer {
|
||||
pub fn new() -> ComInitializer {
|
||||
unsafe {
|
||||
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
||||
}
|
||||
ComInitializer { _dummy: () }
|
||||
}
|
||||
}
|
||||
impl Drop for ComInitializer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
use crate::utils::com::ComInitializer;
|
||||
use scopeguard::defer;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
use thiserror::Error;
|
||||
use windows::Win32::Graphics::Gdi::MODIFY_WORLD_TRANSFORM_MODE;
|
||||
use windows::Win32::Graphics::Gdi::{CHECKJPEGFORMAT, CHECKPNGFORMAT, EMRSETWORLDTRANSFORM};
|
||||
use windows::Win32::Graphics::Gdi::{EMRMODIFYWORLDTRANSFORM, QUERYESCSUPPORT};
|
||||
use windows::Win32::Storage::Xps::ExtEscape;
|
||||
use windows::{
|
||||
core::PCWSTR,
|
||||
Win32::{
|
||||
Foundation::{LPARAM, RECT},
|
||||
Graphics::{
|
||||
Gdi::{
|
||||
CloseEnhMetaFile, CreateEnhMetaFileW, DeleteEnhMetaFile, EnumEnhMetaFile,
|
||||
GetEnhMetaFileHeader, GetWorldTransform, ModifyWorldTransform,
|
||||
PlayEnhMetaFileRecord, SetWorldTransform, StretchDIBits, BITMAPINFO,
|
||||
BITMAPINFOHEADER, BI_JPEG, BI_PNG, BI_RGB, DIB_USAGE, EMRSTRETCHDIBITS,
|
||||
EMR_MODIFYWORLDTRANSFORM, EMR_SETLAYOUT, EMR_SETWORLDTRANSFORM, EMR_STRETCHDIBITS,
|
||||
ENHMETAHEADER, ENHMETARECORD, HANDLETABLE, HDC, HENHMETAFILE, MWT_IDENTITY,
|
||||
MWT_LEFTMULTIPLY, MWT_RIGHTMULTIPLY, ROP_CODE, XFORM,
|
||||
},
|
||||
Imaging::{
|
||||
CLSID_WICImagingFactory2, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone,
|
||||
WICBitmapPaletteTypeMedianCut, WICDecodeMetadataCacheOnDemand,
|
||||
D2D::IWICImagingFactory2,
|
||||
},
|
||||
},
|
||||
System::Com::{CoCreateInstance, CLSCTX_ALL},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Failed to create EMF")]
|
||||
pub struct EmfCreateFailedError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Failed to play EMF")]
|
||||
pub struct EmfPlaybackFailedError;
|
||||
|
||||
pub struct Emf {
|
||||
metafile: HENHMETAFILE,
|
||||
}
|
||||
|
||||
impl Emf {
|
||||
/// Creates a new EMF by drawing on a metafile DC.
|
||||
/// All units are in .01-millimeter units.
|
||||
pub unsafe fn new(
|
||||
hdc: HDC,
|
||||
width: i32,
|
||||
height: i32,
|
||||
draw: impl FnOnce(HDC) -> bool,
|
||||
) -> Result<Self, EmfCreateFailedError> {
|
||||
// in .01-millimeter units
|
||||
let frame = RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: width,
|
||||
bottom: height,
|
||||
};
|
||||
|
||||
let emf_hdc = CreateEnhMetaFileW(
|
||||
hdc,
|
||||
PCWSTR::null(),
|
||||
Some(ptr::addr_of!(frame)),
|
||||
PCWSTR::null(),
|
||||
);
|
||||
if emf_hdc.is_invalid() {
|
||||
return Err(EmfCreateFailedError);
|
||||
}
|
||||
|
||||
if !draw(emf_hdc) {
|
||||
let _ = CloseEnhMetaFile(emf_hdc);
|
||||
return Err(EmfCreateFailedError);
|
||||
}
|
||||
|
||||
let metafile = CloseEnhMetaFile(emf_hdc);
|
||||
if metafile.is_invalid() {
|
||||
return Err(EmfCreateFailedError);
|
||||
}
|
||||
|
||||
Ok(Self { metafile })
|
||||
}
|
||||
|
||||
pub unsafe fn playback(
|
||||
&self,
|
||||
printer_hdc: HDC,
|
||||
rect: RECT,
|
||||
) -> Result<(), EmfPlaybackFailedError> {
|
||||
let mut base_matrix = XFORM::default();
|
||||
if !GetWorldTransform(printer_hdc, &mut base_matrix).as_bool() {
|
||||
return Err(EmfPlaybackFailedError);
|
||||
}
|
||||
defer! {
|
||||
let _ = SetWorldTransform(printer_hdc, ptr::addr_of!(base_matrix));
|
||||
}
|
||||
|
||||
let mut context = EnumerationContext {
|
||||
base_matrix,
|
||||
};
|
||||
|
||||
let mut header = ENHMETAHEADER::default();
|
||||
if GetEnhMetaFileHeader(
|
||||
self.metafile,
|
||||
mem::size_of::<ENHMETAHEADER>() as u32,
|
||||
Some(&mut header),
|
||||
) != mem::size_of::<ENHMETAHEADER>() as u32
|
||||
{
|
||||
return Err(EmfPlaybackFailedError);
|
||||
}
|
||||
if EnumEnhMetaFile(
|
||||
printer_hdc,
|
||||
self.metafile,
|
||||
Some(Self::playback_proc),
|
||||
Some((&mut context as *mut EnumerationContext).cast()),
|
||||
Some(ptr::addr_of!(rect)),
|
||||
)
|
||||
.as_bool()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
Err(EmfPlaybackFailedError)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn playback_proc(
|
||||
hdc: HDC,
|
||||
handle_table: *const HANDLETABLE,
|
||||
record: *const ENHMETARECORD,
|
||||
handle_count: i32,
|
||||
param: LPARAM,
|
||||
) -> i32 {
|
||||
let context = &mut *(param.0 as *mut EnumerationContext);
|
||||
let handle_table_slice = if handle_table.is_null() || handle_count <= 0 {
|
||||
&[]
|
||||
} else {
|
||||
slice::from_raw_parts(handle_table, handle_count as usize)
|
||||
};
|
||||
match play_record(hdc, record, handle_table_slice, context) {
|
||||
true => 1,
|
||||
false => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Emf {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = DeleteEnhMetaFile(self.metafile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EnumerationContext {
|
||||
base_matrix: XFORM,
|
||||
}
|
||||
|
||||
unsafe fn play_record(
|
||||
hdc: HDC,
|
||||
record: *const ENHMETARECORD,
|
||||
handle_table_slice: &[HANDLETABLE],
|
||||
context: &mut EnumerationContext,
|
||||
) -> bool {
|
||||
match (*record).iType {
|
||||
EMR_STRETCHDIBITS => {
|
||||
// EMRSTRETCHDIBITS is flexible in size, so we check the size with "<" operation.
|
||||
if (*record).nSize < (mem::size_of::<EMRSTRETCHDIBITS>()) as u32 {
|
||||
return false;
|
||||
}
|
||||
play_stretch_dibits_record(hdc, record as *const EMRSTRETCHDIBITS, handle_table_slice)
|
||||
}
|
||||
EMR_SETWORLDTRANSFORM => {
|
||||
if (*record).nSize != (mem::size_of::<EMRSETWORLDTRANSFORM>()) as u32 {
|
||||
return false;
|
||||
}
|
||||
let set_world_transform = &*(record as *const EMRSETWORLDTRANSFORM);
|
||||
SetWorldTransform(hdc, ptr::addr_of!(context.base_matrix)).as_bool()
|
||||
&& ModifyWorldTransform(
|
||||
hdc,
|
||||
Some(ptr::addr_of!(set_world_transform.xform)),
|
||||
MWT_LEFTMULTIPLY,
|
||||
)
|
||||
.as_bool()
|
||||
}
|
||||
EMR_MODIFYWORLDTRANSFORM => {
|
||||
if (*record).nSize != (mem::size_of::<EMRMODIFYWORLDTRANSFORM>()) as u32 {
|
||||
return false;
|
||||
}
|
||||
let modify_world_transform = &*(record as *const EMRMODIFYWORLDTRANSFORM);
|
||||
match modify_world_transform.iMode {
|
||||
MWT_IDENTITY => {
|
||||
SetWorldTransform(hdc, ptr::addr_of!(context.base_matrix)).as_bool()
|
||||
}
|
||||
MWT_LEFTMULTIPLY => ModifyWorldTransform(
|
||||
hdc,
|
||||
Some(ptr::addr_of!(modify_world_transform.xform)),
|
||||
MWT_LEFTMULTIPLY,
|
||||
)
|
||||
.as_bool(),
|
||||
MWT_RIGHTMULTIPLY => ModifyWorldTransform(
|
||||
hdc,
|
||||
Some(ptr::addr_of!(modify_world_transform.xform)),
|
||||
MWT_RIGHTMULTIPLY,
|
||||
)
|
||||
.as_bool(),
|
||||
// 4 = MWT_SET
|
||||
MODIFY_WORLD_TRANSFORM_MODE(4) => {
|
||||
SetWorldTransform(hdc, ptr::addr_of!(context.base_matrix)).as_bool()
|
||||
&& ModifyWorldTransform(
|
||||
hdc,
|
||||
Some(ptr::addr_of!(modify_world_transform.xform)),
|
||||
MWT_LEFTMULTIPLY,
|
||||
)
|
||||
.as_bool()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
// Skip EMR_SETLAYOUT because some printer drivers choke on
|
||||
// layout changes when replaying EMF records.
|
||||
// Chromium also skips EMR_SETLAYOUT in their EMF playback implementation:
|
||||
// https://github.com/chromium/chromium/blob/056cc5e47e8dd311cf345d4d83a2729691ac2610/printing/emf_win.cc#L368-L371
|
||||
EMR_SETLAYOUT => true,
|
||||
_ => PlayEnhMetaFileRecord(hdc, handle_table_slice, record).as_bool(),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn play_stretch_dibits_record(
|
||||
hdc: HDC,
|
||||
sdib_record: *const EMRSTRETCHDIBITS,
|
||||
handle_table_slice: &[HANDLETABLE],
|
||||
) -> bool {
|
||||
let record_size = (*sdib_record).emr.nSize as usize;
|
||||
let bmi_offset = (*sdib_record).offBmiSrc as usize;
|
||||
let bmi_len = (*sdib_record).cbBmiSrc as usize;
|
||||
let bmi_end = match bmi_offset.checked_add(bmi_len) {
|
||||
Some(end) => end,
|
||||
None => return false,
|
||||
};
|
||||
if bmi_len < mem::size_of::<BITMAPINFOHEADER>() || bmi_end > record_size {
|
||||
return false;
|
||||
}
|
||||
let bitmap_header = &*(sdib_record.byte_add(bmi_offset) as *const BITMAPINFOHEADER);
|
||||
let bits_offset = (*sdib_record).offBitsSrc as usize;
|
||||
let bits_len = bitmap_header.biSizeImage as usize;
|
||||
let bits_end = match bits_offset.checked_add(bits_len) {
|
||||
Some(end) => end,
|
||||
None => return false,
|
||||
};
|
||||
if bits_end > record_size {
|
||||
return false;
|
||||
}
|
||||
let bits = {
|
||||
if bits_len == 0 {
|
||||
&[]
|
||||
} else {
|
||||
slice::from_raw_parts(sdib_record.byte_add(bits_offset) as *const u8, bits_len)
|
||||
}
|
||||
};
|
||||
if (bitmap_header.biCompression == BI_JPEG.0
|
||||
&& !dib_format_natively_supported(hdc, CHECKJPEGFORMAT, bits))
|
||||
|| (bitmap_header.biCompression == BI_PNG.0
|
||||
&& !dib_format_natively_supported(hdc, CHECKPNGFORMAT, bits))
|
||||
{
|
||||
// Decode compressed format in application and play with StretchDIBits, to avoid EMF playback failure.
|
||||
// Aligns with how Chromium plays EMF records with compressed DIBs:
|
||||
// https://github.com/chromium/chromium/blob/c6e8aae4573cec06352a4748ae902831f9a1334c/printing/emf_win.cc#L276
|
||||
return match DecodedBitmap::from_compressed(bits) {
|
||||
Ok(decoded) => {
|
||||
StretchDIBits(
|
||||
hdc,
|
||||
(*sdib_record).xDest,
|
||||
(*sdib_record).yDest,
|
||||
(*sdib_record).cxDest,
|
||||
(*sdib_record).cyDest,
|
||||
(*sdib_record).xSrc,
|
||||
(*sdib_record).ySrc,
|
||||
(*sdib_record).cxSrc,
|
||||
(*sdib_record).cySrc,
|
||||
Some(decoded.pixels.as_ptr().cast()),
|
||||
ptr::addr_of!(decoded.bitmap_info),
|
||||
DIB_USAGE((*sdib_record).iUsageSrc),
|
||||
ROP_CODE((*sdib_record).dwRop),
|
||||
) != 0
|
||||
}
|
||||
Err(_) => false,
|
||||
};
|
||||
}
|
||||
|
||||
PlayEnhMetaFileRecord(hdc, handle_table_slice, sdib_record as *const ENHMETARECORD).as_bool()
|
||||
}
|
||||
|
||||
fn dib_format_natively_supported(dc: HDC, escape: u32, bits: &[u8]) -> bool {
|
||||
let mut supported: [u8; 4] = [0, 0, 0, 0];
|
||||
unsafe {
|
||||
if ExtEscape(
|
||||
dc,
|
||||
QUERYESCSUPPORT as _,
|
||||
Some(escape.to_ne_bytes().as_slice()),
|
||||
None,
|
||||
) > 0
|
||||
{
|
||||
ExtEscape(dc, escape as _, Some(bits), Some(supported.as_mut_slice()));
|
||||
}
|
||||
}
|
||||
supported != [0, 0, 0, 0]
|
||||
}
|
||||
|
||||
struct DecodedBitmap {
|
||||
bitmap_info: BITMAPINFO,
|
||||
pixels: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DecodedBitmap {
|
||||
unsafe fn from_compressed(bytes: &[u8]) -> windows::core::Result<Self> {
|
||||
let _com = ComInitializer::new();
|
||||
let factory: IWICImagingFactory2 =
|
||||
CoCreateInstance(&CLSID_WICImagingFactory2, None, CLSCTX_ALL)?;
|
||||
let stream = factory.CreateStream()?;
|
||||
stream.InitializeFromMemory(bytes)?;
|
||||
let decoder = factory.CreateDecoderFromStream(
|
||||
&stream,
|
||||
ptr::null(),
|
||||
WICDecodeMetadataCacheOnDemand,
|
||||
)?;
|
||||
let frame = decoder.GetFrame(0)?;
|
||||
let converter = factory.CreateFormatConverter()?;
|
||||
converter.Initialize(
|
||||
&frame,
|
||||
&GUID_WICPixelFormat32bppBGRA,
|
||||
WICBitmapDitherTypeNone,
|
||||
None,
|
||||
0.0,
|
||||
WICBitmapPaletteTypeMedianCut,
|
||||
)?;
|
||||
|
||||
let mut width = 0u32;
|
||||
let mut height = 0u32;
|
||||
converter.GetSize(&mut width, &mut height)?;
|
||||
let stride = width.saturating_mul(4);
|
||||
let mut pixels = vec![0u8; stride.saturating_mul(height) as usize];
|
||||
converter.CopyPixels(ptr::null(), stride, &mut pixels)?;
|
||||
|
||||
Ok(Self {
|
||||
bitmap_info: BITMAPINFO {
|
||||
bmiHeader: BITMAPINFOHEADER {
|
||||
biSize: mem::size_of::<BITMAPINFOHEADER>() as u32,
|
||||
biWidth: width as i32,
|
||||
biHeight: -(height as i32),
|
||||
biPlanes: 1,
|
||||
biBitCount: 32,
|
||||
biCompression: BI_RGB.0,
|
||||
biSizeImage: pixels.len() as u32,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
pixels,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub(crate) mod com;
|
||||
pub(crate) mod emf;
|
||||
#[cfg(feature = "pdfium")]
|
||||
pub(crate) mod pdfium;
|
||||
pub(crate) mod print_completion_source;
|
||||
|
||||
Reference in new issue
Block a user