blob: 42616581efb71824b56b35314a3b83283c908262 [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.
*/
/* $Id$ */
package org.apache.fop.render.pcl.fonts;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
public class PCLSoftFontManager {
private Map<Typeface, PCLFontReader> fontReaderMap;
private PCLFontReader fontReader;
private List<PCLSoftFont> fonts = new ArrayList<PCLSoftFont>();
private static final int SOFT_FONT_SIZE = 255;
public PCLSoftFontManager(Map<Typeface, PCLFontReader> fontReaderMap) {
this.fontReaderMap = fontReaderMap;
}
public ByteArrayOutputStream makeSoftFont(Typeface font, String text) throws IOException {
List<Map<Character, Integer>> mappedGlyphs = mapFontGlyphs(font);
if (!fontReaderMap.containsKey(font)) {
fontReaderMap.put(font, PCLFontReaderFactory.createInstance(font));
}
fontReader = fontReaderMap.get(font);
if (mappedGlyphs.isEmpty()) {
mappedGlyphs.add(new HashMap<Character, Integer>());
}
if (fontReader != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PCLSoftFont softFont = null;
for (Map<Character, Integer> glyphSet : mappedGlyphs) {
softFont = getSoftFont(font, text, mappedGlyphs, softFont);
softFont.setMappedChars(glyphSet);
writeFontID(softFont.getFontID(), baos);
writeFontHeader(softFont.getMappedChars(), baos);
softFont.setCharacterOffsets(fontReader.getCharacterOffsets());
softFont.setOpenFont(fontReader.getFontFile());
softFont.setReader(fontReader.getFontFileReader());
softFont.setMtxCharIndexes(fontReader.scanMtxCharacters());
}
return baos;
} else {
return null;
}
}
private PCLSoftFont getSoftFont(Typeface font, String text, List<Map<Character, Integer>> mappedGlyphs,
PCLSoftFont last) {
if (text == null) {
Iterator<PCLSoftFont> fontIterator = fonts.iterator();
while (fontIterator.hasNext()) {
PCLSoftFont sftFont = fontIterator.next();
if (sftFont.getTypeface().equals(font)) {
fontIterator.remove();
return sftFont;
}
}
}
for (PCLSoftFont sftFont : fonts) {
if (sftFont.getTypeface().equals(font) && sftFont != last
&& (sftFont.getCharCount() + countNonMatches(sftFont, text)) < SOFT_FONT_SIZE) {
return sftFont;
}
}
PCLSoftFont f = new PCLSoftFont(fonts.size() + 1, font, mappedGlyphs.get(0).size() != 0);
fonts.add(f);
return f;
}
private List<Map<Character, Integer>> mapFontGlyphs(Typeface tf) {
List<Map<Character, Integer>> mappedGlyphs = new ArrayList<Map<Character, Integer>>();
if (tf instanceof CustomFontMetricsMapper) {
CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) tf;
CustomFont customFont = (CustomFont) fontMetrics.getRealFont();
mappedGlyphs = mapGlyphs(customFont.getUsedGlyphs(), customFont);
}
return mappedGlyphs;
}
private List<Map<Character, Integer>> mapGlyphs(Map<Integer, Integer> usedGlyphs, CustomFont font) {
int charCount = 32;
List<Map<Character, Integer>> mappedGlyphs = new ArrayList<Map<Character, Integer>>();
Map<Character, Integer> fontGlyphs = new HashMap<Character, Integer>();
for (Entry<Integer, Integer> entry : usedGlyphs.entrySet()) {
int glyphID = entry.getKey();
if (glyphID == 0) {
continue;
}
char unicode = font.getUnicodeFromGID(glyphID);
if (charCount > SOFT_FONT_SIZE) {
mappedGlyphs.add(fontGlyphs);
charCount = 32;
fontGlyphs = new HashMap<Character, Integer>();
}
fontGlyphs.put(unicode, charCount++);
}
if (fontGlyphs.size() > 0) {
mappedGlyphs.add(fontGlyphs);
}
return mappedGlyphs;
}
private void writeFontID(int fontID, OutputStream os) throws IOException {
os.write(assignFontID(fontID));
}
public byte[] assignFontID(int fontID) throws IOException {
return PCLByteWriterUtil.writeCommand(String.format("*c%dD", fontID));
}
private void writeFontHeader(Map<Character, Integer> mappedGlyphs, OutputStream os) throws IOException {
ByteArrayOutputStream header = new ByteArrayOutputStream();
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getDescriptorSize()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getHeaderFormat()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getFontType()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getStyleMSB()));
header.write(0); // Reserved
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getBaselinePosition()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getCellWidth()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getCellHeight()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getOrientation()));
header.write(fontReader.getSpacing());
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getSymbolSet()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getPitch()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getHeight()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getXHeight()));
header.write(PCLByteWriterUtil.signedByte(fontReader.getWidthType()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getStyleLSB()));
header.write(PCLByteWriterUtil.signedByte(fontReader.getStrokeWeight()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getTypefaceLSB()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getTypefaceMSB()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getSerifStyle()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getQuality()));
header.write(PCLByteWriterUtil.signedByte(fontReader.getPlacement()));
header.write(PCLByteWriterUtil.signedByte(fontReader.getUnderlinePosition()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getUnderlineThickness()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getTextHeight()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getTextWidth()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getFirstCode()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getLastCode()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getPitchExtended()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getHeightExtended()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getCapHeight()));
header.write(PCLByteWriterUtil.unsignedLongInt(fontReader.getFontNumber()));
header.write(PCLByteWriterUtil.padBytes(fontReader.getFontName().getBytes("US-ASCII"), 16, 32));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getScaleFactor()));
header.write(PCLByteWriterUtil.signedInt(fontReader.getMasterUnderlinePosition()));
header.write(PCLByteWriterUtil.unsignedInt(fontReader.getMasterUnderlineThickness()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getFontScalingTechnology()));
header.write(PCLByteWriterUtil.unsignedByte(fontReader.getVariety()));
writeSegmentedFontData(header, mappedGlyphs);
os.write(getFontHeaderCommand(header.size()));
os.write(header.toByteArray());
}
private void writeSegmentedFontData(ByteArrayOutputStream header,
Map<Character, Integer> mappedGlyphs) throws IOException {
List<PCLFontSegment> fontSegments = fontReader.getFontSegments(mappedGlyphs);
for (PCLFontSegment segment : fontSegments) {
writeFontSegment(header, segment);
}
header.write(0); // Reserved
// Checksum must equal 0 when added to byte 64 offset (modulo 256)
long sum = 0;
byte[] headerBytes = header.toByteArray();
for (int i = 64; i < headerBytes.length; i++) {
sum += headerBytes[i];
}
int remainder = (int) (sum % 256);
header.write(256 - remainder);
}
private byte[] getFontHeaderCommand(int headerSize) throws IOException {
return PCLByteWriterUtil.writeCommand(String.format(")s%dW", headerSize));
}
private void writeFontSegment(ByteArrayOutputStream header, PCLFontSegment segment) throws IOException {
header.write(PCLByteWriterUtil.unsignedInt(segment.getIdentifier().getValue()));
header.write(PCLByteWriterUtil.unsignedInt(segment.getData().length));
header.write(segment.getData());
}
/**
* Finds a soft font associated with the given typeface. If more than one instance of the font exists (as each font
* is bound and restricted to 255 characters) it will find the last font with available capacity.
* @param font The typeface associated with the soft font
* @return Returns the PCLSoftFont with available capacity
*/
public PCLSoftFont getSoftFont(Typeface font, String text) {
for (PCLSoftFont sftFont : fonts) {
if (sftFont.getTypeface().equals(font)
&& sftFont.getCharCount() + countNonMatches(sftFont, text) < SOFT_FONT_SIZE) {
return sftFont;
}
}
return null;
}
public PCLSoftFont getSoftFontFromID(int index) {
return fonts.get(index - 1);
}
private int countNonMatches(PCLSoftFont font, String text) {
int result = 0;
for (char ch : text.toCharArray()) {
int value = font.getUnicodeCodePoint(ch);
if (value == -1) {
result++;
}
}
return result;
}
public int getSoftFontID(Typeface tf) throws IOException {
PCLSoftFont font = getSoftFont(tf, "");
for (int i = 0; i < fonts.size(); i++) {
if (fonts.get(i).equals(font)) {
return i + 1;
}
}
return -1;
}
public List<PCLTextSegment> getTextSegments(String text, Typeface font) {
List<PCLTextSegment> textSegments = new ArrayList<PCLTextSegment>();
int curFontID = -1;
String current = "";
for (char ch : text.toCharArray()) {
for (PCLSoftFont softFont : fonts) {
if (curFontID == -1) {
curFontID = softFont.getFontID();
}
if (softFont.getCharIndex(ch) == -1 || !softFont.getTypeface().equals(font)) {
continue;
}
if (current.length() > 0 && curFontID != softFont.getFontID()) {
textSegments.add(new PCLTextSegment(curFontID, current));
current = "";
curFontID = softFont.getFontID();
}
if (curFontID != softFont.getFontID()) {
curFontID = softFont.getFontID();
}
current += ch;
break;
}
}
if (current.length() > 0) {
textSegments.add(new PCLTextSegment(curFontID, current));
}
return textSegments;
}
public static class PCLTextSegment {
private String text;
private int fontID;
public PCLTextSegment(int fontID, String text) {
this.text = text;
this.fontID = fontID;
}
public String getText() {
return text;
}
public int getFontID() {
return fontID;
}
}
}