gdx-studio
git clone https://git.pyrossh.dev/gdx-studio
An IDE for creating Games using libgdx and Java supported on all platforms Android, iOS, Desktop
src_libs/com/badlogic/gdx/tools/hiero/Kerning.java
| a62d533 | 1 | /******************************************************************************* |
| a62d533 | 2 | * Copyright 2011 See AUTHORS file. |
| a62d533 | 3 | * |
| a62d533 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| a62d533 | 5 | * you may not use this file except in compliance with the License. |
| a62d533 | 6 | * You may obtain a copy of the License at |
| a62d533 | 7 | * |
| a62d533 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| a62d533 | 9 | * |
| a62d533 | 10 | * Unless required by applicable law or agreed to in writing, software |
| a62d533 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| a62d533 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| a62d533 | 13 | * See the License for the specific language governing permissions and |
| a62d533 | 14 | * limitations under the License. |
| a62d533 | 15 | ******************************************************************************/ |
| a62d533 | 16 | |
| a62d533 | 17 | package com.badlogic.gdx.tools.hiero; |
| a62d533 | 18 | |
| a62d533 | 19 | import java.awt.font.GlyphVector; |
| a62d533 | 20 | import java.io.EOFException; |
| a62d533 | 21 | import java.io.IOException; |
| a62d533 | 22 | import java.io.InputStream; |
| a62d533 | 23 | import java.util.ArrayList; |
| a62d533 | 24 | import java.util.Collections; |
| a62d533 | 25 | import java.util.HashMap; |
| a62d533 | 26 | import java.util.Iterator; |
| a62d533 | 27 | import java.util.List; |
| a62d533 | 28 | import java.util.ListIterator; |
| a62d533 | 29 | import java.util.Map; |
| a62d533 | 30 | import java.util.Map.Entry; |
| a62d533 | 31 | |
| a62d533 | 32 | /** Reads a TTF font file and provides access to kerning information. |
| a62d533 | 33 | * |
| a62d533 | 34 | * Thanks to the Apache FOP project for their inspiring work! |
| a62d533 | 35 | * |
| a62d533 | 36 | * @author Nathan Sweet */ |
| a62d533 | 37 | class Kerning {
|
| a62d533 | 38 | private Map values = Collections.EMPTY_MAP; |
| a62d533 | 39 | private int size = -1; |
| a62d533 | 40 | private int kerningPairCount = -1; |
| a62d533 | 41 | private float scale; |
| a62d533 | 42 | private long bytePosition; |
| a62d533 | 43 | private long headOffset = -1; |
| a62d533 | 44 | private long kernOffset = -1; |
| a62d533 | 45 | |
| a62d533 | 46 | /** @param input The data for the TTF font. |
| a62d533 | 47 | * @param size The font size to use to determine kerning pixel offsets. |
| a62d533 | 48 | * @throws IOException If the font could not be read. */ |
| a62d533 | 49 | public void load (InputStream input, int size) throws IOException {
|
| a62d533 | 50 | this.size = size; |
| a62d533 | 51 | if (input == null) throw new IllegalArgumentException("input cannot be null.");
|
| a62d533 | 52 | readTableDirectory(input); |
| a62d533 | 53 | if (headOffset == -1) throw new IOException("HEAD table not found.");
|
| a62d533 | 54 | if (kernOffset == -1) {
|
| a62d533 | 55 | values = Collections.EMPTY_MAP; |
| a62d533 | 56 | return; |
| a62d533 | 57 | } |
| a62d533 | 58 | values = new HashMap(256); |
| a62d533 | 59 | if (headOffset < kernOffset) {
|
| a62d533 | 60 | readHEAD(input); |
| a62d533 | 61 | readKERN(input); |
| a62d533 | 62 | } else {
|
| a62d533 | 63 | readKERN(input); |
| a62d533 | 64 | readHEAD(input); |
| a62d533 | 65 | } |
| a62d533 | 66 | input.close(); |
| a62d533 | 67 | |
| a62d533 | 68 | for (Iterator entryIter = values.entrySet().iterator(); entryIter.hasNext();) {
|
| a62d533 | 69 | Entry entry = (Entry)entryIter.next(); |
| a62d533 | 70 | // Scale the offset values using the font size. |
| a62d533 | 71 | List valueList = (List)entry.getValue(); |
| a62d533 | 72 | for (ListIterator valueIter = valueList.listIterator(); valueIter.hasNext();) {
|
| a62d533 | 73 | int value = ((Integer)valueIter.next()).intValue(); |
| a62d533 | 74 | int glyphCode = value & 0xffff; |
| a62d533 | 75 | int offset = value >> 16; |
| a62d533 | 76 | offset = Math.round(offset * scale); |
| a62d533 | 77 | if (offset == 0) |
| a62d533 | 78 | valueIter.remove(); |
| a62d533 | 79 | else |
| a62d533 | 80 | valueIter.set(new Integer((offset << 16) | glyphCode)); |
| a62d533 | 81 | } |
| a62d533 | 82 | if (valueList.isEmpty()) {
|
| a62d533 | 83 | entryIter.remove(); |
| a62d533 | 84 | } else {
|
| a62d533 | 85 | // Replace ArrayList with int[]. |
| a62d533 | 86 | int[] valueArray = new int[valueList.size()]; |
| a62d533 | 87 | int i = 0; |
| a62d533 | 88 | for (Iterator valueIter = valueList.iterator(); valueIter.hasNext(); i++) |
| a62d533 | 89 | valueArray[i] = ((Integer)valueIter.next()).intValue(); |
| a62d533 | 90 | entry.setValue(valueArray); |
| a62d533 | 91 | kerningPairCount += valueArray.length; |
| a62d533 | 92 | } |
| a62d533 | 93 | } |
| a62d533 | 94 | } |
| a62d533 | 95 | |
| a62d533 | 96 | /** Returns the encoded kerning value for the specified glyph. The glyph code for a Unicode codepoint can be retrieved with |
| a62d533 | 97 | * {@link GlyphVector#getGlyphCode(int)}. */
|
| a62d533 | 98 | public int[] getValues (int firstGlyphCode) {
|
| a62d533 | 99 | return (int[])values.get(new Integer(firstGlyphCode)); |
| a62d533 | 100 | } |
| a62d533 | 101 | |
| a62d533 | 102 | public int getKerning (int[] values, int otherGlyphCode) {
|
| a62d533 | 103 | int low = 0; |
| a62d533 | 104 | int high = values.length - 1; |
| a62d533 | 105 | while (low <= high) {
|
| a62d533 | 106 | int midIndex = (low + high) >>> 1; |
| a62d533 | 107 | int value = values[midIndex]; |
| a62d533 | 108 | int foundGlyphCode = value & 0xffff; |
| a62d533 | 109 | if (foundGlyphCode < otherGlyphCode) |
| a62d533 | 110 | low = midIndex + 1; |
| a62d533 | 111 | else if (foundGlyphCode > otherGlyphCode) |
| a62d533 | 112 | high = midIndex - 1; |
| a62d533 | 113 | else |
| a62d533 | 114 | return value >> 16; |
| a62d533 | 115 | } |
| a62d533 | 116 | return 0; |
| a62d533 | 117 | } |
| a62d533 | 118 | |
| a62d533 | 119 | public int getCount () {
|
| a62d533 | 120 | return kerningPairCount; |
| a62d533 | 121 | } |
| a62d533 | 122 | |
| a62d533 | 123 | private void readTableDirectory (InputStream input) throws IOException {
|
| a62d533 | 124 | skip(input, 4); |
| a62d533 | 125 | int tableCount = readUnsignedShort(input); |
| a62d533 | 126 | skip(input, 6); |
| a62d533 | 127 | |
| a62d533 | 128 | byte[] tagBytes = new byte[4]; |
| a62d533 | 129 | for (int i = 0; i < tableCount; i++) {
|
| a62d533 | 130 | tagBytes[0] = readByte(input); |
| a62d533 | 131 | tagBytes[1] = readByte(input); |
| a62d533 | 132 | tagBytes[2] = readByte(input); |
| a62d533 | 133 | tagBytes[3] = readByte(input); |
| a62d533 | 134 | skip(input, 4); |
| a62d533 | 135 | long offset = readUnsignedLong(input); |
| a62d533 | 136 | skip(input, 4); |
| a62d533 | 137 | |
| a62d533 | 138 | String tag = new String(tagBytes, "ISO-8859-1"); |
| a62d533 | 139 | if (tag.equals("head")) {
|
| a62d533 | 140 | headOffset = offset; |
| a62d533 | 141 | if (kernOffset != -1) break; |
| a62d533 | 142 | } else if (tag.equals("kern")) {
|
| a62d533 | 143 | kernOffset = offset; |
| a62d533 | 144 | if (headOffset != -1) break; |
| a62d533 | 145 | } |
| a62d533 | 146 | } |
| a62d533 | 147 | } |
| a62d533 | 148 | |
| a62d533 | 149 | private void readHEAD (InputStream input) throws IOException {
|
| a62d533 | 150 | seek(input, headOffset + 2 * 4 + 2 * 4 + 2); |
| a62d533 | 151 | int unitsPerEm = readUnsignedShort(input); |
| a62d533 | 152 | scale = (float)size / unitsPerEm; |
| a62d533 | 153 | } |
| a62d533 | 154 | |
| a62d533 | 155 | private void readKERN (InputStream input) throws IOException {
|
| a62d533 | 156 | seek(input, kernOffset + 2); |
| a62d533 | 157 | for (int subTableCount = readUnsignedShort(input); subTableCount > 0; subTableCount--) {
|
| a62d533 | 158 | skip(input, 2 * 2); |
| a62d533 | 159 | int tupleIndex = readUnsignedShort(input); |
| a62d533 | 160 | if (!((tupleIndex & 1) != 0) || (tupleIndex & 2) != 0 || (tupleIndex & 4) != 0) return; |
| a62d533 | 161 | if (tupleIndex >> 8 != 0) continue; |
| a62d533 | 162 | |
| a62d533 | 163 | int kerningCount = readUnsignedShort(input); |
| a62d533 | 164 | skip(input, 3 * 2); |
| a62d533 | 165 | while (kerningCount-- > 0) {
|
| a62d533 | 166 | int firstGlyphCode = readUnsignedShort(input); |
| a62d533 | 167 | int secondGlyphCode = readUnsignedShort(input); |
| a62d533 | 168 | int offset = readShort(input); |
| a62d533 | 169 | int value = (offset << 16) | secondGlyphCode; |
| a62d533 | 170 | |
| a62d533 | 171 | List firstGlyphValues = (List)values.get(new Integer(firstGlyphCode)); |
| a62d533 | 172 | if (firstGlyphValues == null) {
|
| a62d533 | 173 | firstGlyphValues = new ArrayList(256); |
| a62d533 | 174 | values.put(new Integer(firstGlyphCode), firstGlyphValues); |
| a62d533 | 175 | } |
| a62d533 | 176 | firstGlyphValues.add(new Integer(value)); |
| a62d533 | 177 | } |
| a62d533 | 178 | } |
| a62d533 | 179 | } |
| a62d533 | 180 | |
| a62d533 | 181 | private int readUnsignedByte (InputStream input) throws IOException {
|
| a62d533 | 182 | bytePosition++; |
| a62d533 | 183 | int b = input.read(); |
| a62d533 | 184 | if (b == -1) throw new EOFException("Unexpected end of file.");
|
| a62d533 | 185 | return b; |
| a62d533 | 186 | } |
| a62d533 | 187 | |
| a62d533 | 188 | private byte readByte (InputStream input) throws IOException {
|
| a62d533 | 189 | return (byte)readUnsignedByte(input); |
| a62d533 | 190 | } |
| a62d533 | 191 | |
| a62d533 | 192 | private int readUnsignedShort (InputStream input) throws IOException {
|
| a62d533 | 193 | return (readUnsignedByte(input) << 8) + readUnsignedByte(input); |
| a62d533 | 194 | } |
| a62d533 | 195 | |
| a62d533 | 196 | private short readShort (InputStream input) throws IOException {
|
| a62d533 | 197 | return (short)readUnsignedShort(input); |
| a62d533 | 198 | } |
| a62d533 | 199 | |
| a62d533 | 200 | private long readUnsignedLong (InputStream input) throws IOException {
|
| a62d533 | 201 | long value = readUnsignedByte(input); |
| a62d533 | 202 | value = (value << 8) + readUnsignedByte(input); |
| a62d533 | 203 | value = (value << 8) + readUnsignedByte(input); |
| a62d533 | 204 | value = (value << 8) + readUnsignedByte(input); |
| a62d533 | 205 | return value; |
| a62d533 | 206 | } |
| a62d533 | 207 | |
| a62d533 | 208 | private void skip (InputStream input, long skip) throws IOException {
|
| a62d533 | 209 | while (skip > 0) {
|
| a62d533 | 210 | long skipped = input.skip(skip); |
| a62d533 | 211 | if (skipped <= 0) break; |
| a62d533 | 212 | bytePosition += skipped; |
| a62d533 | 213 | skip -= skipped; |
| a62d533 | 214 | } |
| a62d533 | 215 | } |
| a62d533 | 216 | |
| a62d533 | 217 | private void seek (InputStream input, long position) throws IOException {
|
| a62d533 | 218 | skip(input, position - bytePosition); |
| a62d533 | 219 | } |
| a62d533 | 220 | } |