diff options
author | Hossein <hossein@libreoffice.org> | 2021-09-08 23:13:17 +0200 |
---|---|---|
committer | Hossein <hossein@libreoffice.org> | 2021-09-08 23:27:41 +0200 |
commit | 77a03bd26e4076553ad3ea0972c64488baa467da (patch) | |
tree | 726d28dd8421a34f7c37a7800e0ed0819b2c97cc | |
parent | 684a25f0b19723337b9aedd765565b72ede89e91 (diff) |
Add preliminary wmf support and minor cleanups
* Added "wmf-dump.py" and preliminary support for dumping header/data
from wmf files in msodumper/wmfrecord.py
* Most of the records have data shown as <todo/> for now. Should be
completed later
* Changed msodumper/wmfrecord.py and msodumper/emfrecord.py to
handle wmf and emf files with unexpected end of file
* Changed msodumper/wmfrecord.py and msodumper/emfrecord.py to
handle invalid values for the fields
* Tested to work (at least do not cause exception) for all the wmf
and most of the emf files found inside LibreOffice core repository
Change-Id: Ia067961d006c9dc543e521ad64509b03b8454a80
Reviewed-on: https://gerrit.libreoffice.org/c/mso-dumper/+/121833
Tested-by: Hossein <hossein@libreoffice.org>
Reviewed-by: Hossein <hossein@libreoffice.org>
-rw-r--r-- | msodumper/emfrecord.py | 11 | ||||
-rw-r--r-- | msodumper/wmfrecord.py | 237 | ||||
-rwxr-xr-x | wmf-dump.py | 31 |
3 files changed, 275 insertions, 4 deletions
diff --git a/msodumper/emfrecord.py b/msodumper/emfrecord.py index 7d7be0a..ca4b022 100644 --- a/msodumper/emfrecord.py +++ b/msodumper/emfrecord.py @@ -65,7 +65,7 @@ class EMFStream(BinaryStream): emrHeader.dump() for i in range(emrHeader.header.Records): id = self.getuInt32() - record = RecordType[id] + record = RecordType.get(id, ["INVALID"]) type = record[0] size = self.getuInt32(pos=self.pos + 4) # EmrHeader is already dumped @@ -77,7 +77,14 @@ class EMFStream(BinaryStream): else: print('<todo/>') print('</record>') - self.pos += size + # EMR_EOF + if type == "EMR_EOF": + break + if self.pos + size <= self.size: + self.pos += size + else: + print('<Error value="Unexpected end of file" />') + break print('</stream>') diff --git a/msodumper/wmfrecord.py b/msodumper/wmfrecord.py index 1d7b1fa..2294301 100644 --- a/msodumper/wmfrecord.py +++ b/msodumper/wmfrecord.py @@ -7,6 +7,14 @@ from .binarystream import BinaryStream +PlaceableKey = { + 0x9ac6cdd7: "META_PLACEABLE", +} + +FileType = { + 0x00: "Memory", + 0x01: "Disk", +} # The BrushStyle Enumeration specifies the different possible brush types that can be used in graphics operations. BrushStyle = { @@ -22,7 +30,6 @@ BrushStyle = { 0x0009: "BS_MONOPATTERN" } - # The HatchStyle Enumeration specifies the hatch pattern. HatchStyle = { 0x0000: "HS_HORIZONTAL", @@ -33,7 +40,6 @@ HatchStyle = { 0x0005: "HS_DIAGCROSS" } - # No idea what's the proper name of this thing, see # http://msdn.microsoft.com/en-us/library/dd145130%28VS.85%29.aspx RasterPolishMap = { @@ -296,6 +302,41 @@ RasterPolishMap = { } +class WMFStream(BinaryStream): + def __init__(self, bytes): + BinaryStream.__init__(self, bytes) + + def dump(self): + print('<stream type="WMF" size="%d">' % self.size) + wmfHeader = WmfHeader(self) + wmfHeader.dump() + + MAXIMUM_RECORDS = 100000 + for i in range(1, MAXIMUM_RECORDS): + size = self.getuInt32() + id = self.getuInt16(pos=self.pos + 4) + record = RecordType.get(id, ["INVALID"]) + type = record[0] + # WmfHeader is already dumped + if i: + print('<record index="%s" type="%s">' % (i, type)) + if len(record) > 1: + handler = record[1](self) + handler.dump() + else: + print('<todo/>') + print('</record>') + # META_EOF + if type == "META_EOF": + break + if self.pos + size * 2 <= self.size: + self.pos += size * 2 + else: + print('<Error value="Unexpected end of file" />') + break + print('</stream>') + + class WMFRecord(BinaryStream): def __init__(self, parent): BinaryStream.__init__(self, parent.bytes) @@ -303,8 +344,28 @@ class WMFRecord(BinaryStream): self.pos = parent.pos +class Rect(WMFRecord): + """The Rect Object defines a rectangle.""" + def __init__(self, parent, name=None): + WMFRecord.__init__(self, parent) + if name: + self.name = name + else: + self.name = "rect" + + def dump(self): + print('<%s type="Rect">' % self.name) + self.printAndSet("Left", self.readInt16(), hexdump=False) + self.printAndSet("Top", self.readInt16(), hexdump=False) + self.printAndSet("Right", self.readInt16(), hexdump=False) + self.printAndSet("Bottom", self.readInt16(), hexdump=False) + print('</%s>' % self.name) + self.parent.pos = self.pos + + class RectL(WMFRecord): """The RectL Object defines a rectangle.""" + def __init__(self, parent, name=None): WMFRecord.__init__(self, parent) if name: @@ -324,6 +385,7 @@ class RectL(WMFRecord): class SizeL(WMFRecord): """The SizeL Object defines a rectangle.""" + def __init__(self, parent, name=None): WMFRecord.__init__(self, parent) if name: @@ -341,6 +403,7 @@ class SizeL(WMFRecord): class PointL(WMFRecord): """The PointL Object defines the coordinates of a point.""" + def __init__(self, parent, name=None): WMFRecord.__init__(self, parent) if name: @@ -358,6 +421,7 @@ class PointL(WMFRecord): class PointS(WMFRecord): """The PointS Object defines the x- and y-coordinates of a point.""" + def __init__(self, parent, name): WMFRecord.__init__(self, parent) self.name = name @@ -372,6 +436,7 @@ class PointS(WMFRecord): class ColorRef(WMFRecord): """The ColorRef Object defines the RGB color.""" + def __init__(self, parent, name): WMFRecord.__init__(self, parent) self.name = name @@ -385,4 +450,172 @@ class ColorRef(WMFRecord): print('</%s>' % self.name) self.parent.pos = self.pos + +class WMFLineto(WMFRecord): + """Draws a line from the current position up to, but not including, the + specified point.""" + def __init__(self, parent): + WMFRecord.__init__(self, parent) + + def dump(self): + posOrig = self.pos + self.printAndSet("Type", self.readuInt32()) + self.printAndSet("Size", self.readuInt32(), hexdump=False) + PointL(self, "Point").dump() + + +class WmfHeader(WMFRecord): + """The HEADER record types define the starting points of WMF metafiles.""" + def __init__(self, parent): + WMFRecord.__init__(self, parent) + + def dump(self): + print('<wmfHeader>') + self.header = Header(self) + self.header.dump() + self.parent.pos = self.pos + print('</wmfHeader>') + + +class Header(WMFRecord): + """The Header object defines the WMF metafile header.""" + def __init__(self, parent): + WMFRecord.__init__(self, parent) + + def dump(self): + posOrig = self.pos + self.Size = 18 + if PlaceableHeader(self).isPlaceable(): + PlaceableHeader(self).dump() + self.Size += 22 + print("<header>") + self.printAndSet("FileType", self.readuInt16(), dict=FileType) + self.printAndSet("HeaderSize", self.readuInt16(), hexdump=False) + self.printAndSet("Version", self.readuInt16(), hexdump=False) + self.printAndSet("FileSize", self.readuInt32(), hexdump=False) + self.printAndSet("NumObjects", self.readuInt16(), hexdump=False) + self.printAndSet("MaxRecordSize", self.readuInt32(), hexdump=False) + self.printAndSet("NumOfParams", self.readuInt16(), hexdump=False) + print("</header>") + assert self.pos - posOrig == self.Size + self.parent.pos = self.pos + + +class PlaceableHeader(WMFRecord): + """The header for the placeable WMF""" + def __init__(self, parent): + WMFRecord.__init__(self, parent) + + def dump(self): + print("<placeableHeader>") + self.header = Header(self) + self.Size = 22 + pos = self.pos + dataPos = self.pos + self.printAndSet("Key", self.readuInt32(), dict=PlaceableKey) + self.printAndSet("HWmf", self.readuInt16()) + Rect(self, "BoundingBox").dump() + self.printAndSet("Inch", self.readuInt16(), hexdump=False) + self.printAndSet("Reserved", self.readuInt32()) + self.printAndSet("Checksum", self.readuInt16()) + assert self.pos == dataPos + self.Size + self.parent.pos = self.pos + print("</placeableHeader>") + + def isPlaceable(self): + self.header = Header(self) + key = self.readuInt32() + if key in PlaceableKey.keys(): + return True + return False + + +class WmfSetviewportorgex(WMFRecord): + """Defines the viewport origin.""" + + def __init__(self, parent): + WMFRecord.__init__(self, parent) + + def dump(self): + posOrig = self.pos + self.printAndSet("Type", self.readuInt32()) + self.printAndSet("Size", self.readuInt32(), hexdump=False) + PointL(self, "Origin").dump() + assert self.pos - posOrig == self.Size + + +"""The RecordType enumeration defines values that uniquely identify EMF records.""" +RecordType = { + 0x0000: ['META_EOF'], + 0x0035: ['META_REALIZEPALETTE'], + 0x0037: ['META_SETPALENTRIES'], + 0x0102: ['META_SETBKMODE'], + 0x0103: ['META_SETMAPMODE'], + 0x0104: ['META_SETROP2'], + 0x0105: ['META_SETRELABS'], + 0x0106: ['META_SETPOLYFILLMODE'], + 0x0107: ['META_SETSTRETCHBLTMODE'], + 0x0108: ['META_SETTEXTCHAREXTRA'], + 0x0127: ['META_RESTOREDC'], + 0x0139: ['META_RESIZEPALETTE'], + 0x0142: ['META_DIBCREATEPATTERNBRUSH'], + 0x0149: ['META_SETLAYOUT'], + 0x0201: ['META_SETBKCOLOR'], + 0x0209: ['META_SETTEXTCOLOR'], + 0x0211: ['META_OFFSETVIEWPORTORG'], + 0x0213: ['META_LINETO', WMFLineto], + 0x0214: ['META_MOVETO'], + 0x0220: ['META_OFFSETCLIPRGN'], + 0x0228: ['META_FILLREGION'], + 0x0231: ['META_SETMAPPERFLAGS'], + 0x0234: ['META_SELECTPALETTE'], + 0x0324: ['META_POLYGON'], + 0x0325: ['META_POLYLINE'], + 0x020A: ['META_SETTEXTJUSTIFICATION'], + 0x020B: ['META_SETWINDOWORG'], + 0x020C: ['META_SETWINDOWEXT'], + 0x020D: ['META_SETVIEWPORTORG'], + 0x020E: ['META_SETVIEWPORTEXT'], + 0x020F: ['META_OFFSETWINDOWORG'], + 0x0410: ['META_SCALEWINDOWEXT'], + 0x0412: ['META_SCALEVIEWPORTEXT'], + 0x0415: ['META_EXCLUDECLIPRECT'], + 0x0416: ['META_INTERSECTCLIPRECT'], + 0x0418: ['META_ELLIPSE'], + 0x0419: ['META_FLOODFILL'], + 0x0429: ['META_FRAMEREGION'], + 0x0436: ['META_ANIMATEPALETTE'], + 0x0521: ['META_TEXTOUT'], + 0x0538: ['META_POLYPOLYGON'], + 0x0548: ['META_EXTFLOODFILL'], + 0x041B: ['META_RECTANGLE'], + 0x041F: ['META_SETPIXEL'], + 0x061C: ['META_ROUNDRECT'], + 0x061D: ['META_PATBLT'], + 0x001E: ['META_SAVEDC'], + 0x081A: ['META_PIE'], + 0x0B23: ['META_STRETCHBLT'], + 0x0626: ['META_ESCAPE'], + 0x012A: ['META_INVERTREGION'], + 0x012B: ['META_PAINTREGION'], + 0x012C: ['META_SELECTCLIPREGION'], + 0x012D: ['META_SELECTOBJECT'], + 0x012E: ['META_SETTEXTALIGN'], + 0x0817: ['META_ARC'], + 0x0830: ['META_CHORD'], + 0x0922: ['META_BITBLT'], + 0x0a32: ['META_EXTTEXTOUT'], + 0x0d33: ['META_SETDIBTODEV'], + 0x0940: ['META_DIBBITBLT'], + 0x0b41: ['META_DIBSTRETCHBLT'], + 0x0f43: ['META_STRETCHDIB'], + 0x01f0: ['META_DELETEOBJECT'], + 0x00f7: ['META_CREATEPALETTE'], + 0x01F9: ['META_CREATEPATTERNBRUSH'], + 0x02FA: ['META_CREATEPENINDIRECT'], + 0x02FB: ['META_CREATEFONTINDIRECT'], + 0x02FC: ['META_CREATEBRUSHINDIRECT'], + 0x06FF: ['META_CREATEREGION'], +} + # vim:set filetype=python shiftwidth=4 softtabstop=4 expandtab: diff --git a/wmf-dump.py b/wmf-dump.py new file mode 100755 index 0000000..3c17636 --- /dev/null +++ b/wmf-dump.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +from msodumper import wmfrecord +import sys + +class WMFDumper: + def __init__(self, filepath): + self.filepath = filepath + + def dump(self): + file = open(self.filepath, 'rb') + strm = wmfrecord.WMFStream(file.read()) + file.close() + print('<?xml version="1.0"?>') + strm.dump() + + +def main(args): + dumper = WMFDumper(args[1]) + dumper.dump() + + +if __name__ == '__main__': + main(sys.argv) + +# vim:set filetype=python shiftwidth=4 softtabstop=4 expandtab: |