| /* |
| * 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; |
| } |
| } |
| } |