blob: b47d8fed245808d8d218c60eebbb1264a66de4fe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.fontbox.cff;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.fontbox.cff.CharStringCommand.Type2KeyWord;
/**
* This class represents a converter for a mapping into a Type2-sequence.
*
* @author Villu Ruusmann
*/
public class Type2CharStringParser
{
// 1-byte commands
private static final int CALLSUBR = 10;
private static final int CALLGSUBR = 29;
private final String fontName;
/**
* Constructs a new Type1CharStringParser object for a Type 1-equivalent font.
*
* @param fontName font name
*/
public Type2CharStringParser(String fontName)
{
this.fontName = fontName;
}
/**
* The given byte array will be parsed and converted to a Type2 sequence.
*
* @param bytes the given mapping as byte array
* @param globalSubrIndex array containing all global subroutines
* @param localSubrIndex array containing all local subroutines
*
* @return the Type2 sequence
* @throws IOException if an error occurs during reading
*/
public List<Object> parse(byte[] bytes, byte[][] globalSubrIndex, byte[][] localSubrIndex)
throws IOException
{
GlyphData glyphData = new GlyphData();
parseSequence(bytes, globalSubrIndex, localSubrIndex, glyphData);
return glyphData.sequence;
}
private void parseSequence(byte[] bytes, byte[][] globalSubrIndex, byte[][] localSubrIndex,
GlyphData glyphData) throws IOException
{
DataInput input = new DataInputByteArray(bytes);
while (input.hasRemaining())
{
int b0 = input.readUnsignedByte();
if (b0 == CALLSUBR)
{
processCallSubr(globalSubrIndex, localSubrIndex, glyphData);
}
else if (b0 == CALLGSUBR)
{
processCallGSubr(globalSubrIndex, localSubrIndex, glyphData);
}
else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31))
{
glyphData.sequence.add(readCommand(b0, input, glyphData));
}
else if (b0 == 28 || (b0 >= 32 && b0 <= 255))
{
glyphData.sequence.add(readNumber(b0, input));
}
else
{
throw new IllegalArgumentException();
}
}
}
private byte[] getSubrBytes(byte[][] subrIndex, GlyphData glyphData)
{
int subrNumber = calculateSubrNumber(
(Integer) glyphData.sequence.remove(glyphData.sequence.size() - 1),
subrIndex.length);
return subrNumber < subrIndex.length ? subrIndex[subrNumber] : null;
}
private void processCallSubr(byte[][] globalSubrIndex, byte[][] localSubrIndex,
GlyphData glyphData) throws IOException
{
if (localSubrIndex != null && localSubrIndex.length > 0)
{
byte[] subrBytes = getSubrBytes(localSubrIndex, glyphData);
processSubr(globalSubrIndex, localSubrIndex, subrBytes, glyphData);
}
}
private void processCallGSubr(byte[][] globalSubrIndex, byte[][] localSubrIndex,
GlyphData glyphData) throws IOException
{
if (globalSubrIndex != null && globalSubrIndex.length > 0)
{
byte[] subrBytes = getSubrBytes(globalSubrIndex, glyphData);
processSubr(globalSubrIndex, localSubrIndex, subrBytes, glyphData);
}
}
private void processSubr(byte[][] globalSubrIndex, byte[][] localSubrIndex, byte[] subrBytes,
GlyphData glyphData) throws IOException
{
parseSequence(subrBytes, globalSubrIndex, localSubrIndex, glyphData);
Object lastItem = glyphData.sequence.get(glyphData.sequence.size() - 1);
if (lastItem instanceof CharStringCommand
&& Type2KeyWord.RET == ((CharStringCommand) lastItem).getType2KeyWord())
{
// remove "return" command
glyphData.sequence.remove(glyphData.sequence.size() - 1);
}
}
private int calculateSubrNumber(int operand, int subrIndexlength)
{
if (subrIndexlength < 1240)
{
return 107 + operand;
}
if (subrIndexlength < 33900)
{
return 1131 + operand;
}
return 32768 + operand;
}
private CharStringCommand readCommand(int b0, DataInput input, GlyphData glyphData)
throws IOException
{
switch (b0)
{
case 1:
case 18:
glyphData.hstemCount += countNumbers(glyphData.sequence) / 2;
return CharStringCommand.getInstance(b0);
case 3:
case 23:
glyphData.vstemCount += countNumbers(glyphData.sequence) / 2;
return CharStringCommand.getInstance(b0);
case 12:
return CharStringCommand.getInstance(b0, input.readUnsignedByte());
case 19:
case 20:
glyphData.vstemCount += countNumbers(glyphData.sequence) / 2;
int[] value = new int[1 + getMaskLength(glyphData.hstemCount, glyphData.vstemCount)];
value[0] = b0;
for (int i = 1; i < value.length; i++)
{
value[i] = input.readUnsignedByte();
}
return CharStringCommand.getInstance(value);
default:
return CharStringCommand.getInstance(b0);
}
}
private Number readNumber(int b0, DataInput input) throws IOException
{
if (b0 == 28)
{
return (int) input.readShort();
}
if (b0 >= 32 && b0 <= 246)
{
return b0 - 139;
}
if (b0 >= 247 && b0 <= 250)
{
int b1 = input.readUnsignedByte();
return (b0 - 247) * 256 + b1 + 108;
}
if (b0 >= 251 && b0 <= 254)
{
int b1 = input.readUnsignedByte();
return -(b0 - 251) * 256 - b1 - 108;
}
if (b0 == 255)
{
short value = input.readShort();
// The lower bytes are representing the digits after the decimal point
double fraction = input.readUnsignedShort() / 65535d;
return value + fraction;
}
throw new IllegalArgumentException();
}
private int getMaskLength(int hstemCount, int vstemCount)
{
int hintCount = hstemCount + vstemCount;
int length = hintCount / 8;
if (hintCount % 8 > 0)
{
length++;
}
return length;
}
private int countNumbers(List<Object> sequence)
{
int count = 0;
for (int i = sequence.size() - 1; i > -1; i--)
{
if (!(sequence.get(i) instanceof Number))
{
return count;
}
count++;
}
return count;
}
@Override
public String toString()
{
return fontName;
}
private class GlyphData
{
final List<Object> sequence = new ArrayList<>();
int hstemCount = 0;
int vstemCount = 0;
private GlyphData()
{
}
}
}