| /* ==================================================================== |
| 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.emf; |
| |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| 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.common.usermodel.fonts.FontCharset; |
| import org.apache.poi.hwmf.record.HwmfFont; |
| 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; |
| |
| public class HemfFont extends HwmfFont { |
| private static final int LOGFONT_SIZE = 92; |
| private static final int LOGFONTPANOSE_SIZE = 320; |
| |
| protected interface LogFontDetails {} |
| |
| protected static class LogFontExDv implements LogFontDetails { |
| protected int[] designVector; |
| |
| @Override |
| public String toString() { |
| return "{ designVectorLen: " + (designVector == null ? 0 : designVector.length) + " }"; |
| } |
| } |
| |
| protected static class LogFontPanose implements LogFontDetails, GenericRecord { |
| enum FamilyType { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_FAMILY_TEXT_DISPLAY, |
| PAN_FAMILY_SCRIPT, |
| PAN_FAMILY_DECORATIVE, |
| PAN_FAMILY_PICTORIAL |
| } |
| |
| enum SerifType { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_SERIF_COVE, |
| PAN_SERIF_OBTUSE_COVE, |
| PAN_SERIF_SQUARE_COVE, |
| PAN_SERIF_OBTUSE_SQUARE_COVE, |
| PAN_SERIF_SQUARE, |
| PAN_SERIF_THIN, |
| PAN_SERIF_BONE, |
| PAN_SERIF_EXAGGERATED, |
| PAN_SERIF_TRIANGLE, |
| PAN_SERIF_NORMAL_SANS, |
| PAN_SERIF_OBTUSE_SANS, |
| PAN_SERIF_PERP_SANS, |
| PAN_SERIF_FLARED, |
| PAN_SERIF_ROUNDED |
| } |
| |
| enum FontWeight { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_WEIGHT_VERY_LIGHT, |
| PAN_WEIGHT_LIGHT, |
| PAN_WEIGHT_THIN, |
| PAN_WEIGHT_BOOK, |
| PAN_WEIGHT_MEDIUM, |
| PAN_WEIGHT_DEMI, |
| PAN_WEIGHT_BOLD, |
| PAN_WEIGHT_HEAVY, |
| PAN_WEIGHT_BLACK, |
| PAN_WEIGHT_NORD |
| } |
| |
| enum Proportion { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_PROP_OLD_STYLE, |
| PAN_PROP_MODERN, |
| PAN_PROP_EVEN_WIDTH, |
| PAN_PROP_EXPANDED, |
| PAN_PROP_CONDENSED, |
| PAN_PROP_VERY_EXPANDED, |
| PAN_PROP_VERY_CONDENSED, |
| PAN_PROP_MONOSPACED |
| } |
| |
| enum Contrast { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_CONTRAST_NONE, |
| PAN_CONTRAST_VERY_LOW, |
| PAN_CONTRAST_LOW, |
| PAN_CONTRAST_MEDIUM_LOW, |
| PAN_CONTRAST_MEDIUM, |
| PAN_CONTRAST_MEDIUM_HIGH, |
| PAN_CONTRAST_HIGH, |
| PAN_CONTRAST_VERY_HIGH |
| } |
| |
| enum StrokeVariation { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_STROKE_GRADUAL_DIAG, |
| PAN_STROKE_GRADUAL_TRAN, |
| PAN_STROKE_GRADUAL_VERT, |
| PAN_STROKE_GRADUAL_HORZ, |
| PAN_STROKE_RAPID_VERT, |
| PAN_STROKE_RAPID_HORZ, |
| PAN_STROKE_INSTANT_VERT |
| } |
| |
| enum ArmStyle { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_STRAIGHT_ARMS_HORZ, |
| PAN_STRAIGHT_ARMS_WEDGE, |
| PAN_STRAIGHT_ARMS_VERT, |
| PAN_STRAIGHT_ARMS_SINGLE_SERIF, |
| PAN_STRAIGHT_ARMS_DOUBLE_SERIF, |
| PAN_BENT_ARMS_HORZ, |
| PAN_BENT_ARMS_WEDGE, |
| PAN_BENT_ARMS_VERT, |
| PAN_BENT_ARMS_SINGLE_SERIF, |
| PAN_BENT_ARMS_DOUBLE_SERIF |
| } |
| |
| enum Letterform { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_LETT_NORMAL_CONTACT, |
| PAN_LETT_NORMAL_WEIGHTED, |
| PAN_LETT_NORMAL_BOXED, |
| PAN_LETT_NORMAL_FLATTENED, |
| PAN_LETT_NORMAL_ROUNDED, |
| PAN_LETT_NORMAL_OFF_CENTER, |
| PAN_LETT_NORMAL_SQUARE, |
| PAN_LETT_OBLIQUE_CONTACT, |
| PAN_LETT_OBLIQUE_WEIGHTED, |
| PAN_LETT_OBLIQUE_BOXED, |
| PAN_LETT_OBLIQUE_FLATTENED, |
| PAN_LETT_OBLIQUE_ROUNDED, |
| PAN_LETT_OBLIQUE_OFF_CENTER, |
| PAN_LETT_OBLIQUE_SQUARE |
| } |
| |
| enum MidLine { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_MIDLINE_STANDARD_TRIMMED, |
| PAN_MIDLINE_STANDARD_POINTED, |
| PAN_MIDLINE_STANDARD_SERIFED, |
| PAN_MIDLINE_HIGH_TRIMMED, |
| PAN_MIDLINE_HIGH_POINTED, |
| PAN_MIDLINE_HIGH_SERIFED, |
| PAN_MIDLINE_CONSTANT_TRIMMED, |
| PAN_MIDLINE_CONSTANT_POINTED, |
| PAN_MIDLINE_CONSTANT_SERIFED, |
| PAN_MIDLINE_LOW_TRIMMED, |
| PAN_MIDLINE_LOW_POINTED, |
| PAN_MIDLINE_LOW_SERIFED |
| } |
| |
| enum XHeight { |
| PAN_ANY, |
| PAN_NO_FIT, |
| PAN_XHEIGHT_CONSTANT_SMALL, |
| PAN_XHEIGHT_CONSTANT_STD, |
| PAN_XHEIGHT_CONSTANT_LARGE, |
| PAN_XHEIGHT_DUCKING_SMALL, |
| PAN_XHEIGHT_DUCKING_STD, |
| PAN_XHEIGHT_DUCKING_LARGE |
| } |
| |
| protected int styleSize; |
| protected int vendorId; |
| protected int culture; |
| protected FamilyType familyType; |
| protected SerifType serifStyle; |
| protected FontWeight weight; |
| protected Proportion proportion; |
| protected Contrast contrast; |
| protected StrokeVariation strokeVariation; |
| protected ArmStyle armStyle; |
| protected Letterform letterform; |
| protected MidLine midLine; |
| protected XHeight xHeight; |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| final Map<String,Supplier<?>> m = new LinkedHashMap<>(); |
| m.put("styleSize", () -> styleSize); |
| m.put("vendorId", () -> vendorId); |
| m.put("culture", () -> culture); |
| m.put("familyType", () -> familyType); |
| m.put("serifStyle", () -> serifStyle); |
| m.put("weight", () -> weight); |
| m.put("proportion", () -> proportion); |
| m.put("contrast", () -> contrast); |
| m.put("strokeVariation", () -> strokeVariation); |
| m.put("armStyle", () -> armStyle); |
| m.put("letterform", () -> letterform); |
| m.put("midLine", () -> midLine); |
| m.put("xHeight", () -> xHeight); |
| return Collections.unmodifiableMap(m); |
| } |
| } |
| |
| protected String fullname; |
| protected String style; |
| protected String script; |
| |
| protected LogFontDetails details; |
| |
| @SuppressWarnings("unused") |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize) throws IOException { |
| // A 32-bit signed integer that specifies the height, in logical units, of the font's |
| // character cell or character. The character height value, also known as the em size, is the |
| // character cell height value minus the internal leading value. The font mapper SHOULD |
| // interpret the value specified in the Height field in the following manner. |
| // |
| // 0x00000000 < value: |
| // The font mapper transforms this value into device units and matches it against |
| // the cell height of the available fonts. |
| // |
| // 0x00000000 |
| // The font mapper uses a default height value when it searches for a match. |
| // |
| // value < 0x00000000: |
| // The font mapper transforms this value into device units and matches its |
| // absolute value against the character height of the available fonts. |
| // |
| // For all height comparisons, the font mapper SHOULD look for the largest font that does not |
| // exceed the requested size. |
| height = leis.readInt(); |
| |
| // A 32-bit signed integer that specifies the average width, in logical units, of |
| // characters in the font. If the Width field value is zero, an appropriate value SHOULD be |
| // calculated from other LogFont values to find a font that has the typographer's intended |
| // aspect ratio. |
| width = leis.readInt(); |
| |
| // A 32-bit signed integer that specifies the angle, in tenths of degrees, |
| // between the escapement vector and the x-axis of the device. The escapement vector is |
| // parallel to the baseline of a row of text. |
| // |
| // When the graphics mode is set to GM_ADVANCED, the escapement angle of the string can |
| // be specified independently of the orientation angle of the string's characters. |
| escapement = leis.readInt(); |
| |
| // A 32-bit signed integer that specifies the angle, in tenths of degrees, |
| // between each character's baseline and the x-axis of the device. |
| orientation = leis.readInt(); |
| |
| // A 32-bit signed integer that specifies the weight of the font in the range zero through 1000. |
| // For example, 400 is normal and 700 is bold. If this value is zero, a default weight can be used. |
| weight = leis.readInt(); |
| |
| // An 8-bit unsigned integer that specifies an italic font if set to 0x01; |
| // otherwise, it MUST be set to 0x00. |
| italic = (leis.readUByte() != 0x00); |
| |
| // An 8-bit unsigned integer that specifies an underlined font if set to 0x01; |
| // otherwise, it MUST be set to 0x00. |
| underline = (leis.readUByte() != 0x00); |
| |
| // An 8-bit unsigned integer that specifies a strikeout font if set to 0x01; |
| // otherwise, it MUST be set to 0x00. |
| strikeOut = (leis.readUByte() != 0x00); |
| |
| // An 8-bit unsigned integer that specifies the set of character glyphs. |
| // It MUST be a value in the WMF CharacterSet enumeration. |
| // If the character set is unknown, metafile processing SHOULD NOT attempt |
| // to translate or interpret strings that are rendered with that font. |
| // If a typeface name is specified in the Facename field, the CharSet field |
| // value MUST match the character set of that typeface. |
| charSet = FontCharset.valueOf(leis.readUByte()); |
| |
| // An 8-bit unsigned integer that specifies the output precision. |
| // The output precision defines how closely the font is required to match the requested height, width, |
| // character orientation, escapement, pitch, and font type. |
| // It MUST be a value from the WMF OutPrecision enumeration. |
| // Applications can use the output precision to control how the font mapper chooses a font when the |
| // operating system contains more than one font with a specified name. For example, if an operating |
| // system contains a font named Symbol in rasterized and TrueType forms, an output precision value |
| // of OUT_TT_PRECIS forces the font mapper to choose the TrueType version. |
| // A value of OUT_TT_ONLY_PRECIS forces the font mapper to choose a TrueType font, even if it is |
| // necessary to substitute a TrueType font with another name. |
| outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); |
| |
| // An 8-bit unsigned integer that specifies the clipping precision. |
| // The clipping precision defines how to clip characters that are partially outside the clipping region. |
| // It can be one or more of the WMF ClipPrecision Flags |
| clipPrecision.init(leis); |
| |
| // An 8-bit unsigned integer that specifies the output quality. The output quality defines how closely |
| // to attempt to match the logical-font attributes to those of an actual physical font. |
| // It MUST be one of the values in the WMF FontQuality enumeration |
| quality = WmfFontQuality.valueOf(leis.readUByte()); |
| |
| // A WMF PitchAndFamily object that specifies the pitch and family of the font. |
| // Font families describe the look of a font in a general way. |
| // They are intended for specifying a font when the specified typeface is not available. |
| pitchAndFamily = leis.readUByte(); |
| |
| int size = 5* LittleEndianConsts.INT_SIZE+8*LittleEndianConsts.BYTE_SIZE; |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| // A string of no more than 32 Unicode characters that specifies the typeface name of the font. |
| // If the length of this string is less than 32 characters, a terminating NULL MUST be present, |
| // after which the remainder of this field MUST be ignored. |
| int readBytes = readString(leis, sb, 32); |
| if (readBytes == -1) { |
| throw new IOException("Font facename can't be determined."); |
| } |
| facename = sb.toString(); |
| size += readBytes; |
| |
| if (recordSize <= LOGFONT_SIZE) { |
| return size; |
| } |
| |
| // A string of 64 Unicode characters that contains the font's full name. |
| // Ifthe length of this string is less than 64 characters, a terminating |
| // NULL MUST be present, after which the remainder of this field MUST be ignored. |
| readBytes = readString(leis, sb, 64); |
| if (readBytes == -1) { |
| throw new IOException("Font fullname can't be determined."); |
| } |
| fullname = sb.toString(); |
| size += readBytes; |
| |
| // A string of 32 Unicode characters that defines the font's style. If the length of |
| // this string is less than 32 characters, a terminating NULL MUST be present, |
| // after which the remainder of this field MUST be ignored. |
| readBytes = readString(leis, sb, 32); |
| if (readBytes == -1) { |
| throw new IOException("Font style can't be determined."); |
| } |
| style = sb.toString(); |
| size += readBytes; |
| |
| if (recordSize == LOGFONTPANOSE_SIZE) { |
| // LogFontPanose Object |
| |
| LogFontPanose logPan = new LogFontPanose(); |
| details = logPan; |
| |
| int version = leis.readInt(); |
| |
| // A 32-bit unsigned integer that specifies the point size at which font |
| //hinting is performed. If set to zero, font hinting is performed at the point size corresponding |
| //to the Height field in the LogFont object in the LogFont field. |
| logPan.styleSize = (int)leis.readUInt(); |
| |
| int match = leis.readInt(); |
| |
| int reserved = leis.readInt(); |
| |
| logPan.vendorId = leis.readInt(); |
| |
| logPan.culture = leis.readInt(); |
| |
| // An 8-bit unsigned integer that specifies the family type. |
| // The value MUST be in the FamilyType enumeration table. |
| logPan.familyType = LogFontPanose.FamilyType.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the serif style. |
| // The value MUST be in the SerifType enumeration table. |
| logPan.serifStyle = LogFontPanose.SerifType.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the weight of the font. |
| // The value MUST be in the Weight enumeration table. |
| logPan.weight = LogFontPanose.FontWeight.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the proportion of the font. |
| // The value MUST be in the Proportion enumeration table. |
| logPan.proportion = LogFontPanose.Proportion.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the proportion of the font. |
| // The value MUST be in the Proportion enumeration table. |
| logPan.contrast = LogFontPanose.Contrast.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the stroke variation for the font. |
| // The value MUST be in the StrokeVariation enumeration table. |
| logPan.strokeVariation = LogFontPanose.StrokeVariation.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the arm style of the font. |
| // The value MUST be in the ArmStyle enumeration table. |
| logPan.armStyle = LogFontPanose.ArmStyle.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the letterform of the font. |
| // The value MUST be in the Letterform enumeration table. |
| logPan.letterform = LogFontPanose.Letterform.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the midline of the font. |
| // The value MUST be in the MidLine enumeration table. |
| logPan.midLine = LogFontPanose.MidLine.values()[leis.readUByte()]; |
| |
| // An 8-bit unsigned integer that specifies the x height of the font. |
| // The value MUST be in the XHeight enumeration table. |
| logPan.xHeight = LogFontPanose.XHeight.values()[leis.readUByte()]; |
| |
| // skip 2 byte to ensure 32-bit alignment of this structure. |
| long skipped = IOUtils.skipFully(leis,2); |
| if (skipped != 2) { |
| throw new IOException("Didn't skip 2: "+skipped); |
| } |
| |
| size += 6*LittleEndianConsts.INT_SIZE+10* LittleEndianConsts.BYTE_SIZE+2; |
| } else { |
| // LogFontExDv Object |
| |
| LogFontExDv logEx = new LogFontExDv(); |
| details = logEx; |
| |
| // A string of 32 Unicode characters that defines the character set of the font. |
| // If the length of this string is less than 32 characters, a terminating NULL MUST be present, |
| // after which the remainder of this field MUST be ignored. |
| readBytes = readString(leis, sb, 32); |
| if (readBytes == -1) { |
| throw new IOException("Font script can't be determined."); |
| } |
| script = sb.toString(); |
| size += readBytes; |
| |
| // Design Vector |
| |
| // A 32-bit unsigned integer that MUST be set to the value 0x08007664. |
| int signature = leis.readInt(); |
| // some non-conformant applications don't write the magic code in |
| // assert (signature == 0x08007664); |
| |
| // A 32-bit unsigned integer that specifies the number of elements in the |
| // Values array. It MUST be in the range 0 to 16, inclusive. |
| int numAxes = leis.readInt(); |
| size += 2*LittleEndianConsts.INT_SIZE; |
| |
| // An optional array of 32-bit signed integers that specify the values of the font axes of a |
| // multiple master, OpenType font. The maximum number of values in the array is 16. |
| if (0 <= numAxes && numAxes <= 16) { |
| logEx.designVector = new int[numAxes]; |
| for (int i=0; i<numAxes; i++) { |
| logEx.designVector[i] = leis.readInt(); |
| } |
| size += numAxes*LittleEndianConsts.INT_SIZE; |
| } |
| } |
| |
| return size; |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "base", super::getGenericProperties, |
| "fullname", () -> fullname, |
| "style", () -> style, |
| "script", () -> script, |
| "details", () -> details |
| ); |
| } |
| |
| public void setHeight(double height) { |
| this.height = height; |
| } |
| |
| public void setWeight(int weight) { |
| this.weight = weight; |
| } |
| |
| public void setItalic(boolean italic) { |
| this.italic = italic; |
| } |
| |
| public void setUnderline(boolean underline) { |
| this.underline = underline; |
| } |
| |
| public void setStrikeOut(boolean strikeOut) { |
| this.strikeOut = strikeOut; |
| } |
| |
| public void setTypeface(String typeface) { |
| this.facename = typeface; |
| } |
| |
| @Override |
| protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) { |
| sb.setLength(0); |
| byte[] buf = new byte[limit * 2]; |
| leis.readFully(buf); |
| |
| int b1, b2, readBytes = 0; |
| do { |
| if (readBytes == limit*2) { |
| return -1; |
| } |
| |
| b1 = buf[readBytes++]; |
| b2 = buf[readBytes++]; |
| } while ((b1 != 0 || b2 != 0) && b1 != -1 && b2 != -1 && readBytes <= limit*2); |
| sb.append(new String(buf, 0, readBytes-2, StandardCharsets.UTF_16LE)); |
| |
| return limit*2; |
| } |
| } |
| |