| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.hwmf.record; |
| |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.function.Supplier; |
| |
| import org.apache.poi.common.usermodel.GenericRecord; |
| import org.apache.poi.hwmf.draw.HwmfGraphics; |
| import org.apache.poi.util.GenericRecordJsonWriter; |
| import org.apache.poi.util.GenericRecordUtil; |
| import org.apache.poi.util.IOUtils; |
| import org.apache.poi.util.LittleEndianConsts; |
| import org.apache.poi.util.LittleEndianInputStream; |
| |
| /** |
| * The MetafileEscapes specifies printer driver functionality that |
| * might not be directly accessible through WMF records |
| */ |
| @SuppressWarnings("WeakerAccess") |
| public class HwmfEscape implements HwmfRecord { |
| private static final int MAX_OBJECT_SIZE = 0xFFFF; |
| |
| public enum EscapeFunction { |
| /** Notifies the printer driver that the application has finished writing to a page. */ |
| NEWFRAME(0x0001, WmfEscapeUnknownData::new), |
| /** Stops processing the current document. */ |
| ABORTDOC(0x0002, WmfEscapeUnknownData::new), |
| /** Notifies the printer driver that the application has finished writing to a band. */ |
| NEXTBAND(0x0003, WmfEscapeUnknownData::new), |
| /** Sets color table values. */ |
| SETCOLORTABLE(0x0004, WmfEscapeUnknownData::new), |
| /** Gets color table values. */ |
| GETCOLORTABLE(0x0005, WmfEscapeUnknownData::new), |
| /** Causes all pending output to be flushed to the output device. */ |
| FLUSHOUT(0x0006, WmfEscapeUnknownData::new), |
| /** Indicates that the printer driver SHOULD print text only, and no graphics. */ |
| DRAFTMODE(0x0007, WmfEscapeUnknownData::new), |
| /** Queries a printer driver to determine whether a specific escape function is supported on the output device it drives. */ |
| QUERYESCSUPPORT(0x0008, WmfEscapeUnknownData::new), |
| /** Sets the application-defined function that allows a print job to be canceled during printing. */ |
| SETABORTPROC(0x0009, WmfEscapeUnknownData::new), |
| /** Notifies the printer driver that a new print job is starting. */ |
| STARTDOC(0x000A, WmfEscapeUnknownData::new), |
| /** Notifies the printer driver that the current print job is ending. */ |
| ENDDOC(0x000B, WmfEscapeUnknownData::new), |
| /** Retrieves the physical page size currently selected on an output device. */ |
| GETPHYSPAGESIZE(0x000C, WmfEscapeUnknownData::new), |
| /** Retrieves the offset from the upper-left corner of the physical page where the actual printing or drawing begins. */ |
| GETPRINTINGOFFSET(0x000D, WmfEscapeUnknownData::new), |
| /** Retrieves the scaling factors for the x-axis and the y-axis of a printer. */ |
| GETSCALINGFACTOR(0x000E, WmfEscapeUnknownData::new), |
| /** Used to embed an enhanced metafile format (EMF) metafile within a WMF metafile. */ |
| META_ESCAPE_ENHANCED_METAFILE(0x000F, WmfEscapeEMF::new), |
| /** Sets the width of a pen in pixels. */ |
| SETPENWIDTH(0x0010, WmfEscapeUnknownData::new), |
| /** Sets the number of copies. */ |
| SETCOPYCOUNT(0x0011, WmfEscapeUnknownData::new), |
| /** Sets the source, such as a particular paper tray or bin on a printer, for output forms. */ |
| SETPAPERSOURCE(0x0012, WmfEscapeUnknownData::new), |
| /** This record passes through arbitrary data. */ |
| PASSTHROUGH(0x0013, WmfEscapeUnknownData::new), |
| /** Gets information concerning graphics technology that is supported on a device. */ |
| GETTECHNOLOGY(0x0014, WmfEscapeUnknownData::new), |
| /** Specifies the line-drawing mode to use in output to a device. */ |
| SETLINECAP(0x0015, WmfEscapeUnknownData::new), |
| /** Specifies the line-joining mode to use in output to a device. */ |
| SETLINEJOIN(0x0016, WmfEscapeUnknownData::new), |
| /** Sets the limit for the length of miter joins to use in output to a device. */ |
| SETMITERLIMIT(0x0017, WmfEscapeUnknownData::new), |
| /** Retrieves or specifies settings concerning banding on a device, such as the number of bands. */ |
| BANDINFO(0x0018, WmfEscapeUnknownData::new), |
| /** Draws a rectangle with a defined pattern. */ |
| DRAWPATTERNRECT(0x0019, WmfEscapeUnknownData::new), |
| /** Retrieves the physical pen size currently defined on a device. */ |
| GETVECTORPENSIZE(0x001A, WmfEscapeUnknownData::new), |
| /** Retrieves the physical brush size currently defined on a device. */ |
| GETVECTORBRUSHSIZE(0x001B, WmfEscapeUnknownData::new), |
| /** Enables or disables double-sided (duplex) printing on a device. */ |
| ENABLEDUPLEX(0x001C, WmfEscapeUnknownData::new), |
| /** Retrieves or specifies the source of output forms on a device. */ |
| GETSETPAPERBINS(0x001D, WmfEscapeUnknownData::new), |
| /** Retrieves or specifies the paper orientation on a device. */ |
| GETSETPRINTORIENT(0x001E, WmfEscapeUnknownData::new), |
| /** Retrieves information concerning the sources of different forms on an output device. */ |
| ENUMPAPERBINS(0x001F, WmfEscapeUnknownData::new), |
| /** Specifies the scaling of device-independent bitmaps (DIBs). */ |
| SETDIBSCALING(0x0020, WmfEscapeUnknownData::new), |
| /** Indicates the start and end of an encapsulated PostScript (EPS) section. */ |
| EPSPRINTING(0x0021, WmfEscapeUnknownData::new), |
| /** Queries a printer driver for paper dimensions and other forms data. */ |
| ENUMPAPERMETRICS(0x0022, WmfEscapeUnknownData::new), |
| /** Retrieves or specifies paper dimensions and other forms data on an output device. */ |
| GETSETPAPERMETRICS(0x0023, WmfEscapeUnknownData::new), |
| /** Sends arbitrary PostScript data to an output device. */ |
| POSTSCRIPT_DATA(0x0025, WmfEscapeUnknownData::new), |
| /** Notifies an output device to ignore PostScript data. */ |
| POSTSCRIPT_IGNORE(0x0026, WmfEscapeUnknownData::new), |
| /** Gets the device units currently configured on an output device. */ |
| GETDEVICEUNITS(0x002A, WmfEscapeUnknownData::new), |
| /** Gets extended text metrics currently configured on an output device. */ |
| GETEXTENDEDTEXTMETRICS(0x0100, WmfEscapeUnknownData::new), |
| /** Gets the font kern table currently defined on an output device. */ |
| GETPAIRKERNTABLE(0x0102, WmfEscapeUnknownData::new), |
| /** Draws text using the currently selected font, background color, and text color. */ |
| EXTTEXTOUT(0x0200, WmfEscapeUnknownData::new), |
| /** Gets the font face name currently configured on a device. */ |
| GETFACENAME(0x0201, WmfEscapeUnknownData::new), |
| /** Sets the font face name on a device. */ |
| DOWNLOADFACE(0x0202, WmfEscapeUnknownData::new), |
| /** Queries a printer driver about the support for metafiles on an output device. */ |
| METAFILE_DRIVER(0x0801, WmfEscapeUnknownData::new), |
| /** Queries the printer driver about its support for DIBs on an output device. */ |
| QUERYDIBSUPPORT(0x0C01, WmfEscapeUnknownData::new), |
| /** Opens a path. */ |
| BEGIN_PATH(0x1000, WmfEscapeUnknownData::new), |
| /** Defines a clip region that is bounded by a path. The input MUST be a 16-bit quantity that defines the action to take. */ |
| CLIP_TO_PATH(0x1001, WmfEscapeUnknownData::new), |
| /** Ends a path. */ |
| END_PATH(0x1002, WmfEscapeUnknownData::new), |
| /** The same as STARTDOC specified with a NULL document and output filename, data in raw mode, and a type of zero. */ |
| OPEN_CHANNEL(0x100E, WmfEscapeUnknownData::new), |
| /** Instructs the printer driver to download sets of PostScript procedures. */ |
| DOWNLOADHEADER(0x100F, WmfEscapeUnknownData::new), |
| /** The same as ENDDOC. See OPEN_CHANNEL. */ |
| CLOSE_CHANNEL(0x1010, WmfEscapeUnknownData::new), |
| /** Sends arbitrary data directly to a printer driver, which is expected to process this data only when in PostScript mode. */ |
| POSTSCRIPT_PASSTHROUGH(0x1013, WmfEscapeUnknownData::new), |
| /** Sends arbitrary data directly to the printer driver. */ |
| ENCAPSULATED_POSTSCRIPT(0x1014, WmfEscapeUnknownData::new), |
| /** Sets the printer driver to either PostScript or GDI mode. */ |
| POSTSCRIPT_IDENTIFY(0x1015, WmfEscapeUnknownData::new), |
| /** Inserts a block of raw data into a PostScript stream. The input MUST be |
| a 32-bit quantity specifying the number of bytes to inject, a 16-bit quantity specifying the |
| injection point, and a 16-bit quantity specifying the page number, followed by the bytes to |
| inject. */ |
| POSTSCRIPT_INJECTION(0x1016, WmfEscapeUnknownData::new), |
| /** Checks whether the printer supports a JPEG image. */ |
| CHECKJPEGFORMAT(0x1017, WmfEscapeUnknownData::new), |
| /** Checks whether the printer supports a PNG image */ |
| CHECKPNGFORMAT(0x1018, WmfEscapeUnknownData::new), |
| /** Gets information on a specified feature setting for a PostScript printer driver. */ |
| GET_PS_FEATURESETTING(0x1019, WmfEscapeUnknownData::new), |
| /** Enables applications to write documents to a file or to a printer in XML Paper Specification (XPS) format. */ |
| MXDC_ESCAPE(0x101A, WmfEscapeUnknownData::new), |
| /** Enables applications to include private procedures and other arbitrary data in documents. */ |
| SPCLPASSTHROUGH2(0x11D8, WmfEscapeUnknownData::new); |
| |
| private final int flag; |
| private final Supplier<? extends HwmfEscape.HwmfEscapeData> constructor; |
| |
| |
| EscapeFunction(int flag, Supplier<? extends HwmfEscape.HwmfEscapeData> constructor) { |
| this.flag = flag; |
| this.constructor = constructor; |
| } |
| |
| public int getFlag() { |
| return flag; |
| } |
| |
| public Supplier<? extends HwmfEscapeData> getConstructor() { |
| return constructor; |
| } |
| |
| static EscapeFunction valueOf(int flag) { |
| for (EscapeFunction hs : values()) { |
| if (hs.flag == flag) return hs; |
| } |
| return null; |
| } |
| } |
| |
| public interface HwmfEscapeData { |
| public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException; |
| } |
| |
| |
| /** |
| * A 16-bit unsigned integer that defines the escape function. The |
| * value MUST be from the MetafileEscapes enumeration. |
| */ |
| private EscapeFunction escapeFunction; |
| private HwmfEscapeData escapeData; |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.escape; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| escapeFunction = EscapeFunction.valueOf(leis.readUShort()); |
| // A 16-bit unsigned integer that specifies the size, in bytes, of the EscapeData field. |
| int byteCount = leis.readUShort(); |
| int size = 2*LittleEndianConsts.SHORT_SIZE; |
| |
| escapeData = (escapeFunction == null) ? new WmfEscapeUnknownData() : escapeFunction.constructor.get(); |
| size += escapeData.init(leis, byteCount, escapeFunction); |
| |
| return size; |
| } |
| |
| public EscapeFunction getEscapeFunction() { |
| return escapeFunction; |
| } |
| |
| public <T extends HwmfEscapeData> T getEscapeData() { |
| return (T)escapeData; |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| |
| } |
| |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "escapeFunction", this::getEscapeFunction, |
| "escapeData", this::getEscapeData |
| ); |
| } |
| |
| public static class WmfEscapeUnknownData implements HwmfEscapeData, GenericRecord { |
| EscapeFunction escapeFunction; |
| private byte[] escapeDataBytes; |
| |
| public byte[] getEscapeDataBytes() { |
| return escapeDataBytes; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException { |
| this.escapeFunction = escapeFunction; |
| escapeDataBytes = IOUtils.toByteArray(leis,(int)recordSize,MAX_OBJECT_SIZE); |
| return (int)recordSize; |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("escapeDataBytes", this::getEscapeDataBytes); |
| } |
| } |
| |
| public static class WmfEscapeEMF implements HwmfEscapeData, GenericRecord { |
| // The magic for EMF parts, i.e. the byte sequence for "WMFC" |
| private static final int EMF_COMMENT_IDENTIFIER = 0x43464D57; |
| |
| int commentIdentifier; |
| int commentType; |
| int version; |
| int checksum; |
| int flags; |
| int commentRecordCount; |
| int currentRecordSize; |
| int remainingBytes; |
| int emfRecordSize; |
| byte[] emfData; |
| |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException { |
| if (recordSize < LittleEndianConsts.INT_SIZE) { |
| return 0; |
| } |
| |
| // A 32-bit unsigned integer that defines this record as a WMF Comment record. |
| commentIdentifier = leis.readInt(); |
| |
| if (commentIdentifier != EMF_COMMENT_IDENTIFIER) { |
| // there are some WMF implementation using this record as a MFCOMMENT or similar |
| // if the commentIdentifier doesn't match, then return immediately |
| emfData = IOUtils.toByteArray(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE), MAX_OBJECT_SIZE); |
| remainingBytes = emfData.length; |
| return (int)recordSize; |
| } |
| |
| // A 32-bit unsigned integer that identifies the type of comment in this record. |
| // This value MUST be 0x00000001. |
| commentType = leis.readInt(); |
| assert(commentType == 0x00000001); |
| |
| // A 32-bit unsigned integer that specifies EMF metafile interoperability. This SHOULD be 0x00010000. |
| version = leis.readInt(); |
| |
| // A 16-bit unsigned integer used to validate the correctness of the embedded EMF stream. |
| // This value MUST be the one's-complement of the result of applying an XOR operation to all WORDs in the EMF stream. |
| checksum = leis.readUShort(); |
| |
| // This 32-bit unsigned integer is unused and MUST be set to zero. |
| flags = leis.readInt(); |
| assert(flags == 0); |
| |
| // A 32-bit unsigned integer that specifies the total number of consecutive META_ESCAPE_ENHANCED_METAFILE |
| // records that contain the embedded EMF metafile. |
| commentRecordCount = leis.readInt(); |
| |
| // A 32-bit unsigned integer that specifies the size, in bytes, of the EnhancedMetafileData field. |
| // This value MUST be less than or equal to 8,192. |
| currentRecordSize = leis.readInt(); |
| assert(0 <= currentRecordSize && currentRecordSize <= 0x2000); |
| |
| // A 32-bit unsigned integer that specifies the number of bytes in the EMF stream that remain to be |
| // processed after this record. Those additional EMF bytes MUST follow in the EnhancedMetafileData |
| // fields of subsequent META_ESCAPE_ENHANDED_METAFILE escape records. |
| remainingBytes = leis.readInt(); |
| |
| // A 32-bit unsigned integer that specifies the total size of the EMF stream embedded in this |
| // sequence of META_ESCAPE_ENHANCED_METAFILE records. |
| emfRecordSize = leis.readInt(); |
| |
| |
| // A segment of an EMF file. The bytes in consecutive META_ESCAPE_ENHANCED_METAFILE records |
| // MUST be concatenated to represent the entire embedded EMF file. |
| emfData = IOUtils.toByteArray(leis, currentRecordSize, MAX_OBJECT_SIZE); |
| |
| return LittleEndianConsts.INT_SIZE*8+ LittleEndianConsts.SHORT_SIZE+emfData.length; |
| } |
| |
| public boolean isValid() { |
| return commentIdentifier == EMF_COMMENT_IDENTIFIER; |
| } |
| |
| public int getCommentRecordCount() { |
| return commentRecordCount; |
| } |
| |
| public int getCurrentRecordSize() { |
| return currentRecordSize; |
| } |
| |
| public int getRemainingBytes() { |
| return remainingBytes; |
| } |
| |
| public int getEmfRecordSize() { |
| return emfRecordSize; |
| } |
| |
| public byte[] getEmfData() { |
| return emfData; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| final Map<String,Supplier<?>> m = new LinkedHashMap<>(); |
| m.put("commentIdentifier", () -> commentIdentifier); |
| m.put("commentType", () -> commentType); |
| m.put("version", () -> version); |
| m.put("checksum", () -> checksum); |
| m.put("flags", () -> flags); |
| m.put("commentRecordCount", this::getCommentRecordCount); |
| m.put("currentRecordSize", this::getCurrentRecordSize); |
| m.put("remainingBytes", this::getRemainingBytes); |
| m.put("emfRecordSize", this::getEmfRecordSize); |
| m.put("emfData", this::getEmfData); |
| return Collections.unmodifiableMap(m); |
| } |
| } |
| } |