Simple PDF Creator in Java

Simple PDF Creator in Java

----------------- SmPDF.java -------------------

import java.io.*;
import java.nio.charset.*;
import java.util.*;
import java.util.AbstractMap.*;
import java.text.*;

public class SmPDF {

  public static void main(String argv[]) { new SmPDF().demo(); }

  static final float[] A4 = { 595.28f, 841.89f }, A3 = { 841.89f, 1190.55f }, LETTER = { 612f, 792f }, LEGAL = { 612f, 1008f };
  static final boolean PORTRAIT = true, LANDSCAPE = false;
  static final int TIMES_ROMAN = 0, TIMES_BOLD = 1, TIMES_ITALIC = 2, TIMES_BOLDITALIC = 3,
    HELVETICA = 4, HELVETICA_BOLD = 5, HELVETICA_OBLIQUE = 6, HELVETICA_BOLDOBLIQUE = 7,
    COURIER = 8, COURIER_BOLD = 9, COURIER_OBLIQUE = 10, COURIER_BOLDOBLIQUE = 11,
    SYMBOL = 12, ZAPFDINGBATS = 13;
  static final String[] stdFonts = { /*0*/ "Times-Roman", /*1*/ "Times-Bold", /*2*/ "Times-Italic", /*3*/ "Times-BoldItalic",
    /*4*/ "Helvetica", /*5*/ "Helvetica-Bold", /*6*/ "Helvetica-Oblique", /*7*/ "Helvetica-BoldOblique",
    /*8*/ "Courier", /*9*/ "Courier-Bold", /*10*/ "Courier-Oblique", /*11*/ "Courier-BoldOblique",
    /*12*/ "Symbol", /*13*/ "ZapfDingbats" };

  String CR = "\r\n", indt = "  ";
  DecimalFormat df;
  OutputStream out;
  int pos = 0;
  List<Integer> lstObj = new ArrayList<>();
  List<Page> pages = new ArrayList<>();
  List<Map.Entry<File, Boolean>> dImg = new ArrayList<>();
  List<Map.Entry<Object, Set<Integer>>> dFnt = new ArrayList<>();
  Map<Integer, Integer> dChr = new HashMap<>();

  SmPDF() {
    df = (DecimalFormat) NumberFormat.getInstance(Locale.ROOT);
    DecimalFormatSymbols symbols = new DecimalFormatSymbols();
    symbols.setDecimalSeparator('.');
    df.setDecimalFormatSymbols(symbols);
    df.setGroupingUsed(false);
    df.setMaximumFractionDigits(2);
  }

  void demo() {
    Page page = new Page(A4, PORTRAIT);
    int bg = setImage(new File("bg.jpg"));
    int fnt = setFont(new File("RampartOne-Regular.ttf"));
    //page.drawImage(bg, page.w, page.h, 0f, 0f);
    //page.drawString(fnt, "PDF", 150f, 150f, 600f);
    for (int i = 0; i < stdFonts.length; i++) {
      page.drawString(setFont(i), stdFonts[i], 24f, 100f, 450 - (i * 24));
    }
    page.addContent("q [3 4] 5 d 0 0 1 RG 70 100 450 400 re S Q");
    addPage(page);
    write(new File("demo.pdf"));
  }

  void write(File fl) {
    try (OutputStream os = new FileOutputStream(fl)) {
       write(os);
    } catch (Exception ex) { throw new RuntimeException(ex); }
  }

  void write(OutputStream out) {
    if(pages.size() == 0) throw new RuntimeException("No pages");
    this.out = out;
    write("%PDF-1.4", CR, "%\u00E2\u00E3\u00CF\u00D3", CR);
    int ctlg = obj(null, "/Type /Catalog", "/Pages " + (lstObj.size() + 2) + " 0 R");
    int lCont = 0;
    StringBuilder kids = new StringBuilder();
    for (int i = 0; i < pages.size(); i++) {
      if (i > 0) kids.append(' ');
      kids.append(3 + i * 2).append(" 0 R");
      if (!pages.get(i).isContEmpty()) lCont++;
    }
    int pg = obj(null, "/Type /Pages", "/Kids [" + kids.toString() + "]", "/Count " + pages.size());
    int[] alFnt = new int[dFnt.size()]; 
    int lFnt = 0;
    for (int i = 0; i < dFnt.size(); i++) {
      alFnt[i] = lFnt;
      Map.Entry<Object, Set<Integer>> entry = dFnt.get(i);
      if (entry.getValue().size() > 0) {
        lFnt += (entry.getKey() instanceof String ? 1 : 5);
      }
    }
    int[] alImg = new int[dImg.size()]; 
    int lImg = 0;
    for (int i = 0; i < dImg.size(); i++) {
      alImg[i] = lImg;
      Map.Entry<File, Boolean> entry = dImg.get(i);
      if (entry.getValue().booleanValue()) lImg++;
    }
    int offCont = pg + 1 + (pages.size() * 2);
    int offFnt = offCont + lCont;
    int offImg = offFnt + lFnt;
    int offUni = offImg + lImg;
    for (Page page : pages) {
      obj(null,
        "/Type /Page",
        "/Parent " + pg + " 0 R",
        "/Resources " + (lstObj.size() + 2) + " 0 R",
        (page.isContEmpty() ? null : "/Contents "+ (offCont++) +" 0 R"),
        "/MediaBox [0 0 " + ff(page.w, page.h) + "]");
      String sFnt = null, sImg = null;
      if (page.fnt.size() > 0) {
        Integer[] arr = page.fnt.toArray(new Integer[0]);
        Arrays.sort(arr);
        StringBuilder sb = new StringBuilder("/Font <<");
        for (int n, i = 0; i < arr.length; i++) {
          if (i > 0) sb.append(' ');
          n = arr[i].intValue();
          sb.append("/F").append(n + 1).append(' ').append(offFnt + alFnt[n]).append(" 0 R");
        }
        sFnt = sb.append(">>").toString();
      }
      if (page.img.size() > 0) {
        Integer[] arr = page.img.toArray(new Integer[0]);
        Arrays.sort(arr);
        StringBuilder sb = new StringBuilder("/XObject <<");
        for (int n, i = 0; i < arr.length; i++) {
          if (i > 0) sb.append(' ');
          n = arr[i].intValue();
          sb.append("/Im").append(n + 1).append(' ').append(offImg + alImg[n]).append(" 0 R");
        }
        sImg = sb.append(">>").toString();
      }
      obj(null, sFnt, sImg);
    }
//Contents
    for (Page page : pages) {
      if (!page.isContEmpty()) {
        obj(page.cont.toString().getBytes(StandardCharsets.ISO_8859_1));
      }
    }
//Fonts
    for (Map.Entry<Object, Set<Integer>> ob : dFnt) {
      if (ob.getValue().size() == 0) continue;
      if (ob.getKey() instanceof String) {
        obj(null,
          "/Type /Font",
          "/Subtype /Type1",
          "/BaseFont /" + (String) ob.getKey());
      } else if (ob.getKey() instanceof File) {
        File fl = (File) ob.getKey();
        try (RandomAccessFile raf = new RandomAccessFile(fl, "r")) {
          Fnt fnt = new Fnt(raf);
          String fname = "/ABCDEF+" + dec(fnt.name);
          obj(null,
            "/Type /Font",
            "/Subtype /Type0",
            "/BaseFont " + fname,
            "/Encoding /Identity-H",
            "/DescendantFonts [" + (lstObj.size() + 2) + " 0 R]",
            "/ToUnicode " + offUni + " 0 R");
          double k = (fnt.unitsPerEm < 16 ? 1 : 1000d / fnt.unitsPerEm);
          Integer[] arr = ob.getValue().toArray(new Integer[0]);
          Arrays.sort(arr, (t1, t2) -> dChr.get(t1).compareTo(dChr.get(t2)));
          StringBuilder sb = new StringBuilder("[");
          for (int nextcid = -1, i = 0; i < arr.length; i++) {
            int cid = dChr.get(arr[i]).intValue();
            if (nextcid != cid) {
              if (i > 0) sb.append("] ");
              sb.append(cid).append(" [");
            } else sb.append(' ');
            sb.append((int) (fnt.uni2width(arr[i].intValue()) * k));
            nextcid = cid + 1;
          }
          sb.append("]]");
          obj(null,
            "/Type /Font",
            "/Subtype /CIDFontType2",
            "/CIDSystemInfo <</Supplement 0 /Registry (Adobe) /Ordering (Identity)>>",
            "/BaseFont " + fname,
            "/FontDescriptor " + (lstObj.size() + 2) + " 0 R",
            "/CIDToGIDMap " + (lstObj.size() + 3) + " 0 R",
            "/W " + sb.toString());
          int ascent = (int) (fnt.ascender * k);
          int descent = (int) (fnt.descender * k);
          int xMin = (int) (fnt.xMin * k);
          int xMax = (int) (fnt.xMax * k);
          obj(null,
            "/Type /FontDescriptor",
            "/FontFile2 " + (lstObj.size() + 3) + " 0 R",
            "/FontName " + fname,
            "/Ascent "+ ascent,
            "/Descent "+ descent,
            "/CapHeight " + (int) (ascent * .8),
            "/FontBBox [" + xMin + " " + descent + " " + xMax + " " + ascent + "]",
            "/StemV " + (int) ((xMax - xMin) * .1),
            "/Flags 32",
            "/ItalicAngle 0");
          byte[] btgl = new byte[dChr.size() * 2];
          int cid, gid;
          for (Integer ich : ob.getValue()) {
            gid = fnt.uni2gid(ich.intValue());
            cid = dChr.get(ich).intValue();
            btgl[cid * 2] = (byte) (gid >> 8);
            btgl[cid * 2 + 1] = (byte) gid;
          }
          obj(btgl);
          obj(raf);
        } catch (Exception ex) { throw new RuntimeException(ex); }
      } else throw new RuntimeException(ob.getKey().getClass().getName() + " expected String or File");
    }
//XObject
    for (Map.Entry<File, Boolean> entry : dImg) {
      if (!entry.getValue().booleanValue()) continue;
      try (RandomAccessFile raf = new RandomAccessFile(entry.getKey(), "r")) {
        int[] size = getPictSize(raf);
        obj(raf,
          "/Type /XObject",
          "/Subtype /Image",
          "/Width " + size[0],
          "/Height " + size[1],
          "/BitsPerComponent 8",
          "/ColorSpace /DeviceRGB",
          "/Interpolate false",
          "/Filter /DCTDecode");
      } catch (Exception ex) { throw new RuntimeException(ex); }
    }
//CMap
    if (dChr.size() > 0) {
      obj(dict());
    }
    int info = obj(null, "/Creator (SmPDF)");
    int xpos = pos;
    write("xref", CR, "0 ", String.valueOf(lstObj.size() + 1), CR);
    String p = "%010d %05d %c" + CR;
    write(String.format(p, 0, 65535, 'f'));
    for (Integer i : lstObj) {
      write(String.format(p, i.intValue(), 0, 'n'));
    }
    write("trailer <</Size ", String.valueOf(lstObj.size() + 1), " /Root ", String.valueOf(ctlg), " 0 R /Info ", String.valueOf(info), " 0 R>>", CR,
      "startxref", CR, String.valueOf(xpos), CR, "%%EOF", CR);
  }

  void write(String... args) {
    for (String st : args) write(st.getBytes(StandardCharsets.ISO_8859_1));
  }

  void write(byte[] bt) {
    write(bt, 0, bt.length);
  }

  void write(byte[] bt, int off, int len) {
    try {
      out.write(bt, off, len);
      pos += len;
    } catch (IOException ex) { throw new RuntimeException(ex); }
  }

  int obj(Object strm, Object... args) {
    lstObj.add(Integer.valueOf(pos));
    StringBuilder sb = new StringBuilder();
    sb.append(lstObj.size()).append(" 0 obj <<").append(CR);
    for(Object s : args) {
      String str = (String) s;
      if (str != null && !str.isEmpty()) sb.append(indt).append(str).append(CR);
    }
    if (strm != null) {
      long length = 0;
      RandomAccessFile raf = null;
      if (strm instanceof RandomAccessFile) {
        raf = (RandomAccessFile) strm;
        try { length = raf.length(); } catch (IOException ex) { throw new RuntimeException(ex); }
      } else if (strm instanceof byte[]) {
        length = ((byte[]) strm).length;
      } else throw new RuntimeException(strm.getClass().getName() + " expected byte[] or RandomAccessFile");
      sb.append(indt).append("/Length " + length).append(CR);
      sb.append(">>").append(CR);
      sb.append("stream").append(CR);
      write(sb.toString());
      sb.setLength(0);
      if (raf != null) {
        try {
          int read;
          final int BUFFER_SIZE = 512;
          byte[] buff = new byte[BUFFER_SIZE];
          raf.seek(0);
          while ((read = raf.read(buff, 0, BUFFER_SIZE)) >= 0) {
            write(buff, 0, read);
          }
        } catch (IOException ex) { throw new RuntimeException(ex); }
      } else {
        write((byte[]) strm);
      }
      sb.append(CR).append("endstream").append(CR);
    } else {
      sb.append(">> ");
    }
    sb.append("endobj").append(CR);
    write(sb.toString());
    return lstObj.size();
  }

  int setImage(File fl) {
    dImg.add(new SimpleEntry<>(fl, Boolean.valueOf(false)));
    return dImg.size() - 1;
  }

  int setFont(int n) { return setFont(stdFonts[n]); }

  int setFont(Object ob) {
    dFnt.add(new SimpleEntry<>(ob, new HashSet<>()));
    return dFnt.size() - 1;
  }

  void addPage(Page... args) {
    for (Page page : args) pages.add(page);
  }

  String ff(float... args) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < args.length; i++) {
      if (i > 0) sb.append(' ');
      sb.append(df.format(args[i]));
    }
    return sb.toString();
  }

  String dec(String st) {
    byte[] bf = st.getBytes(StandardCharsets.UTF_8);
    StringBuilder sb = new StringBuilder();
    for (int c, i = 0; i < bf.length; i++){
      c = bf[i] & 0xFF;
      if (c < 33 || c == 35 || c > 126) sb.append(String.format("#%02X", c));
      else if (c != 0) sb.append((char) c);
    }
    return sb.toString();
  }

  public class Page {
    float w, h;
    Set<Integer> img = new HashSet<>();
    Set<Integer> fnt = new HashSet<>();
    StringBuilder cont = new StringBuilder();

    Page(float [] arr, boolean orient) {
      this((orient ? arr[0] : arr[1]), (orient ? arr[1] : arr[0]));
    }

    Page(float w, float h) {
      this.w = w;
      this.h = h;
    }

    void addContent(String... args) {
      if (cont.length() > 0) cont.append(CR);
      for (String st : args) cont.append(st);
    }

    boolean isContEmpty() {
      return cont.isEmpty();
    }

    void drawImage(int image, float w, float h, float x, float y) {
      if (image < 0 || image >= dImg.size()) throw new RuntimeException("Image " + image + " not exist");
      Integer iimage = Integer.valueOf(image);
      img.add(iimage);
      Boolean b = dImg.get(iimage).getValue().booleanValue();
      if (!b) dImg.get(iimage).setValue(Boolean.valueOf(true));
      addContent("q ", ff(w, 0f, 0f, h, x, y), " cm /Im", String.valueOf(iimage + 1)," Do Q");
    }

    void drawString(int font, String st, float size, float x, float y) {
      if (font < 0 || font >= dFnt.size()) throw new RuntimeException("Font " + font + " not exist");
      Integer ifont = Integer.valueOf(font);
      Object ofont = dFnt.get(ifont).getKey();
      Set<Integer> sChr = dFnt.get(ifont).getValue();
      boolean isFontStd = (ofont instanceof String);
      fnt.add(ifont);
      int len = st.length();
      if (isFontStd && sChr.size() == 0 && len > 0) sChr.add(null);
      addContent("BT /F", String.valueOf(font + 1), " ", ff(size), " Tf 1 0 0 1 ", ff(x, y), " Tm ");
      cont.append(isFontStd ? '(' : '<');
      for (int ch, cid = 0, i = 0; i < len; i++) {
        ch = st.charAt(i);
        if (isFontStd) {
          if (ch > 127) throw new RuntimeException("Standard font supports codes 0-127 found " + ch);
          if ((ch == '('|| ch == ')' || ch == '\\')) cont.append('\\');
          cont.append((char) ch);
        } else {
          Integer ich = Integer.valueOf(ch);
          Integer v = dChr.get(ich);
          if (v == null) {
            cid = dChr.size();
            dChr.put(ich, Integer.valueOf(cid));
          } else {
            cid = v.intValue();
          }
          sChr.add(ich);
          cont.append(String.format("%04X", cid));
        }
      }
      cont.append(isFontStd ? ')' : '>').append(" Tj ET");
    }
  }  // End class Page

  int[] getPictSize(RandomAccessFile r) throws IOException {
    int h = 0, w = 0;
    byte[] buf = new byte[11];
    r.seek(0);
    r.read(buf);
    if (buf[0] == -1 && buf[1] == -40 && buf[2] == -1 && buf[3] == -32 &&
        buf[6] == 74 && buf[7] == 70 && buf[8] == 73 && buf[9] == 70 && buf[10] == 0) {
      long length = r.length();
      int blockLength = ((buf[4] & 0xFF) << 8) + (buf[5] & 0xFF);
      long blockStart = 4 + blockLength;
      while(blockStart < length) {
        r.seek(blockStart);
        r.read(buf, 0, 4);
        if(buf[0] != -1) throw new IOException("JPEG marker search error");
        if(buf[1] == -64) {   // 0xFFC0 - Start Of Frame 0 (SOF0) marker
          r.skipBytes(1);
          h = read16(r);
          w = read16(r);
          break;
        }
        blockLength = ((buf[2] & 0xFF) << 8) + (buf[3] & 0xFF);
        blockStart += blockLength + 2;
      }
    } else throw new IOException("Only JPEG Image format supported");
    if(w <= 0 || h <= 0) throw new IOException("Picture size is invalid");
    return new int[] { w, h };
  }

  int read16(RandomAccessFile r) throws IOException {
    int b1 = r.read(), b2 = r.read();
    if ((b1 | b2) < 0) throw new EOFException();
    return (b1 << 8) + b2;
  }

  byte[] dict() {
    StringBuilder sb = new StringBuilder("/CIDInit /ProcSet findresource begin").append(CR);
    sb.append("12 dict begin").append(CR);
    sb.append("begincmap").append(CR);
    sb.append("/CIDSystemInfo <</Registry (Adobe) /Ordering (UCS) /Supplement 0>> def").append(CR);
    sb.append("/CMapName /Adobe-Identity-UCS def").append(CR);
    sb.append("/CMapType 2 def").append(CR);
    sb.append("1 begincodespacerange").append(CR);
    sb.append("<0000><FFFF>").append(CR);
    sb.append("endcodespacerange").append(CR);
    Integer[] arr = dChr.keySet().toArray(new Integer[0]);
    Arrays.sort(arr, (t1, t2) -> dChr.get(t1).compareTo(dChr.get(t2)));
    sb.append("1 beginbfrange").append(CR).append(String.format("<0000> <%04X> [", arr.length - 1));
    for (int i = 0; i < arr.length; i++) {
      if (i > 0) sb.append(' ');
      sb.append(String.format("<%04X>", arr[i].intValue()));
    }
    sb.append(']').append(CR).append("endbfrange").append(CR);
    sb.append("endcmap").append(CR);
    sb.append("CMapName currentdict /CMap defineresource pop").append(CR);
    sb.append("end").append(CR).append("end");
    return sb.toString().getBytes(StandardCharsets.ISO_8859_1);
  }

  class Fnt {
    RandomAccessFile r;

    Fnt(RandomAccessFile r) throws IOException {
      this.r = r;
      r.seek(0);
      long sfntVersion = uint32();
      if (sfntVersion != 0x74727565 && sfntVersion != 0x00010000) throw new RuntimeException("Only True Type font format supported");
      int numTables = uint16();
      r.skipBytes(6);
      String[] tableTag = new String[numTables];
      long[] offset = new long[numTables];
      for (int i = 0; i < numTables; i++) {
        tableTag[i] = tag();
        r.skipBytes(4);
        offset[i] = uint32();   // offset32
        r.skipBytes(4);
      }
      boolean glyfTable = false;
      for (int i = 0; i < numTables; i++) {
        r.seek(offset[i]);
        switch (tableTag[i]) {
          case "glyf" : glyfTable = true; break;
          case "cmap" : cmap(); break;
          case "head" : head(); break;
          case "hhea" : hhea(); break;
          case "hmtx" : hmtx(); break;
          case "name" : name(); break;
        }
      }
      if (!glyfTable || advanceWidth == null || advanceWidth.length == 0 || map.size() == 0) throw new RuntimeException("No required data");
    }

    short xMin = 0, xMax = 0;
    int unitsPerEm = 0;

    void head() throws IOException {
      r.skipBytes(18);
      unitsPerEm = uint16();
      r.skipBytes(16);
      xMin = int16();
      r.skipBytes(2);
      xMax = int16();
    }

    short ascender = 0, descender = 0;
    int numberOfHMetrics = 0;

    void hhea() throws IOException {
      r.skipBytes(4);
      ascender = int16();   // FWORD
      descender = int16();  // FWORD
      r.skipBytes(26);
      numberOfHMetrics = uint16();
    }

    int[] advanceWidth;

    void hmtx() throws IOException {
      advanceWidth = new int[numberOfHMetrics];
      for (int i = 0; i < numberOfHMetrics; i++) {
        advanceWidth[i] = uint16();
        r.skipBytes(2);
      }
    }

    String name = "na";

    void name() throws IOException {
      long startOfTable = r.getFilePointer();
      r.skipBytes(2);
      int count = uint16();
      int storageOffset = uint16(); // offset16
      int platformID, encodingID, nameID, length, stringOffset;
      for (int i = 0; i < count; i++) {
        platformID = uint16();
        encodingID = uint16();
        r.skipBytes(2);
        nameID = uint16();
        length = uint16();
        stringOffset = uint16();  // offset16
        if (nameID == 4) {  // fullName
          r.seek(startOfTable + storageOffset + stringOffset);
          byte[] buf = readBytes(length);
          if (platformID == 0 || (platformID == 3 && (encodingID == 1 || encodingID == 10))) {  // Unicode
            name = new String(buf, StandardCharsets.UTF_16);
          } else {
            name = new String(buf, StandardCharsets.ISO_8859_1);
          }
        }
      }
    }

    Map<Integer, Integer> map = new HashMap<>();

    void cmap() throws IOException {
      long startOfTable = r.getFilePointer();
      r.skipBytes(2);
      int numTables = uint16();
      long subtableOffset = 0;
      boolean notbl = true;
      for (int platformID, encodingID, i = 0; i < numTables; i++) {
          platformID = uint16();
          encodingID = uint16();
          subtableOffset = uint32();  // offset32
          if ((platformID == 3 && (encodingID == 0 || encodingID == 1 || encodingID == 10)) ||                                       // Windows
              (platformID == 0 && (encodingID == 0 || encodingID == 1 || encodingID == 2 || encodingID == 3 || encodingID == 4))) {  // Unicode
            notbl = false;
            break;
          }
      }
      if (notbl) { throw new IOException("No valid cmap sub-tables found"); }
      r.seek(startOfTable + subtableOffset);
      int format = uint16();
      if (format == 4) {
        r.skipBytes(4);
        int segCountX2 = uint16();
        r.skipBytes(6);
        int segCount = segCountX2 / 2;
        int[] endCode = new int[segCount];
        int[] startCode = new int[segCount];
        short[] idDelta = new short[segCount];
        int[] idRangeOffset = new int[segCount];
        for (int i = 0; i < segCount; i++) endCode[i] = uint16();
        r.skipBytes(2);
        for (int i = 0; i < segCount; i++) startCode[i] = uint16();
        for (int i = 0; i < segCount; i++) idDelta[i] = int16();
        long idRangeOffsetsStart = r.getFilePointer();
        for (int i = 0; i < segCount; i++) idRangeOffset[i] = uint16();
        for (int i = 0; i < segCount - 1; i++) {
          int glyphIndex = 0;
          long glyphIndexOffset;
          for (int c = startCode[i]; c <= endCode[i]; c++) {
            if (idRangeOffset[i] != 0) {
              glyphIndexOffset = idRangeOffsetsStart + idRangeOffset[i] + (i + c - startCode[i]) * 2;
              r.seek(glyphIndexOffset);
              glyphIndex = uint16();
              if (glyphIndex != 0) {
                glyphIndex = (glyphIndex + idDelta[i]) & 0xFFFF;
              }
            } else {
              glyphIndex = (c + idDelta[i]) & 0xFFFF;
            }
            map.put(Integer.valueOf(c), Integer.valueOf(glyphIndex));
          }
        }
      } else {
        throw new IOException("Only format 4 cmap table supported, found format " + format);
      }
    }

    int uint16() throws IOException {
      int b1 = r.read(), b2 = r.read();
      if ((b1 | b2) < 0) throw new EOFException();
      return (b1 << 8) + b2;
    }

    short int16() throws IOException { return (short) uint16(); }

    long uint32() throws IOException {
      long b1 = r.read(), b2 = r.read(), b3 = r.read(), b4 = r.read();
      if ((b1 | b2 | b3 | b4) < 0) throw new EOFException();
      return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;
    }

    int int32() throws IOException { return (int) uint32(); }

    byte[] readBytes(int len) throws IOException {
      byte[] buf = new byte[len];
      if (r.read(buf) < buf.length) throw new EOFException();
      return buf;
    }

    String tag() throws IOException {
      return new String(readBytes(4), StandardCharsets.ISO_8859_1);
    }

    int uni2gid(int uni) {
      Integer gid = map.get(Integer.valueOf(uni));
      return (gid == null ? 0 : gid.intValue());
    }

    int uni2width(int uni) {
      return advanceWidth[uni2gid(uni)];
    }
  }  // End class Fnt
}  // End class SmPDF

Download ZIP

Back