/* ==================================================================== 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.hemf.record; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.RecordFormatException; /** * Container class to gather all text-related commands * This is starting out as read only, and very little is actually * implemented at this point! */ @Internal public class HemfText { private final static Charset UTF16LE = Charset.forName("UTF-16LE"); public static class ExtCreateFontIndirectW extends UnimplementedHemfRecord { } public static class ExtTextOutA implements HemfRecord { private long left,top,right,bottom; //TODO: translate this to a graphicsmode enum private long graphicsMode; private long exScale; private long eyScale; EmrTextObject textObject; @Override public HemfRecordType getRecordType() { return HemfRecordType.exttextouta; } @Override public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { //note that the first 2 uInts have been read off and the recordsize has //been decreased by 8 left = leis.readInt(); top = leis.readInt(); right = leis.readInt(); bottom = leis.readInt(); graphicsMode = leis.readUInt(); exScale = leis.readUInt(); eyScale = leis.readUInt(); int recordSizeInt = -1; if (recordSize < Integer.MAX_VALUE) { recordSizeInt = (int)recordSize; } else { throw new RecordFormatException("can't have text length > Integer.MAX_VALUE"); } //guarantee to read the rest of the EMRTextObjectRecord //emrtextbytes start after 7*4 bytes read above byte[] emrTextBytes = new byte[recordSizeInt-(7*LittleEndian.INT_SIZE)]; IOUtils.readFully(leis, emrTextBytes); textObject = new EmrTextObject(emrTextBytes, getEncodingHint(), 20);//should be 28, but recordSizeInt has already subtracted 8 return recordSize; } protected Charset getEncodingHint() { return null; } /** * * To be implemented! We need to get the current character set * from the current font for {@link ExtTextOutA}, * which has to be tracked in the playback device. * * For {@link ExtTextOutW}, the charset is "UTF-16LE" * * @param charset the charset to be used to decode the character bytes * @return text from this text element * @throws IOException */ public String getText(Charset charset) throws IOException { return textObject.getText(charset); } /** * * @return the x offset for the EmrTextObject */ public long getX() { return textObject.x; } /** * * @return the y offset for the EmrTextObject */ public long getY() { return textObject.y; } public long getLeft() { return left; } public long getTop() { return top; } public long getRight() { return right; } public long getBottom() { return bottom; } public long getGraphicsMode() { return graphicsMode; } public long getExScale() { return exScale; } public long getEyScale() { return eyScale; } } public static class ExtTextOutW extends ExtTextOutA { @Override public HemfRecordType getRecordType() { return HemfRecordType.exttextoutw; } @Override protected Charset getEncodingHint() { return UTF16LE; } public String getText() throws IOException { return getText(UTF16LE); } } /** * Needs to be implemented. Couldn't find example. */ public static class PolyTextOutA extends UnimplementedHemfRecord { } /** * Needs to be implemented. Couldn't find example. */ public static class PolyTextOutW extends UnimplementedHemfRecord { } public static class SetTextAlign extends UnimplementedHemfRecord { } public static class SetTextColor extends UnimplementedHemfRecord { } public static class SetTextJustification extends UnimplementedHemfRecord { } private static class EmrTextObject { long x; long y; int numChars; byte[] rawTextBytes;//this stores _all_ of the bytes to the end of the EMRTextObject record. //Because of potential variable length encodings, must //carefully read only the numChars from this byte array. EmrTextObject(byte[] emrTextObjBytes, Charset charsetHint, int readSoFar) throws IOException { int offset = 0; x = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE; y = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE; long numCharsLong = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE; long offString = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE; int start = (int)offString-offset-readSoFar; if (numCharsLong == 0) { rawTextBytes = new byte[0]; numChars = 0; return; } if (numCharsLong > Integer.MAX_VALUE) { throw new RecordFormatException("Number of characters can't be > Integer.MAX_VALUE"); } numChars = (int)numCharsLong; rawTextBytes = new byte[emrTextObjBytes.length-start]; System.arraycopy(emrTextObjBytes, start, rawTextBytes, 0, emrTextObjBytes.length-start); } String getText(Charset charset) throws IOException { StringBuilder sb = new StringBuilder(); Reader r = null; try { r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset); for (int i = 0; i < numChars; i++) { sb.appendCodePoint(readCodePoint(r)); } } finally { IOUtils.closeQuietly(r); } return sb.toString(); } //TODO: move this to IOUtils? private int readCodePoint(Reader r) throws IOException { int c1 = r.read(); if (c1 == -1) { throw new EOFException("Tried to read beyond byte array"); } if (!Character.isHighSurrogate((char)c1)) { return c1; } int c2 = r.read(); if (c2 == -1) { throw new EOFException("Tried to read beyond byte array"); } if (!Character.isLowSurrogate((char)c2)) { throw new RecordFormatException("Expected low surrogate after high surrogate"); } return Character.toCodePoint((char)c1, (char)c2); } } }