/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed 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.icepdf.core.pobjects.fonts.ofont; import org.icepdf.core.pobjects.fonts.CMap; import org.icepdf.core.pobjects.fonts.Encoding; import org.icepdf.core.pobjects.fonts.FontFile; import org.icepdf.core.pobjects.graphics.TextState; import java.awt.*; import java.awt.Font; import java.awt.font.FontRenderContext; import java.awt.font.GlyphMetrics; import java.awt.font.GlyphVector; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * OFont is an awt Font wrapper used to aid in the paint of glyphs. * * @since 3.0 */ public class OFont implements FontFile { private static final Logger log = Logger.getLogger(OFont.class.toString()); private Font awtFont; private Rectangle2D maxCharBounds = new Rectangle2D.Double(0.0, 0.0, 1.0, 1.0); // text layout map, very expensive to create, so we'll cache them. private HashMap<String, Point2D.Float> echarAdvanceCache; protected float[] widths; protected Map<Integer, Float> cidWidths; protected float missingWidth; protected int firstCh; protected float ascent; protected float descent; protected Encoding encoding; protected CMap toUnicode; protected char[] cMap; public OFont(Font awtFont) { this.awtFont = awtFont; maxCharBounds = new Rectangle2D.Double(); this.echarAdvanceCache = new HashMap<String, Point2D.Float>(256); } private OFont(OFont font) { this.echarAdvanceCache = font.echarAdvanceCache; this.awtFont = font.awtFont; this.encoding = font.encoding; this.toUnicode = font.toUnicode; this.missingWidth = font.missingWidth; this.firstCh = font.firstCh; this.ascent = font.ascent; this.descent = font.descent; this.widths = font.widths; this.cidWidths = font.cidWidths; this.cMap = font.cMap; this.maxCharBounds = font.maxCharBounds; } public FontFile deriveFont(Encoding encoding, CMap toUnicode) { OFont font = new OFont(this); this.echarAdvanceCache.clear(); font.encoding = encoding; font.toUnicode = toUnicode; return font; } public FontFile deriveFont(float[] widths, int firstCh, float missingWidth, float ascent, float descent, char[] diff) { OFont font = new OFont(this); this.echarAdvanceCache.clear(); font.missingWidth = this.missingWidth; font.firstCh = firstCh; font.ascent = ascent; font.descent = descent; font.widths = widths; font.cMap = diff; return font; } public FontFile deriveFont(Map<Integer, Float> widths, int firstCh, float missingWidth, float ascent, float descent, char[] diff) { OFont font = new OFont(this); this.echarAdvanceCache.clear(); font.missingWidth = this.missingWidth; font.firstCh = firstCh; font.ascent = ascent; font.descent = descent; font.cidWidths = widths; font.cMap = diff; return font; } public FontFile deriveFont(AffineTransform at) { OFont font = new OFont(this); // clear font metric cache if we change the font's transform if (!font.getTransform().equals(this.awtFont.getTransform())) { this.echarAdvanceCache.clear(); } font.awtFont = this.awtFont.deriveFont(at); font.maxCharBounds = this.maxCharBounds; return font; } public boolean canDisplayEchar(char ech) { return true; } public FontFile deriveFont(float pointsize) { OFont font = new OFont(this); font.awtFont = this.awtFont.deriveFont(pointsize); font.maxCharBounds = this.maxCharBounds; return font; } public Point2D echarAdvance(final char ech) { // create a glyph vector for the char float advance; float advanceY; // check cache for existing layout String text = ech + "_" + awtFont.getSize(); Point2D.Float echarAdvance = echarAdvanceCache.get(text); // generate metrics is needed if (echarAdvance == null) { // the glyph vector should be created using any toUnicode value if present, as this is what we // are drawing, the method also does a check to apply differences if toUnicode is null. char echGlyph = getCMapping(ech); FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, true); GlyphVector glyphVector = awtFont.createGlyphVector( frc, String.valueOf(echGlyph)); TextLayout textLayout = new TextLayout(String.valueOf(echGlyph), awtFont, frc); // get bounds, only need to do this once. maxCharBounds = awtFont.getMaxCharBounds(frc); ascent = textLayout.getAscent(); descent = textLayout.getDescent(); GlyphMetrics glyphMetrics = glyphVector.getGlyphMetrics(0); advance = glyphMetrics.getAdvanceX(); advanceY = glyphMetrics.getAdvanceY(); echarAdvanceCache.put(text, new Point2D.Float(advance, advanceY)); } // returned cashed value else { advance = echarAdvance.x; advanceY = echarAdvance.y; } // widths uses original cid's, not the converted to unicode value. if (widths != null && ech - firstCh >= 0 && ech - firstCh < widths.length) { advance = widths[ech - firstCh] * awtFont.getSize2D(); } else if (cidWidths != null) { Float width = cidWidths.get((int) ech); if (width != null) { advance = cidWidths.get((int) ech) * awtFont.getSize2D(); } } // find any widths in the font descriptor else if (missingWidth > 0) { advance = missingWidth / 1000f; } return new Point2D.Float(advance, advanceY); } /** * Gets the ToUnicode character value for the given character. * * @param currentChar character to find a corresponding CMap for. * @return a new Character based on the CMap tranformation. If the character * can not be found in the CMap the orginal value is returned. */ private char getCMapping(char currentChar) { if (toUnicode != null) { return toUnicode.toSelector(currentChar); } return currentChar; } /** * Return the width of the given character * * @param character character to retreive width of * @return width of the given <code>character</code> */ public char getCharDiff(char character) { if (cMap != null && character < cMap.length) { return cMap[character]; } else { return character; } } private char findAlternateSymbol(char character) { // test for known symbol aliases for (int i = 0; i < org.icepdf.core.pobjects.fonts.ofont.Encoding.symbolAlaises.length; i++) { for (int j = 0; j < org.icepdf.core.pobjects.fonts.ofont.Encoding.symbolAlaises[i].length; j++) { if (org.icepdf.core.pobjects.fonts.ofont.Encoding.symbolAlaises[i][j] == character) { //System.out.println("found char " + Encoding.symbolAlaises[i][0]); return (char) org.icepdf.core.pobjects.fonts.ofont.Encoding.symbolAlaises[i][0]; } } } return character; } public CMap getToUnicode() { return toUnicode; } public int getStyle() { return awtFont.getStyle(); } public String getFamily() { return awtFont.getFamily(); } public float getSize() { return awtFont.getSize(); } public double getAscent() { return ascent; } public double getDescent() { return descent; } public Rectangle2D getMaxCharBounds() { return maxCharBounds; } public AffineTransform getTransform() { return awtFont.getTransform(); } public int getRights() { return 0; } public String getName() { return awtFont.getName(); } public boolean isHinted() { return false; } public void setIsCid() { } public int getNumGlyphs() { return awtFont.getNumGlyphs(); } public char getSpaceEchar() { return 32; } public Rectangle2D getEstringBounds(String estr, int beginIndex, int limit) { return null; } public String getFormat() { return null; } public void drawEstring(Graphics2D g, String displayText, float x, float y, long layout, int mode, Color strokecolor) { AffineTransform af = g.getTransform(); Shape outline = getEstringOutline(displayText, x, y); if (TextState.MODE_FILL == mode || TextState.MODE_FILL_STROKE == mode || TextState.MODE_FILL_ADD == mode || TextState.MODE_FILL_STROKE_ADD == mode) { g.fill(outline); } if (TextState.MODE_STROKE == mode || TextState.MODE_FILL_STROKE == mode || TextState.MODE_STROKE_ADD == mode || TextState.MODE_FILL_STROKE_ADD == mode) { g.draw(outline); } g.setTransform(af); } public String toUnicode(String displayText) { // Check string for displayable Glyphs, try and substitute any failed ones StringBuilder sb = new StringBuilder(displayText.length()); for (int i = 0; i < displayText.length(); i++) { // Updated with displayable glyph when possible sb.append(toUnicode(displayText.charAt(i))); } return sb.toString(); } public String toUnicode(char c1) { // the toUnicode map is used for font substitution and especially for CID fonts. If toUnicode is available // we use it as is, if not then we can use the charDiff mapping, which takes care of font encoding // differences. char c = toUnicode == null ? getCharDiff(c1) : c1; // The problem here is that some CMapping only work properly if the // embedded font is working properly, so that's how this logic works. //System.out.print((int)c + " (" + (char)c + ")"); // check for CMap ToUnicode properties, if so we return it, no point // jumping though the other hoops. if (toUnicode != null) { return toUnicode.toUnicode(c); } // otherwise work with a single char c = getCMapping(c); //System.out.print(" -> " + (int)c + " (" + (char)c + ")"); //System.out.println(); // try alternate representation of character if (!awtFont.canDisplay(c)) { c |= 0xF000; } // correct the character c if possible // if (!textState.font.font.canDisplay(c) && textState.font.font.canDisplay(c1)) { // c = c1; // } // due to different character encoding for invalid embedded fonts // the proper font can not always be found if (!awtFont.canDisplay(c)) { // try and find a similar symbol that can be displayed. c = findAlternateSymbol(c); // System.out.println(c + " + " + (int) c + " " + // textState.currentfont.getName() + " " + // textState.font.font ); } // Debug code, show any undisplayable glyphs if (log.isLoggable(Level.FINER)) { if (!awtFont.canDisplay(c)) { log.finer( ((int) c1) + " " + Character.toString(c1) + " " + (int) c + " " + c + " " + awtFont); //+ " " + textState.font.font + " " + textState.font.font.getNumGlyphs()); } } return String.valueOf(c); } public ByteEncoding getByteEncoding() { return ByteEncoding.ONE_BYTE; } public Shape getEstringOutline(String displayText, float x, float y) { displayText = toUnicode(displayText); FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, true); GlyphVector glyphVector = awtFont.createGlyphVector(frc, displayText); glyphVector.setGlyphPosition(0, new Point2D.Float(x, y)); // Iterate through displayText to calculate the the new advance value if // the displayLength is greater then one character. This in sures that // cid -> String will get displayed correctly. int displayLength = displayText.length(); float lastx; if (displayLength > 1) { Point2D p; float advance = 0; for (int i = 0; i < displayText.length(); i++) { // Position of the specified glyph relative to the origin of glyphVector p = glyphVector.getGlyphPosition(i); lastx = (float) p.getX(); // add fonts rise to the to glyph position (sup,sub scripts) glyphVector.setGlyphPosition( i, new Point2D.Double(lastx + advance, p.getY())); // subtract the advance because we will be getting it from the fonts width float adv1 = glyphVector.getGlyphMetrics(i).getAdvance(); double adv2 = echarAdvance(displayText.charAt(i)).getX(); advance += -adv1 + adv2 + lastx; } } return glyphVector.getOutline(); } public URL getSource() { return null; } }