Package org.pdfclown.documents.contents.fonts

Source Code of org.pdfclown.documents.contents.fonts.Font

/*
  Copyright 2006-2010 Stefano Chizzolini. http://www.pdfclown.org

  Contributors:
    * Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it)

  This file should be part of the source code distribution of "PDF Clown library"
  (the Program): see the accompanying README files for more info.

  This Program is free software; you can redistribute it and/or modify it under the terms
  of the GNU Lesser General Public License as published by the Free Software Foundation;
  either version 3 of the License, or (at your option) any later version.

  This Program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY,
  either expressed or implied; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.

  You should have received a copy of the GNU Lesser General Public License along with this
  Program (see README files); if not, go to the GNU website (http://www.gnu.org/licenses/).

  Redistribution and use, with or without modification, are permitted provided that such
  redistributions retain the above copyright notice, license and disclaimer, along with
  this list of conditions.
*/

package org.pdfclown.documents.contents.fonts;

import java.io.ByteArrayOutputStream;
import java.util.EnumSet;
import java.util.Hashtable;
import java.util.Map;

import org.pdfclown.PDF;
import org.pdfclown.VersionEnum;
import org.pdfclown.bytes.FileInputStream;
import org.pdfclown.bytes.IInputStream;
import org.pdfclown.documents.Document;
import org.pdfclown.files.File;
import org.pdfclown.objects.PdfArray;
import org.pdfclown.objects.PdfDictionary;
import org.pdfclown.objects.PdfDirectObject;
import org.pdfclown.objects.PdfInteger;
import org.pdfclown.objects.PdfName;
import org.pdfclown.objects.PdfNumber;
import org.pdfclown.objects.PdfObjectWrapper;
import org.pdfclown.objects.PdfReference;
import org.pdfclown.objects.PdfStream;
import org.pdfclown.util.BiMap;
import org.pdfclown.util.ByteArray;
import org.pdfclown.util.NotImplementedException;

/**
  Abstract font [PDF:1.6:5.4].

  @author Stefano Chizzolini (http://www.stefanochizzolini.it)
  @version 0.1.0
*/
@PDF(VersionEnum.PDF10)
public abstract class Font
  extends PdfObjectWrapper<PdfDictionary>
{
  // <class>
  // <classes>
  /**
    Font descriptor flags [PDF:1.6:5.7.1].
  */
  public enum FlagsEnum
  {
    /**
      All glyphs have the same width.
    */
    FixedPitch(0x1),
    /**
      Glyphs have serifs.
    */
    Serif(0x2),
    /**
      Font contains glyphs outside the Adobe standard Latin character set.
    */
    Symbolic(0x4),
    /**
      Glyphs resemble cursive handwriting.
    */
    Script(0x8),
    /**
      Font uses the Adobe standard Latin character set.
    */
    Nonsymbolic(0x20),
    /**
      Glyphs have dominant vertical strokes that are slanted.
    */
    Italic(0x40),
    /**
      Font contains no lowercase letters.
    */
    AllCap(0x10000),
    /**
      Font contains both uppercase and lowercase letters.
    */
    SmallCap(0x20000),
    /**
      Thicken bold glyphs at small text sizes.
    */
    ForceBold(0x40000);

    /**
      Converts an enumeration set into its corresponding bit mask representation.
    */
    public static int toInt(
      EnumSet<FlagsEnum> flags
      )
    {
      int flagsMask = 0;
      for(FlagsEnum flag : flags)
      {flagsMask |= flag.getCode();}

      return flagsMask;
    }

    /**
      Converts a bit mask into its corresponding enumeration representation.
    */
    public static EnumSet<FlagsEnum> toEnumSet(
      int flagsMask
      )
    {
      EnumSet<FlagsEnum> flags = EnumSet.noneOf(FlagsEnum.class);
      for(FlagsEnum flag : FlagsEnum.values())
      {
        if((flagsMask & flag.getCode()) > 0)
        {flags.add(flag);}
      }

      return flags;
    }

    private final int code;

    private FlagsEnum(
      int code
      )
    {this.code = code;}

    public int getCode(
      )
    {return code;}
  }
  // </classes>

  // <static>
  // <interface>
  // <public>
  /**
   * Creates the representation of a font.
   */
  public static Font get(
    Document context,
    String path
    )
  {
    try
    {
      return get(
        context,
        new FileInputStream(
          new java.io.RandomAccessFile(path,"r")
          )
        );
    }
    catch(Exception e)
    {throw new RuntimeException(e);}
  }

  /**
   * Creates the representation of a font.
   */
  public static Font get(
    Document context,
    java.io.File file
    )
  {return get(context,file.getPath());}

  /**
   * Creates the representation of a font.
   */
  public static Font get(
    Document context,
    IInputStream fontData
    )
  {
    if(OpenFontParser.isOpenFont(fontData))
      return CompositeFont.get(context,fontData);
    else
      throw new NotImplementedException();
   }

  /**
    Gets the scaling factor to be applied to unscaled metrics to get actual measures.
  */
  public static final float getScalingFactor(
    float size
    )
  {return 0.001f * size;}

  /**
    Wraps a font reference into a font object.

    @param reference Reference to a font object.
    @return Font object associated to the reference.
  */
  public static final Font wrap(
    PdfReference reference
    )
  {
    if(reference == null)
      return null;

    {
      // Has the font been already instantiated?
      /*
        NOTE: Font structures are reified as complex objects, both IO- and CPU-intensive to load.
        So, it's convenient to retrieve them from a common cache whenever possible.
      */
      Hashtable<PdfReference,Object> cache = reference.getIndirectObject().getFile().getDocument().cache;
      if(cache.containsKey(reference))
        return (Font)cache.get(reference);
    }

    PdfDictionary fontDictionary = (PdfDictionary)reference.getDataObject();
    PdfName fontType = (PdfName)fontDictionary.get(PdfName.Subtype);
    if(fontType == null)
      throw new RuntimeException("Font type undefined (reference: " + reference + ")");

    if(fontType.equals(PdfName.Type1)) // Type 1.
    {
      if(!fontDictionary.containsKey(PdfName.FontDescriptor)) // Standard Type 1.
        return new StandardType1Font(reference);
      else // Custom Type 1.
      {
        PdfDictionary fontDescriptor = (PdfDictionary)fontDictionary.resolve(PdfName.FontDescriptor);
        if(fontDescriptor.containsKey(PdfName.FontFile3)
            && ((PdfName)((PdfStream)fontDescriptor.resolve(PdfName.FontFile3)).getHeader().resolve(PdfName.Subtype)).equals(PdfName.OpenType)) // OpenFont/CFF.
          throw new NotImplementedException();
        else // Non-OpenFont Type 1.
          return new Type1Font(reference);
      }
    }
    else if(fontType.equals(PdfName.TrueType)) // TrueType.
      return new TrueTypeFont(reference);
    else if(fontType.equals(PdfName.Type0)) // OpenFont.
    {
      PdfDictionary cidFontDictionary = (PdfDictionary)((PdfArray)fontDictionary.resolve(PdfName.DescendantFonts)).resolve(0);
      PdfName cidFontType = (PdfName)cidFontDictionary.get(PdfName.Subtype);
      if(cidFontType.equals(PdfName.CIDFontType0)) // OpenFont/CFF.
        return new Type0Font(reference);
      else if(cidFontType.equals(PdfName.CIDFontType2)) // OpenFont/TrueType.
        return new Type2Font(reference);
      else
        throw new NotImplementedException("Type 0 subtype " + cidFontType + " not supported yet.");
    }
    else if(fontType.equals(PdfName.Type3)) // Type 3.
      return new Type3Font(reference);
    else if(fontType.equals(PdfName.MMType1)) // MMType1.
      return new MMType1Font(reference);
    else // Unknown.
      throw new UnsupportedOperationException("Unknown font type: " + fontType + " (reference: " + reference + ")");
  }
  // </public>
  // </interface>
  // </static>

  // <dynamic>
  // <fields>
  /*
    NOTE: In order to avoid nomenclature ambiguities, these terms are used consistently within the code:
    * unicode: character encoded according to the Unicode standard;
    * character code: codepoint corresponding to a character expressed inside a string object of a content stream;
    * glyph index: identifier of the graphical representation of a character.
  */
  /**
    Unicodes by character code.
    <h3>Note</h3>
    <p>When this map is populated, {@link #symbolic} variable shall accordingly be set.</p>
  */
  protected BiMap<ByteArray,Integer> codes;
  /**
    Default glyph width.
  */
  protected int defaultGlyphWidth;
  /**
    Glyph indexes by unicode.
  */
  protected Map<Integer,Integer> glyphIndexes;
  /**
    Glyph kernings by (left-right) glyph index pairs.
  */
  protected Map<Integer,Integer> glyphKernings;
  /**
    Glyph widths by glyph index.
  */
  protected Map<Integer,Integer> glyphWidths;
  /**
    Whether the font encoding is custom (that is non-Unicode).
  */
  protected boolean symbolic = true;

  /**
    Maximum character code byte size.
  */
  private int charCodeMaxLength = 0;
  // </fields>

  // <constructors>
  /**
    Creates a new font structure within the given document context.
  */
  protected Font(
    Document context
    )
  {
    super(
      context.getFile(),
      new PdfDictionary(
        new PdfName[]{PdfName.Type},
        new PdfDirectObject[]{PdfName.Font}
        )
      );
    initialize();
  }

  /**
    Loads an existing font structure.
  */
  protected Font(
    PdfDirectObject baseObject
    )
  {
    super(
      baseObject,
      null // NO container. NOTE: this is a simplification (the spec [PDF:1.6] doesn't apparently prescribe the use of an indirect object for font dictionary, whilst the general practice is as such. If an exception occurs, you'll need to specify the proper container).
      );
    initialize();
    load();
  }
  // </constructors>

  // <interface>
  // <public>
  /**
    Gets the text from the given internal representation.

    @param code Internal representation to decode.
    @since 0.0.6
  */
  public final String decode(
    byte[] code
    )
  {
    StringBuilder textBuilder = new StringBuilder();
    {
      byte[][] codeBuffers = new byte[charCodeMaxLength+1][];
      for(
        int codeBufferIndex = 0;
        codeBufferIndex <= charCodeMaxLength;
        codeBufferIndex++
        )
      {codeBuffers[codeBufferIndex] = new byte[codeBufferIndex];}
      int position = 0;
      int codeLength = code.length;
      int codeBufferSize = 1;
      while(position < codeLength)
      {
        byte[] codeBuffer = codeBuffers[codeBufferSize];
        System.arraycopy(code,position,codeBuffer,0,codeBufferSize);
        Integer textChar = codes.get(new ByteArray(codeBuffer));
        if(textChar == null)
        {
          if(codeBufferSize < charCodeMaxLength)
          {
            codeBufferSize++;
            continue;
          }
          /*
            NOTE: In case no valid code entry is found, a default space is resiliantely
            applied instead of throwing an exception.
            This is potentially risky as failing to determine the actual code length
            may result in a "code shifting" which could affect following characters.
           */
          textChar = (int)' ';
        }
        textBuilder.append((char)(int)textChar);
        position += codeBufferSize;
        codeBufferSize = 1;
      }
    }
    return textBuilder.toString();
  }

  /**
    Gets the internal representation of the given text.

    @param text Text to encode.
    @since 0.0.6
  */
  public final byte[] encode(
    String text
    )
  {
    ByteArrayOutputStream encodedStream = new ByteArrayOutputStream();
    try
    {
      for(char textChar : text.toCharArray())
      {
        byte[] charCode = codes.getKey((int)textChar).data;
        encodedStream.write(charCode);
      }
      encodedStream.close();
    }
    catch(Exception exception)
    {throw new RuntimeException(exception);}

    return encodedStream.toByteArray();
  }

  @Override
  public final boolean equals(
    Object object
    )
  {
    return object != null
      && object.getClass().equals(getClass())
      && ((Font)object).getName().equals(getName());
  }

  /**
    Gets the unscaled vertical offset from the baseline to the ascender line (ascent).
    The value is a positive number.
  */
  public float getAscent(
    )
  {return ((PdfNumber<?>)getDescriptor().get(PdfName.Ascent)).getNumberValue();}

  /**
    Gets the vertical offset from the baseline to the ascender line (ascent),
    scaled to the given font size.
    The value is a positive number.

    @param size Font size.
  */
  public final float getAscent(
    float size
    )
  {return getAscent() * getScalingFactor(size);}

  /**
    Gets the unscaled vertical offset from the baseline to the descender line (descent).
    The value is a negative number.
  */
  public float getDescent(
    )
  {return ((PdfNumber<?>)getDescriptor().get(PdfName.Descent)).getNumberValue();}

  /**
    Gets the vertical offset from the baseline to the descender line (descent), scaled to the given font size.
    The value is a negative number.

    @param size Font size.
  */
  public final float getDescent(
    float size
    )
  {return getDescent() * getScalingFactor(size);}

  /**
    Gets the font descriptor flags.
  */
  public EnumSet<FlagsEnum> getFlags(
    )
  {
    PdfInteger flagsObject = (PdfInteger)File.resolve(
      getDescriptor().get(PdfName.Flags)
      );
    if(flagsObject == null)
      return EnumSet.noneOf(FlagsEnum.class);

    return FlagsEnum.toEnumSet(flagsObject.getRawValue());
  }

  /**
    Gets the unscaled height of the given character.

    @param textChar Character whose height has to be calculated.
  */
  public final float getHeight(
    char textChar
    )
  {return getLineHeight();}

  /**
    Gets the height of the given character, scaled to the given font size.

    @param textChar Character whose height has to be calculated.
    @param size Font size.
  */
  public final float getHeight(
    char textChar,
    float size
    )
  {return getHeight(textChar) * getScalingFactor(size);}

  /**
    Gets the unscaled height of the given text.

    @param text Text whose height has to be calculated.
  */
  public final float getHeight(
    String text
    )
  {return getLineHeight();}

  /**
    Gets the height of the given text, scaled to the given font size.

    @param text Text whose height has to be calculated.
    @param size Font size.
  */
  public final float getHeight(
    String text,
    float size
    )
  {return getHeight(text) * getScalingFactor(size);}

  /**
    Gets the width (kerning inclusive) of the given text, scaled to the given font size.

    @param text Text whose width has to be calculated.
    @param size Font size.
  */
  public final float getKernedWidth(
    String text,
    float size
    )
  {return (getWidth(text) + getKerning(text)) * getScalingFactor(size);}

  /**
    Gets the unscaled kerning width between two given characters.

    @param textChar1 Left character.
    @param textChar2 Right character,
  */
  public final int getKerning(
    char textChar1,
    char textChar2
    )
  {
    try
    {
      return glyphKernings.get(
        glyphIndexes.get((int)textChar1) << 16 // Left-hand glyph index.
          + glyphIndexes.get((int)textChar2) // Right-hand glyph index.
        );
    }
    catch(Exception e)
    {return 0;}
  }

  /**
    Gets the unscaled kerning width inside the given text.

    @param text Text whose kerning has to be calculated.
  */
  public final int getKerning(
    String text
    )
  {
    int kerning = 0;
    // Are kerning pairs available?
    if(glyphKernings != null)
    {
      char textChars[] = text.toCharArray();
      for(
        int index = 0,
          length = text.length() - 1;
        index < length;
        index++
        )
      {
        kerning += getKerning(
          textChars[index],
          textChars[index + 1]
          );
      }
    }
    return kerning;
  }

  /**
    Gets the kerning width inside the given text, scaled to the given font size.

    @param text Text whose kerning has to be calculated.
    @param size Font size.
  */
  public final float getKerning(
    String text,
    float size
    )
  {return getKerning(text) * getScalingFactor(size);}

  /**
    Gets the unscaled line height.
  */
  public float getLineHeight(
    )
  {return getAscent() - getDescent();}

  /**
    Gets the line height, scaled to the given font size.

    @param size Font size.
  */
  public final float getLineHeight(
    float size
    )
  {return getLineHeight() * getScalingFactor(size);}

  /**
    Gets the PostScript name of the font.
  */
  public final String getName(
    )
  {return ((PdfName)getBaseDataObject().get(PdfName.BaseFont)).toString();}

  /**
    Gets the unscaled width of the given character.

    @param textChar Character whose width has to be calculated.
  */
  public int getWidth(
    char textChar
    )
  {
    Integer glyphWidth = glyphWidths.get(glyphIndexes.get((int)textChar));
    if(glyphWidth == null)
      return defaultGlyphWidth;
    else
      return glyphWidth;
  }

  /**
    Gets the width of the given character, scaled to the given font size.

    @param textChar Character whose height has to be calculated.
    @param size Font size.
  */
  public final float getWidth(
    char textChar,
    float size
    )
  {return getWidth(textChar) * getScalingFactor(size);}

  /**
    Gets the unscaled width (kerning exclusive) of the given text.

    @param text Text whose width has to be calculated.
  */
  public int getWidth(
    String text
    )
  {
    int width = 0;
    for(char textChar : text.toCharArray())
    {width += getWidth(textChar);}

    return width;
  }

  /**
    Gets the width (kerning exclusive) of the given text, scaled to the given font size.

    @param text Text whose width has to be calculated.
    @param size Font size.
  */
  public final float getWidth(
    String text,
    float size
    )
  {return getWidth(text) * getScalingFactor(size);}

  @Override
  public int hashCode(
    )
  {return getName().hashCode();}

  /**
    Gets whether the font encoding is custom (that is non-Unicode).
  */
  public boolean isSymbolic(
    )
  {return symbolic;}
  // </public>

  // <protected>
  /**
    Gets the font descriptor.
  */
  protected abstract PdfDictionary getDescriptor(
    );

  /**
    Loads font information from existing PDF font structure.
  */
  protected void load(
    )
  {
    if(getBaseDataObject().containsKey(PdfName.ToUnicode)) // To-Unicode explicit mapping.
    {
      PdfStream toUnicodeStream = (PdfStream)getBaseDataObject().resolve(PdfName.ToUnicode);
      CMapParser parser = new CMapParser(toUnicodeStream.getBody());
      codes = new BiMap<ByteArray,Integer>(parser.parse());
      symbolic = false;
    }

    onLoad();

    // Maximum character code length.
    for(ByteArray charCode : codes.keySet())
    {
      if(charCode.data.length > charCodeMaxLength)
      {charCodeMaxLength = charCode.data.length;}
    }
  }

  /**
    Notifies the loading of font information from an existing PDF font structure.
   */
  protected abstract void onLoad(
    );
  // </protected>

  // <private>
  private void initialize(
    )
  {
    // Put the newly instantiated font into the common cache!
    /*
      NOTE: Font structures are reified as complex objects, both IO- and CPU-intensive to load.
      So, it's convenient to put them into a common cache for later reuse.
    */
    getDocument().cache.put((PdfReference)getBaseObject(),this);
  }
  // </private>
  // </interface>
  // </dynamic>
  // </class>
}
TOP

Related Classes of org.pdfclown.documents.contents.fonts.Font

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.