2 * Copyright (c) 2008-2012, Matthias Mann
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
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.
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.
30 package de.matthiasmann.javafreetype;
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;
51 import static de.matthiasmann.javafreetype.FT2Library.*;
55 * @author Matthias Mann
59 private static Boolean isAvailable;
60 static FT2Library INSTANCE;
61 static String nativeLibName;
63 static synchronized boolean isAvailable() {
64 if(isAvailable == null) {
67 if(nativeLibName != null) {
68 libName = nativeLibName;
69 } else if(Platform.isWindows()) {
70 libName = "freetype6";
74 INSTANCE = (FT2Library)Native.loadLibrary(libName, FT2Library.class);
75 Pointer library = FT_Init_FreeType();
77 isAvailable = checkLibrary(library);
79 INSTANCE.FT_Done_FreeType(library);
81 } catch (Throwable ex) {
82 isAvailable = Boolean.FALSE;
83 getLogger().log(Level.SEVERE, "Can't load FreeType2 library", ex);
90 static void checkAvailable() {
92 throw new UnsupportedOperationException("FreeType2 library not available");
96 static void checkReturnCode(int error) throws FreeTypeException {
98 throw new FreeTypeException(error);
102 static String trueTypeEngineToString(int 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;
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);
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);
123 getLogger().log(Level.INFO, "FreeType2 version: {0}.{1}.{2} TrueType engine: {3}",
124 new Object[]{ major.getValue(), minor.getValue(), patch.getValue(), trueTypeEngineToString(engine) });
126 final int MIN_MAJOR = 2;
127 final int MIN_MINOR = 3;
129 if(major.getValue() > MIN_MAJOR) {
131 } else if(major.getValue() == MIN_MAJOR && minor.getValue() >= MIN_MINOR) {
134 getLogger().log(Level.WARNING, "FreeType2 library too old");
139 static Pointer FT_Init_FreeType() throws FreeTypeException {
140 PointerByReference pp = new PointerByReference();
141 checkReturnCode(INSTANCE.FT_Init_FreeType(pp));
142 return pp.getValue();
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());
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();
158 static int FT_IMAGE_TAG(int x1, int x2, int x3, int x4) {
159 return (x1 << 24) | (x2 << 16) | (x3 << 8) | x4;
162 static int to26_6(float value) {
163 return Math.round(Math.scalb(value, 6));
166 static int round26_6(NativeLong value) {
167 return round26_6(value.longValue());
170 static int round26_6(long value) {
172 return (int)((value - 32) >> 6);
174 return (int)((value + 32) >> 6);
178 static long FT_FixMul(long a, long b) {
188 static boolean copyGlyphToBufferedImageGray(FT_Bitmap bitmap, BufferedImage img, int x, int y) {
189 if(x + bitmap.width > img.getWidth()) {
192 if(y + bitmap.rows > img.getHeight()) {
196 final DataBufferByte dataBuffer = (DataBufferByte)img.getRaster().getDataBuffer();
197 final byte[] data = dataBuffer.getData();
198 final int stride = ((ComponentSampleModel)img.getSampleModel()).getScanlineStride();
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;
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);
213 static boolean copyGlyphToBufferedImageIntARGB(FT_Bitmap bitmap, BufferedImage img, int x, int y, Color color) {
214 if(x + bitmap.width > img.getWidth()) {
217 if(y + bitmap.rows > img.getHeight()) {
221 final DataBufferInt dataBuffer = (DataBufferInt)img.getRaster().getDataBuffer();
222 final int[] data = dataBuffer.getData();
223 final int stride = ((SinglePixelPackedSampleModel)img.getSampleModel()).getScanlineStride();
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;
229 int colorValue = (color == null ? Color.WHITE : color).getRGB() & 0xFFFFFF;
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);
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);
246 data[dataOff + c++] = colorValue | ((value & 128) << (24-7))*0xFF;
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();
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);
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;
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) {
281 bb.get(dst, dstOff, bitmap.width);
285 case FT_PIXEL_MODE_MONO:
286 for(int r=0 ; r<bitmap.rows ; r++,bbOff+=bitmap.pitch,dstOff+=stride) {
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);
292 dst[dstOff + c++] = (byte)(((value & 128) >> 7)*0xFF);
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;
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;
317 for(int i=0 ; i<colors.length ; i+=2,dstOff++) {
318 dst.put(dstOff, (byte)(colors[i] + ((colors[i+1] * value) >> 8)));
321 dst.position(dstOff);
327 static ByteBuffer inputStreamToByteBuffer(InputStream is) throws IOException {
328 final int PAGE_SIZE = 4096;
329 final ArrayList<byte[]> pages = new ArrayList<byte[]>();
331 byte[] page = new byte[PAGE_SIZE];
335 read = is.read(page, pagePos, PAGE_SIZE-pagePos);
340 } while(pagePos < PAGE_SIZE);
342 if(pagePos == PAGE_SIZE) {
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));
350 fontBuffer.put(page, 0, pagePos).flip();
356 static Logger getLogger() {
357 return Logger.getLogger(FreeTypeFont.class.getName());