diff --git a/src/main/java/org/verapdf/pd/font/CIDVerticalMetrics.java b/src/main/java/org/verapdf/pd/font/CIDVerticalMetrics.java new file mode 100644 index 00000000..95fcddae --- /dev/null +++ b/src/main/java/org/verapdf/pd/font/CIDVerticalMetrics.java @@ -0,0 +1,51 @@ +/* + * This file is part of veraPDF Parser, a module of the veraPDF project. + * Copyright (c) 2015-2026, veraPDF Consortium + * All rights reserved. + * + * veraPDF Parser is free software: you can redistribute it and/or modify + * it under the terms of either: + * + * The GNU General public license GPLv3+. + * You should have received a copy of the GNU General Public License + * along with veraPDF Parser as the LICENSE.GPL file in the root of the source + * tree. If not, see http://www.gnu.org/licenses/ or + * https://www.gnu.org/licenses/gpl-3.0.en.html. + * + * The Mozilla Public License MPLv2+. + * You should have received a copy of the Mozilla Public License along with + * veraPDF Parser as the LICENSE.MPL file in the root of the source tree. + * If a copy of the MPL was not distributed with this file, you can obtain one at + * http://mozilla.org/MPL/2.0/. + */ +package org.verapdf.pd.font; + +/** + * Represents vertical metrics in CID Fonts + * + * @author Vladimir Burshnev + */ +public class CIDVerticalMetrics { + private final double displacementVectorH; + private final double displacementVectorV; + private final double positionVectorH; + private final double positionVectorV; + + public CIDVerticalMetrics(double displacement, double positionVectorH, double positionVectorV) { + this.displacementVectorV = displacement; + this.displacementVectorH = 0; + this.positionVectorH = positionVectorH; + this.positionVectorV = positionVectorV; + } + + public CIDVerticalMetrics(double displacement, double positionVector) { + this(displacement, 0, positionVector); + } + + /** + * @return vertical component of the displacement vector + */ + public double getDisplacement() { + return displacementVectorV; + } +} diff --git a/src/main/java/org/verapdf/pd/font/CIDW2Array.java b/src/main/java/org/verapdf/pd/font/CIDW2Array.java new file mode 100644 index 00000000..7fcb689b --- /dev/null +++ b/src/main/java/org/verapdf/pd/font/CIDW2Array.java @@ -0,0 +1,126 @@ +/* + * This file is part of veraPDF Parser, a module of the veraPDF project. + * Copyright (c) 2015-2026, veraPDF Consortium + * All rights reserved. + * + * veraPDF Parser is free software: you can redistribute it and/or modify + * it under the terms of either: + * + * The GNU General public license GPLv3+. + * You should have received a copy of the GNU General Public License + * along with veraPDF Parser as the LICENSE.GPL file in the root of the source + * tree. If not, see http://www.gnu.org/licenses/ or + * https://www.gnu.org/licenses/gpl-3.0.en.html. + * + * The Mozilla Public License MPLv2+. + * You should have received a copy of the Mozilla Public License along with + * veraPDF Parser as the LICENSE.MPL file in the root of the source tree. + * If a copy of the MPL was not distributed with this file, you can obtain one at + * http://mozilla.org/MPL/2.0/. + */ +package org.verapdf.pd.font; + +import org.verapdf.cos.COSArray; +import org.verapdf.cos.COSObjType; +import org.verapdf.cos.COSObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Represents W2 array in CID fonts. + * + * @author Vladimir Burshnev + */ +public class CIDW2Array { + + private static final Logger LOGGER = Logger.getLogger(CIDW2Array.class.getCanonicalName()); + + private final Map singleMappings; + private final List ranges; + + /** + * Constructor from a COSObject. + * + * @param w2 is W2 array from CIDFont dictionary. + */ + public CIDW2Array(COSArray w2) { + singleMappings = new HashMap<>(); + ranges = new ArrayList<>(); + if (w2 != null) { + for (int i = 0; i < w2.size(); ++i) { + Long cidBegin = w2.at(i++).getInteger(); + if (cidBegin == null) { + LOGGER.log(Level.FINE, "W2 array in CIDFont is invalid."); + return; + } + COSObject obj = w2.at(i); + if (obj.getType() == COSObjType.COS_INTEGER) { + int cidEnd = obj.getInteger().intValue(); + CIDVerticalMetrics verticalMetrics = getVerticalMetricsFromCOSArray(w2, ++i); + if (verticalMetrics == null) { + LOGGER.log(Level.FINE, "W2 array in CIDFont is invalid."); + return; + } + i += 2; + this.ranges.add(new CIDW2ArrayRange(cidBegin.intValue(), cidEnd, verticalMetrics)); + } else if (obj.getType() == COSObjType.COS_ARRAY) { + addSingleMappings(cidBegin.intValue(), (COSArray) obj.getDirectBase()); + } + } + } + } + + /** + * Get vertical metrics from W2 array. + * @param arr is COSArray. + * @param index is position of displacement vector in COSArray. + * @return vertical metrics as it is specified in W2 array. + */ + private CIDVerticalMetrics getVerticalMetricsFromCOSArray(COSArray arr, int index) { + Double displacement = arr.at(index).getReal(); + if (displacement == null) { + return null; + } + Double positionVectorH = arr.at(++index).getReal(); + if (positionVectorH == null) { + return null; + } + Double positionVectorV = arr.at(++index).getReal(); + if (positionVectorV == null) { + return null; + } + return new CIDVerticalMetrics(displacement, positionVectorH, positionVectorV); + } + + private void addSingleMappings(int cidBegin, COSArray arr) { + for (int i = 0; i < arr.size(); i += 3) { + CIDVerticalMetrics verticalMetrics = getVerticalMetricsFromCOSArray(arr, i); + if (verticalMetrics == null) { + LOGGER.log(Level.FINE, "W2 array in CIDFont is invalid."); + return; + } + this.singleMappings.put(cidBegin + i / 3, verticalMetrics); + } + } + + /** + * Get vertical component of the displacement vector of glyph with given cid according to W2 array. + * @param cid is cid of glyph in CIDFont. + * @return width as it is specified in W2 array. + */ + public Double getDisplacement(int cid) { + CIDVerticalMetrics res = singleMappings.get(cid); + if (res != null) return res.getDisplacement(); + for (CIDW2ArrayRange range : ranges) { + if (range.contains(cid)) { + return range.getDisplacement(); + } + } + return null; + } +} diff --git a/src/main/java/org/verapdf/pd/font/CIDW2ArrayRange.java b/src/main/java/org/verapdf/pd/font/CIDW2ArrayRange.java new file mode 100644 index 00000000..d54e5625 --- /dev/null +++ b/src/main/java/org/verapdf/pd/font/CIDW2ArrayRange.java @@ -0,0 +1,57 @@ +/* + * This file is part of veraPDF Parser, a module of the veraPDF project. + * Copyright (c) 2015-2026, veraPDF Consortium + * All rights reserved. + * + * veraPDF Parser is free software: you can redistribute it and/or modify + * it under the terms of either: + * + * The GNU General public license GPLv3+. + * You should have received a copy of the GNU General Public License + * along with veraPDF Parser as the LICENSE.GPL file in the root of the source + * tree. If not, see http://www.gnu.org/licenses/ or + * https://www.gnu.org/licenses/gpl-3.0.en.html. + * + * The Mozilla Public License MPLv2+. + * You should have received a copy of the Mozilla Public License along with + * veraPDF Parser as the LICENSE.MPL file in the root of the source tree. + * If a copy of the MPL was not distributed with this file, you can obtain one at + * http://mozilla.org/MPL/2.0/. + */ +package org.verapdf.pd.font; + +/** + * Represents range of sequential CIDs and vertical metrics for them. This is used in W2 + * array in CIDFonts. + * + * @author Vladimir Burshnev + */ +public class CIDW2ArrayRange { + private final CIDVerticalMetrics verticalMetrics; + private final int beginCID; + private final int endCID; + + public CIDW2ArrayRange(int beginCID, int endCID, CIDVerticalMetrics verticalMetrics) { + this.verticalMetrics = verticalMetrics; + this.beginCID = beginCID; + this.endCID = endCID; + } + + /** + * Returns true if width for given CID is stored in this CIDWArrayRange. + * + * @param cid is CID to check. + * @return true if width for this CID can be obtained from this + * CIDWArrayRange. + */ + public boolean contains(int cid) { + return cid >= beginCID && cid <= endCID; + } + + /** + * @return vertical component of the displacement vector for this range. + */ + public double getDisplacement() { + return verticalMetrics.getDisplacement(); + } +} diff --git a/src/main/java/org/verapdf/pd/font/CIDWArray.java b/src/main/java/org/verapdf/pd/font/CIDWArray.java index 35eeb476..87b697ea 100644 --- a/src/main/java/org/verapdf/pd/font/CIDWArray.java +++ b/src/main/java/org/verapdf/pd/font/CIDWArray.java @@ -53,18 +53,21 @@ public CIDWArray(COSArray w) { ranges = new ArrayList<>(); if (w != null) { for (int i = 0; i < w.size(); ++i) { - int cidBegin = w.at(i++).getInteger().intValue(); + Long cidBegin = w.at(i++).getInteger(); + if (cidBegin == null) { + LOGGER.log(Level.FINE, "W array in CIDFont is invalid."); + } COSObject obj = w.at(i); if (obj.getType() == COSObjType.COS_INTEGER) { int cidEnd = obj.getInteger().intValue(); Double width = w.at(++i).getReal(); if (width == null) { - LOGGER.log(Level.FINE, "Unexpected end of W array in CID font"); + LOGGER.log(Level.FINE, "W array in CIDFont is invalid."); return; } - this.ranges.add(new CIDWArrayRange(cidBegin, cidEnd, width)); + this.ranges.add(new CIDWArrayRange(cidBegin.intValue(), cidEnd, width)); } else if (obj.getType() == COSObjType.COS_ARRAY) { - addSingleMappings(cidBegin, (COSArray) obj.getDirectBase()); + addSingleMappings(cidBegin.intValue(), (COSArray) obj.getDirectBase()); } } } @@ -73,7 +76,7 @@ public CIDWArray(COSArray w) { private void addSingleMappings(int cidBegin, COSArray arr) { for (int i = 0; i < arr.size(); i++) { if (!arr.at(i).getType().isNumber()) { - LOGGER.log(Level.SEVERE, "W array in CIDFont has invalid entry."); + LOGGER.log(Level.FINE, "W array in CIDFont is invalid."); continue; } this.singleMappings.put(cidBegin + i, arr.at(i).getReal()); diff --git a/src/main/java/org/verapdf/pd/font/PDCIDFont.java b/src/main/java/org/verapdf/pd/font/PDCIDFont.java index 0dd06d1c..b076032b 100644 --- a/src/main/java/org/verapdf/pd/font/PDCIDFont.java +++ b/src/main/java/org/verapdf/pd/font/PDCIDFont.java @@ -44,10 +44,12 @@ public class PDCIDFont extends PDFont { private static final Logger LOGGER = Logger.getLogger(PDCIDFont.class.getCanonicalName()); + private static final Double[] DEFAULT_CID_FONT_VERTICAL_WIDTH = { 880d, -1000d }; private static final Double DEFAULT_CID_FONT_WIDTH = 1000d; protected CMap cMap; private CIDWArray widths; + private CIDW2Array verticalMetrics; private PDCIDSystemInfo cidSystemInfo; /** @@ -88,6 +90,13 @@ protected PDCIDFont(COSDictionary dictionary) { super(dictionary); } + /** + * @return true if font is vertical. + */ + public boolean isVertical() { + return cMap.getwMode() == 1; + } + /** * @return a stream identifying which CIDs are present in the CIDFont file. */ @@ -128,6 +137,25 @@ public Double getWidth(int code) { return res; } + /** + * Gets vertical width for glyph with given code in this font. + * + * @param code is code of glyph. + * @return vertical width for glyph with given code as specified in Widths 2 array. + */ + public Double getVerticalWidth(int code) { + Double[] defaultMetrics = getDefaultVMetrics(); + if (this.verticalMetrics == null) { + COSObject w2 = this.dictionary.getKey(ASAtom.W2); + if (w2.empty() || w2.getType() != COSObjType.COS_ARRAY) { + return defaultMetrics[1]; + } + this.verticalMetrics = new CIDW2Array((COSArray) w2.getDirectBase()); + } + Double res = verticalMetrics.getDisplacement(this.cMap.toCID(code)); + return res == null ? defaultMetrics[1] : res; + } + /** * {@inheritDoc} */ @@ -141,6 +169,22 @@ public Double getDefaultWidth() { } } + /** + * @return default vertical metrics for this font as specified in font descriptor. + */ + public Double[] getDefaultVMetrics() { + COSObject dw2 = this.dictionary.getKey(ASAtom.DW2); + if (dw2.getType() == COSObjType.COS_ARRAY && dw2.size() == 2) { + Double[] result = new Double[2]; + result[0] = dw2.at(0).getReal(); + if (result[0] == null) return DEFAULT_CID_FONT_VERTICAL_WIDTH; + result[1] = dw2.at(1).getReal(); + if (result[1] == null) return DEFAULT_CID_FONT_VERTICAL_WIDTH; + return result; + } + return DEFAULT_CID_FONT_VERTICAL_WIDTH; + } + /** * {@inheritDoc} */