src/de/matthiasmann/javafreetype/FT2Helper.java
author Matthias Mann
Tue Jan 03 23:45:15 2012 +0100 (4 months ago)
changeset 12 f357a4c28520
parent 11 896cc713c9b0
permissions -rw-r--r--
fixed handling of FT_Size objects to prevent crash
     1 /*
     2  * Copyright (c) 2008-2012, Matthias Mann
     3  *
     4  * All rights reserved.
     5  *
     6  * Redistribution and use in source and binary forms, with or without
     7  * modification, are permitted provided that the following conditions are met:
     8  *
     9  *     * Redistributions of source code must retain the above copyright notice,
    10  *       this list of conditions and the following disclaimer.
    11  *     * Redistributions in binary form must reproduce the above copyright
    12  *       notice, this list of conditions and the following disclaimer in the
    13  *       documentation and/or other materials provided with the distribution.
    14  *     * Neither the name of Matthias Mann nor the names of its contributors may
    15  *       be used to endorse or promote products derived from this software
    16  *       without specific prior written permission.
    17  *
    18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    22  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    25  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    26  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    27  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    28  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    29  */
    30 package de.matthiasmann.javafreetype;
    31 
    32 import java.awt.image.ComponentSampleModel;
    33 import java.awt.image.SinglePixelPackedSampleModel;
    34 import java.awt.Color;
    35 import java.io.IOException;
    36 import java.util.ArrayList;
    37 import java.util.logging.Level;
    38 import java.util.logging.Logger;
    39 import com.sun.jna.Native;
    40 import com.sun.jna.NativeLong;
    41 import com.sun.jna.Platform;
    42 import com.sun.jna.Pointer;
    43 import com.sun.jna.ptr.IntByReference;
    44 import com.sun.jna.ptr.PointerByReference;
    45 import java.awt.image.BufferedImage;
    46 import java.awt.image.DataBufferByte;
    47 import java.awt.image.DataBufferInt;
    48 import java.io.InputStream;
    49 import java.nio.ByteBuffer;
    50 
    51 import static de.matthiasmann.javafreetype.FT2Library.*;
    52 
    53 /**
    54  *
    55  * @author Matthias Mann
    56  */
    57 class FT2Helper {
    58 
    59     private static Boolean isAvailable;
    60     static FT2Library INSTANCE;
    61     static String nativeLibName;
    62 
    63     static synchronized boolean isAvailable() {
    64         if(isAvailable == null) {
    65             try {
    66                 String libName;
    67                 if(nativeLibName != null) {
    68                     libName = nativeLibName;
    69                 } else if(Platform.isWindows()) {
    70                     libName = "freetype6";
    71                 } else {
    72                     libName = "freetype";
    73                 }
    74                 INSTANCE = (FT2Library)Native.loadLibrary(libName, FT2Library.class);
    75                 Pointer library = FT_Init_FreeType();
    76                 try {
    77                     isAvailable = checkLibrary(library);
    78                 } finally {
    79                     INSTANCE.FT_Done_FreeType(library);
    80                 }
    81             } catch (Throwable ex) {
    82                 isAvailable = Boolean.FALSE;
    83                 getLogger().log(Level.SEVERE, "Can't load FreeType2 library", ex);
    84             }
    85         }
    86 
    87         return isAvailable;
    88     }
    89 
    90     static void checkAvailable() {
    91         if(!isAvailable()) {
    92             throw new UnsupportedOperationException("FreeType2 library not available");
    93         }
    94     }
    95 
    96     static void checkReturnCode(int error) throws FreeTypeException {
    97         if(error != 0) {
    98             throw new FreeTypeException(error);
    99         }
   100     }
   101 
   102     static String trueTypeEngineToString(int engine) {
   103         switch(engine) {
   104             case FT_TRUETYPE_ENGINE_TYPE_NONE:       return "NONE";
   105             case FT_TRUETYPE_ENGINE_TYPE_UNPATENTED: return "UNPATENTED";
   106             case FT_TRUETYPE_ENGINE_TYPE_PATENTED:   return "PATENTED";
   107             default:                                 return "unknown: " + engine;
   108         }
   109     }
   110 
   111     static boolean checkLibrary(Pointer library) {
   112         IntByReference major = new IntByReference();
   113         IntByReference minor = new IntByReference();
   114         IntByReference patch = new IntByReference();
   115         INSTANCE.FT_Library_Version(library, major, minor, patch);
   116 
   117         int engine = -1;
   118         if(major.getValue() > 2 || (major.getValue() == 2 && minor.getValue() >= 2)) {
   119             // FT_Get_TrueType_Engine_Type requires FreeType 2.2.x
   120             engine = INSTANCE.FT_Get_TrueType_Engine_Type(library);
   121         }
   122 
   123         getLogger().log(Level.INFO, "FreeType2 version: {0}.{1}.{2} TrueType engine: {3}",
   124                 new Object[]{ major.getValue(), minor.getValue(), patch.getValue(), trueTypeEngineToString(engine) });
   125 
   126         final int MIN_MAJOR = 2;
   127         final int MIN_MINOR = 3;
   128 
   129         if(major.getValue() > MIN_MAJOR) {
   130             return true;
   131         } else if(major.getValue() == MIN_MAJOR && minor.getValue() >= MIN_MINOR) {
   132             return true;
   133         } else {
   134             getLogger().log(Level.WARNING, "FreeType2 library too old");
   135             return false;
   136         }
   137     }
   138 
   139     static Pointer FT_Init_FreeType() throws FreeTypeException {
   140         PointerByReference pp = new PointerByReference();
   141         checkReturnCode(INSTANCE.FT_Init_FreeType(pp));
   142         return pp.getValue();
   143     }
   144 
   145     static FT_Face FT_New_Memory_Face(Pointer library, ByteBuffer buffer, long face_index) throws FreeTypeException {
   146         PointerByReference pp = new PointerByReference();
   147         checkReturnCode(INSTANCE.FT_New_Memory_Face(library, buffer,
   148                 new NativeLong(buffer.remaining()), new NativeLong(face_index), pp));
   149         return new FT_Face(pp.getValue());
   150     }
   151     
   152     static Pointer FT_New_Size(Pointer face) throws FreeTypeException {
   153         PointerByReference pp = new PointerByReference();
   154         checkReturnCode(INSTANCE.FT_New_Size(face, pp));
   155         return pp.getValue();
   156     }
   157 
   158     static int FT_IMAGE_TAG(int x1, int x2, int x3, int x4) {
   159         return (x1 << 24) | (x2 << 16) | (x3 << 8) | x4;
   160     }
   161 
   162     static int to26_6(float value) {
   163         return Math.round(Math.scalb(value, 6));
   164     }
   165 
   166     static int round26_6(NativeLong value) {
   167         return round26_6(value.longValue());
   168     }
   169     
   170     static int round26_6(long value) {
   171         if(value < 0) {
   172             return (int)((value - 32) >> 6);
   173         } else {
   174             return (int)((value + 32) >> 6);
   175         }
   176     }
   177 
   178     static long FT_FixMul(long a, long b) {
   179         long tmp = a * b;
   180         if(tmp < 0) {
   181             tmp -= 0x8000;
   182         } else {
   183             tmp += 0x8000;
   184         }
   185         return tmp >> 16;
   186     }
   187     
   188     static boolean copyGlyphToBufferedImageGray(FT_Bitmap bitmap, BufferedImage img, int x, int y) {
   189         if(x + bitmap.width > img.getWidth()) {
   190             return false;
   191         }
   192         if(y + bitmap.rows > img.getHeight()) {
   193             return false;
   194         }
   195 
   196         final DataBufferByte dataBuffer = (DataBufferByte)img.getRaster().getDataBuffer();
   197         final byte[] data = dataBuffer.getData();
   198         final int stride = ((ComponentSampleModel)img.getSampleModel()).getScanlineStride();
   199 
   200         ByteBuffer bb = bitmap.buffer.getByteBuffer(0, Math.abs(bitmap.pitch) * bitmap.rows);
   201         int bbOff = (bitmap.pitch < 0) ? (-bitmap.pitch * (bitmap.rows-1)) : 0;
   202         int dataOff = dataBuffer.getOffset() + y * stride + x;
   203 
   204         for(int r=0 ; r<bitmap.rows ; r++,bbOff+=bitmap.pitch,dataOff+=stride) {
   205             for(int c=0 ; c<bitmap.width ; c++) {
   206                 data[dataOff + c] = bb.get(bbOff + c);
   207             }
   208         }
   209 
   210         return true;
   211     }
   212 
   213     static boolean copyGlyphToBufferedImageIntARGB(FT_Bitmap bitmap, BufferedImage img, int x, int y, Color color) {
   214         if(x + bitmap.width > img.getWidth()) {
   215             return false;
   216         }
   217         if(y + bitmap.rows > img.getHeight()) {
   218             return false;
   219         }
   220 
   221         final DataBufferInt dataBuffer = (DataBufferInt)img.getRaster().getDataBuffer();
   222         final int[] data = dataBuffer.getData();
   223         final int stride = ((SinglePixelPackedSampleModel)img.getSampleModel()).getScanlineStride();
   224 
   225         ByteBuffer bb = bitmap.buffer.getByteBuffer(0, Math.abs(bitmap.pitch) * bitmap.rows);
   226         int bbOff = (bitmap.pitch < 0) ? (-bitmap.pitch * (bitmap.rows-1)) : 0;
   227         int dataOff = dataBuffer.getOffset() + y * stride + x;
   228 
   229         int colorValue = (color == null ? Color.WHITE : color).getRGB() & 0xFFFFFF;
   230 
   231         switch(bitmap.pixel_mode) {
   232             case FT_PIXEL_MODE_GRAY:
   233                 for(int r=0 ; r<bitmap.rows ; r++,bbOff+=bitmap.pitch,dataOff+=stride) {
   234                     for(int c=0 ; c<bitmap.width ; c++) {
   235                         data[dataOff + c] = colorValue | (bb.get(bbOff + c) << 24);
   236                     }
   237                 }
   238                 return true;
   239                 
   240             case FT_PIXEL_MODE_MONO:
   241                 for(int r=0 ; r<bitmap.rows ; r++,bbOff+=bitmap.pitch,dataOff+=stride) {
   242                     for(int c=0 ; c<bitmap.width ;) {
   243                         int value = bb.get(bbOff + c/8);
   244                         int cnt = Math.min(bitmap.width - c, 8);
   245                         while(cnt-- > 0) {
   246                             data[dataOff + c++] = colorValue | ((value & 128) << (24-7))*0xFF;
   247                             value <<= 1;
   248                         }
   249                     }
   250                 }
   251                 return true;
   252                 
   253             default:
   254                 return false;
   255         }
   256     }
   257 
   258     static boolean copyGlyphToByteBuffer(FT_Bitmap bitmap, ByteBuffer dst, int stride) {
   259         ByteBuffer bb = bitmap.buffer.getByteBuffer(0, Math.abs(bitmap.pitch) * bitmap.rows);
   260         int bbOff = (bitmap.pitch < 0) ? (-bitmap.pitch * (bitmap.rows-1)) : 0;
   261         int dstOff = dst.position();
   262 
   263         for(int r=0 ; r<bitmap.rows ; r++,bbOff+=bitmap.pitch,dstOff+=stride) {
   264             bb.clear().position(bbOff).limit(bbOff + bitmap.width);
   265             dst.position(dstOff);
   266             dst.put(bb);
   267         }
   268 
   269         return true;
   270     }
   271     
   272     static boolean copyGlyphToByteArray(FT_Bitmap bitmap, byte[] dst, int dstOff, int stride) {
   273         ByteBuffer bb = bitmap.buffer.getByteBuffer(0, Math.abs(bitmap.pitch) * bitmap.rows);
   274         int bbOff = (bitmap.pitch < 0) ? (-bitmap.pitch * (bitmap.rows-1)) : 0;
   275         bb.clear();
   276 
   277         switch(bitmap.pixel_mode) {
   278             case FT_PIXEL_MODE_GRAY:
   279                 for(int r=0 ; r<bitmap.rows ; r++,bbOff+=bitmap.pitch,dstOff+=stride) {
   280                     bb.position(bbOff);
   281                     bb.get(dst, dstOff, bitmap.width);
   282                 }
   283                 return true;
   284                 
   285             case FT_PIXEL_MODE_MONO:
   286                 for(int r=0 ; r<bitmap.rows ; r++,bbOff+=bitmap.pitch,dstOff+=stride) {
   287                     bb.position(bbOff);
   288                     for(int c=0 ; c<bitmap.width ;) {
   289                         int value = bb.get(bbOff + c/8);
   290                         int cnt = Math.min(bitmap.width - c, 8);
   291                         while(cnt-- > 0) {
   292                             dst[dstOff + c++] = (byte)(((value & 128) >> 7)*0xFF);
   293                             value <<= 1;
   294                         }
   295                     }
   296                 }
   297                 return true;
   298                 
   299             default:
   300                 return false;
   301         }
   302     }
   303 
   304     static boolean copyGlyphToByteBuffer(FT_Bitmap bitmap, ByteBuffer dst, int stride, short[] colors) {
   305         ByteBuffer bb = bitmap.buffer.getByteBuffer(0, Math.abs(bitmap.pitch) * bitmap.rows);
   306         int bbOff = (bitmap.pitch < 0) ? (-bitmap.pitch * (bitmap.rows-1)) : 0;
   307         int dstRowOff = dst.position();
   308         int width = bitmap.width;
   309 
   310         for(int r=0 ; r<bitmap.rows ; r++,bbOff+=bitmap.pitch,dstRowOff+=stride) {
   311             int dstOff = dstRowOff;
   312             for(int c=0 ; c<width ; c++) {
   313                 int value = bb.get(bbOff + c) & 255;
   314                 if(value >= 0x80) {
   315                     value++;
   316                 }
   317                 for(int i=0 ; i<colors.length ; i+=2,dstOff++) {
   318                     dst.put(dstOff, (byte)(colors[i] + ((colors[i+1] * value) >> 8)));
   319                 }
   320             }
   321             dst.position(dstOff);
   322         }
   323         
   324         return true;
   325     }
   326 
   327     static ByteBuffer inputStreamToByteBuffer(InputStream is) throws IOException {
   328         final int PAGE_SIZE = 4096;
   329         final ArrayList<byte[]> pages = new ArrayList<byte[]>();
   330         for(;;) {
   331             byte[] page = new byte[PAGE_SIZE];
   332             int pagePos = 0;
   333             int read;
   334             do {
   335                 read = is.read(page, pagePos, PAGE_SIZE-pagePos);
   336                 if(read <= 0) {
   337                     break;
   338                 }
   339                 pagePos += read;
   340             } while(pagePos < PAGE_SIZE);
   341 
   342             if(pagePos == PAGE_SIZE) {
   343                 pages.add(page);
   344             } else {
   345                 ByteBuffer fontBuffer = ByteBuffer.allocateDirect(pages.size() * PAGE_SIZE + pagePos);
   346                 for(int i=0,n=pages.size() ; i<n ; i++) {
   347                     fontBuffer.put(pages.get(i));
   348                 }
   349                 pages.clear();
   350                 fontBuffer.put(page, 0, pagePos).flip();
   351                 return fontBuffer;
   352             }
   353         }
   354     }
   355 
   356     static Logger getLogger() {
   357         return Logger.getLogger(FreeTypeFont.class.getName());
   358     }
   359 }