Skip to content

Instantly share code, notes, and snippets.

@spillz
Created January 12, 2021 20:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spillz/1667dd8b04654f32b51133cb7f72b898 to your computer and use it in GitHub Desktop.
Save spillz/1667dd8b04654f32b51133cb7f72b898 to your computer and use it in GitHub Desktop.
Monkey patch for python-docx to enable EMF support
# encoding: utf-8
# This module monkey patches the docx library to add support for Windows Enhanced Metafile images (EMF)
# Put in a local folder and "import docx_emf_patch" to enable EMF support.
from __future__ import absolute_import, division, print_function
import docx
from docx.image.exceptions import UnrecognizedImageError
from docx.image.constants import MIME_TYPE
from docx.image.exceptions import InvalidImageStreamError
from docx.image.helpers import BIG_ENDIAN, StreamReader
from docx.image.image import BaseImageHeader
import struct
def _ImageHeaderFactory(stream):
"""
Return a |BaseImageHeader| subclass instance that knows how to parse the
headers of the image in *stream*.
"""
from docx.image import SIGNATURES
def read_64(stream):
stream.seek(0)
return stream.read(64)
header = read_64(stream)
for cls, offset, signature_bytes in SIGNATURES:
end = offset + len(signature_bytes)
found_bytes = header[offset:end]
if found_bytes == signature_bytes:
return cls.from_stream(stream)
raise UnrecognizedImageError
class Emf(BaseImageHeader):
"""
Image header parser for EMF images
"""
@property
def content_type(self):
"""
MIME content type for this image, unconditionally `image/emf` for
EMF images.
"""
return MIME_TYPE.EMF
@property
def default_ext(self):
"""
Default filename extension, always 'emf' for EMF images.
"""
return 'emf'
@classmethod
def from_stream(cls, stream,filename=None):
"""
Return a |Emf| instance having header properties parsed from image in
*stream*.
"""
"""
@0 DWORD iType; // fixed
@4 DWORD nSize; // var
@8 RECTL rclBounds;
@24 RECTL rclFrame; // .01 millimeter units L T R B
@40 DWORD dSignature; // ENHMETA_SIGNATURE = 0x464D4520
DWORD nVersion;
DWORD nBytes;
DWORD nRecords;
WORD nHandles;
WORD sReserved;
DWORD nDescription;
DWORD offDescription;
DWORD nPalEntries;
SIZEL szlDevice;
SIZEL szlMillimeters;
"""
stream.seek(0)
x = stream.read(40)
stream.seek(0)
iType,nSize = struct.unpack("ii",x[0:8])
rclBounds = struct.unpack("iiii",x[8:24])
rclFrame = struct.unpack("iiii",x[24:40])
dpi = 300
horz_dpi = dpi
vert_dpi = dpi
mmwidth = (rclFrame[2]-rclFrame[0])/100.0
mmheight = (rclFrame[3]-rclFrame[1])/100.0
px_width = int(mmwidth*dpi*0.03937008)
px_height = int(mmheight*dpi*0.03937008)
#1 dot/inch = 0.03937008 pixel/millimeter
return cls(px_width,px_height,horz_dpi,vert_dpi)
docx.image.Emf = Emf
docx.image.constants.MIME_TYPE.EMF = 'image/emf'
docx.image.SIGNATURES = tuple(list(docx.image.SIGNATURES) + [(Emf, 40, b' EMF')])
docx.image.image._ImageHeaderFactory = _ImageHeaderFactory
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment