diff options
Diffstat (limited to 'src/com/google/typography/font/compression/GlyfEncoder.java')
-rw-r--r-- | src/com/google/typography/font/compression/GlyfEncoder.java | 481 |
1 files changed, 0 insertions, 481 deletions
diff --git a/src/com/google/typography/font/compression/GlyfEncoder.java b/src/com/google/typography/font/compression/GlyfEncoder.java deleted file mode 100644 index 1144ee1..0000000 --- a/src/com/google/typography/font/compression/GlyfEncoder.java +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.sfntly.data.ReadableFontData; -import com.google.typography.font.sfntly.table.core.FontHeaderTable; -import com.google.typography.font.sfntly.table.truetype.CompositeGlyph; -import com.google.typography.font.sfntly.table.truetype.Glyph; -import com.google.typography.font.sfntly.table.truetype.GlyphTable; -import com.google.typography.font.sfntly.table.truetype.LocaTable; -import com.google.typography.font.sfntly.table.truetype.SimpleGlyph; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * Implementation of compression of CTF glyph data, as per sections 5.6-5.10 and 6 of the spec. - * This is a hacked-up version with a number of options, for experimenting. - * - * @author Raph Levien - */ -public class GlyfEncoder { - - private final ByteArrayOutputStream nContourStream; - private final ByteArrayOutputStream nPointsStream; - private final ByteArrayOutputStream flagBytesStream; - private final ByteArrayOutputStream compositeStream; - private final ByteArrayOutputStream bboxStream; - private final ByteArrayOutputStream glyfStream; - private final ByteArrayOutputStream pushStream; - private final ByteArrayOutputStream codeStream; - private final boolean sbbox; - private final boolean cbbox; - private final boolean code; - private final boolean triplet; - private final boolean doPush; - private final boolean doHop; - private final boolean push2byte; - private final boolean reslice; - - private int nGlyphs; - private byte[] bboxBitmap; - private FontHeaderTable.IndexToLocFormat indexFmt; - - public GlyfEncoder(String options) { - glyfStream = new ByteArrayOutputStream(); - pushStream = new ByteArrayOutputStream(); - codeStream = new ByteArrayOutputStream(); - nContourStream = new ByteArrayOutputStream(); - nPointsStream = new ByteArrayOutputStream(); - flagBytesStream = new ByteArrayOutputStream(); - compositeStream = new ByteArrayOutputStream(); - bboxStream = new ByteArrayOutputStream(); - boolean sbbox = false; - boolean cbbox = false; - boolean code = false; - boolean triplet = false; - boolean doPush = false; - boolean reslice = false; - boolean doHop = false; - boolean push2byte = false; - for (String option : options.split(",")) { - if (option.equals("sbbox")) { - sbbox = true; - } else if (option.equals("cbbox")) { - cbbox = true; - } else if (option.equals("code")) { - code = true; - } else if (option.equals("triplet")) { - triplet = true; - } else if (option.equals("push")) { - doPush = true; - } else if (option.equals("hop")) { - doHop = true; - } else if (option.equals("push2byte")) { - push2byte = true; - } else if (option.equals("reslice")) { - reslice = true; - } - } - this.sbbox = sbbox; - this.cbbox = cbbox; - this.code = code; - this.triplet = triplet; - this.doPush = doPush; - this.doHop = doHop; - this.push2byte = push2byte; - this.reslice = reslice; - } - - public void encode(Font sourceFont) { - FontHeaderTable head = sourceFont.getTable(Tag.head); - indexFmt = head.indexToLocFormat(); - LocaTable loca = sourceFont.getTable(Tag.loca); - nGlyphs = loca.numGlyphs(); - GlyphTable glyf = sourceFont.getTable(Tag.glyf); - bboxBitmap = new byte[((nGlyphs + 31) >> 5) << 2]; - - for (int glyphId = 0; glyphId < nGlyphs; glyphId++) { - int sourceOffset = loca.glyphOffset(glyphId); - int length = loca.glyphLength(glyphId); - Glyph glyph = glyf.glyph(sourceOffset, length); - writeGlyph(glyphId, glyph); - } - } - - private void writeGlyph(int glyphId, Glyph glyph) { - try { - if (glyph == null || glyph.dataLength() == 0) { - writeNContours(0); - } else if (glyph instanceof SimpleGlyph) { - writeSimpleGlyph(glyphId, (SimpleGlyph)glyph); - } else if (glyph instanceof CompositeGlyph) { - writeCompositeGlyph(glyphId, (CompositeGlyph)glyph); - } - } catch (IOException e) { - throw new RuntimeException("unexpected IOException writing glyph data", e); - } - } - - private void writeInstructions(Glyph glyph) throws IOException{ - if (doPush) { - splitPush(glyph); - } else { - int pushCount = 0; - int codeSize = glyph.instructionSize(); - if (!reslice) { - write255UShort(glyfStream, pushCount); - } - write255UShort(glyfStream, codeSize); - if (codeSize > 0) { - if (code) { - glyph.instructions().copyTo(codeStream); - } else { - glyph.instructions().copyTo(glyfStream); - } - } - } - } - - private void writeSimpleGlyph(int glyphId, SimpleGlyph glyph) throws IOException { - int numContours = glyph.numberOfContours(); - writeNContours(numContours); - if (sbbox) { - writeBbox(glyphId, glyph); - } - // TODO: check that bbox matches, write bbox if not - for (int i = 0; i < numContours; i++) { - if (reslice) { - write255UShort(nPointsStream, glyph.numberOfPoints(i)); - } else { - write255UShort(glyfStream, glyph.numberOfPoints(i) - (i == 0 ? 1 : 0)); - } - } - ByteArrayOutputStream os = new ByteArrayOutputStream(); - int lastX = 0; - int lastY = 0; - for (int i = 0; i < numContours; i++) { - int numPoints = glyph.numberOfPoints(i); - for (int j = 0; j < numPoints; j++) { - int x = glyph.xCoordinate(i, j); - int y = glyph.yCoordinate(i, j); - int dx = x - lastX; - int dy = y - lastY; - if (triplet) { - writeTriplet(os, glyph.onCurve(i, j), dx, dy); - } else { - writeVShort(os, dx * 2 + (glyph.onCurve(i, j) ? 1 : 0)); - writeVShort(os, dy); - } - lastX = x; - lastY = y; - } - } - os.writeTo(glyfStream); - if (numContours > 0) { - writeInstructions(glyph); - } - } - - private void writeCompositeGlyph(int glyphId, CompositeGlyph glyph) throws IOException { - boolean haveInstructions = false; - writeNContours(-1); - if (cbbox) { - writeBbox(glyphId, glyph); - } - ByteArrayOutputStream outStream = reslice ? compositeStream : glyfStream; - for (int i = 0; i < glyph.numGlyphs(); i++) { - int flags = glyph.flags(i); - writeUShort(outStream, flags); - haveInstructions = (flags & CompositeGlyph.FLAG_WE_HAVE_INSTRUCTIONS) != 0; - writeUShort(outStream, glyph.glyphIndex(i)); - if ((flags & CompositeGlyph.FLAG_ARG_1_AND_2_ARE_WORDS) == 0) { - outStream.write(glyph.argument1(i)); - outStream.write(glyph.argument2(i)); - } else { - writeUShort(outStream, glyph.argument1(i)); - writeUShort(outStream, glyph.argument2(i)); - } - if (glyph.transformationSize(i) != 0) { - try { - outStream.write(glyph.transformation(i)); - } catch (IOException e) { - } - } - } - if (haveInstructions) { - writeInstructions(glyph); - } - } - - private void writeNContours(int nContours) { - if (reslice) { - writeUShort(nContourStream, nContours); - } else { - writeUShort(nContours); - } - } - - private void writeBbox(int glyphId, Glyph glyph) { - if (reslice) { - bboxBitmap[glyphId >> 3] |= 0x80 >> (glyphId & 7); - } - ByteArrayOutputStream outStream = reslice ? bboxStream : glyfStream; - writeUShort(outStream, glyph.xMin()); - writeUShort(outStream, glyph.yMin()); - writeUShort(outStream, glyph.xMax()); - writeUShort(outStream, glyph.yMax()); - } - - private void writeUShort(ByteArrayOutputStream os, int value) { - os.write(value >> 8); - os.write(value & 255); - } - - private void writeUShort(int value) { - writeUShort(glyfStream, value); - } - - private void writeLong(OutputStream os, int value) throws IOException { - os.write((value >> 24) & 255); - os.write((value >> 16) & 255); - os.write((value >> 8) & 255); - os.write(value & 255); - } - - // As per 6.1.1 of spec - // visible for testing - static void write255UShort(ByteArrayOutputStream os, int value) { - if (value < 0) { - throw new IllegalArgumentException(); - } - if (value < 253) { - os.write((byte)value); - } else if (value < 506) { - os.write(255); - os.write((byte)(value - 253)); - } else if (value < 762) { - os.write(254); - os.write((byte)(value - 506)); - } else { - os.write(253); - os.write((byte)(value >> 8)); - os.write((byte)(value & 0xff)); - } - } - - // As per 6.1.1 of spec - // visible for testing - static void write255Short(OutputStream os, int value) throws IOException { - int absValue = Math.abs(value); - if (value < 0) { - // spec is unclear about whether words should be signed. This code is conservative, but we - // can test once the implementation is working. - os.write(250); - } - if (absValue < 250) { - os.write((byte)absValue); - } else if (absValue < 500) { - os.write(255); - os.write((byte)(absValue - 250)); - } else if (absValue < 756) { - os.write(254); - os.write((byte)(absValue - 500)); - } else { - os.write(253); - os.write((byte)(absValue >> 8)); - os.write((byte)(absValue & 0xff)); - } - } - - // A simple signed varint encoding - static void writeVShort(ByteArrayOutputStream os, int value) { - if (value >= 0x2000 || value < -0x2000) { - os.write((byte)(0x80 | ((value >> 14) & 0x7f))); - } - if (value >= 0x40 || value < -0x40) { - os.write((byte)(0x80 | ((value >> 7) & 0x7f))); - } - os.write((byte)(value & 0x7f)); - } - - // As in section 5.11 of the spec - // visible for testing - void writeTriplet(OutputStream os, boolean onCurve, int x, int y) throws IOException { - int absX = Math.abs(x); - int absY = Math.abs(y); - int onCurveBit = onCurve ? 0 : 128; - int xSignBit = (x < 0) ? 0 : 1; - int ySignBit = (y < 0) ? 0 : 1; - int xySignBits = xSignBit + 2 * ySignBit; - ByteArrayOutputStream flagStream = reslice ? flagBytesStream : glyfStream; - - if (x == 0 && absY < 1280) { - flagStream.write(onCurveBit + ((absY & 0xf00) >> 7) + ySignBit); - os.write(absY & 0xff); - } else if (y == 0 && absX < 1280) { - flagStream.write(onCurveBit + 10 + ((absX & 0xf00) >> 7) + xSignBit); - os.write(absX & 0xff); - } else if (absX < 65 && absY < 65) { - flagStream.write(onCurveBit + 20 + ((absX - 1) & 0x30) + (((absY - 1) & 0x30) >> 2) + - xySignBits); - os.write((((absX - 1) & 0xf) << 4) | ((absY - 1) & 0xf)); - } else if (absX < 769 && absY < 769) { - flagStream.write(onCurveBit + 84 + 12 * (((absX - 1) & 0x300) >> 8) + - (((absY - 1) & 0x300) >> 6) + xySignBits); - os.write((absX - 1) & 0xff); - os.write((absY - 1) & 0xff); - } else if (absX < 4096 && absY < 4096) { - flagStream.write(onCurveBit + 120 + xySignBits); - os.write(absX >> 4); - os.write(((absX & 0xf) << 4) | (absY >> 8)); - os.write(absY & 0xff); - } else { - flagStream.write(onCurveBit + 124 + xySignBits); - os.write(absX >> 8); - os.write(absX & 0xff); - os.write(absY >> 8); - os.write(absY & 0xff); - } - } - - /** - * Split the instructions into a push sequence and the remainder of the instructions. - * Writes both streams, and the counts to the glyfStream. - * - * @param glyph - */ - private void splitPush(Glyph glyph) throws IOException { - int instrSize = glyph.instructionSize(); - ReadableFontData data = glyph.instructions(); - int i = 0; - List<Integer> result = new ArrayList<Integer>(); - // All push sequences are at least two bytes, make sure there's enough room - while (i + 1 < instrSize) { - int ix = i; - int instr = data.readUByte(ix++); - int n = 0; - int size = 0; - if (instr == 0x40 || instr == 0x41) { - // NPUSHB, NPUSHW - n = data.readUByte(ix++); - size = (instr & 1) + 1; - } else if (instr >= 0xB0 && instr < 0xC0) { - // PUSHB, PUSHW - n = 1 + (instr & 7); - size = ((instr & 8) >> 3) + 1; - } else { - break; - } - if (i + size * n > instrSize) { - // This is a broken font, and a potential buffer overflow, but in the interest - // of preserving the original, we just put the broken instruction sequence in - // the stream. - break; - } - for (int j = 0; j < n; j++) { - if (size == 1) { - result.add(data.readUByte(ix)); - } else { - result.add(data.readShort(ix)); - } - ix += size; - } - i = ix; - } - int pushCount = result.size(); - int codeSize = instrSize - i; - write255UShort(glyfStream, pushCount); - write255UShort(glyfStream, codeSize); - encodePushSequence(pushStream, result); - if (codeSize > 0) { - data.slice(i).copyTo(codeStream); - } - } - - // As per section 6.2.2 of the spec - private void encodePushSequence(ByteArrayOutputStream os, List<Integer> data) throws IOException { - int n = data.size(); - int hopSkip = 0; - for (int i = 0; i < n; i++) { - if ((hopSkip & 1) == 0) { - int val = data.get(i); - if (doHop && hopSkip == 0 && i >= 2 && - i + 2 < n && val == data.get(i - 2) && val == data.get(i + 2)) { - if (i + 4 < n && val == data.get(i + 4)) { - // Hop4 code - os.write(252); - hopSkip = 0x14; - } else { - // Hop3 code - os.write(251); - hopSkip = 4; - } - } else { - if (push2byte) { - // Measure relative effectiveness of 255Short literal encoding vs 2-byte ushort. - writeUShort(os, data.get(i)); - } else { - write255Short(os, data.get(i)); - } - } - } - hopSkip >>= 1; - } - } - - public byte[] getGlyfBytes() { - if (reslice) { - ByteArrayOutputStream newStream = new ByteArrayOutputStream(); - try { - // Pack all the glyf streams in a sensible way - writeLong(newStream, 0); // version - writeUShort(newStream, nGlyphs); - writeUShort(newStream, indexFmt.value()); - writeLong(newStream, nContourStream.size()); - writeLong(newStream, nPointsStream.size()); - writeLong(newStream, flagBytesStream.size()); - writeLong(newStream, glyfStream.size()); - writeLong(newStream, compositeStream.size()); - writeLong(newStream, bboxBitmap.length + bboxStream.size()); - writeLong(newStream, codeStream.size()); -// System.out.printf("stream sizes = %d %d %d %d %d %d %d\n", -// nContourStream.size(), nPointsStream.size(), flagBytesStream.size(), glyfStream.size(), -// compositeStream.size(), bboxStream.size(), codeStream.size()); - nContourStream.writeTo(newStream); - nPointsStream.writeTo(newStream); - flagBytesStream.writeTo(newStream); - glyfStream.writeTo(newStream); - compositeStream.writeTo(newStream); - newStream.write(bboxBitmap); - bboxStream.writeTo(newStream); - codeStream.writeTo(newStream); - } catch (IOException e) { - throw new RuntimeException("Can't happen, world must have come to end", e); - } - return newStream.toByteArray(); - } else { - return glyfStream.toByteArray(); - } - } - - public byte[] getPushBytes() { - return pushStream.toByteArray(); - } - - public byte[] getCodeBytes() { - return codeStream.toByteArray(); - } - - public byte[] getLocaBytes() { - return new byte[]{ }; - } -} |