diff --git a/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/ChunkParser.java b/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/ChunkParser.java index d655b08da..8e7f47031 100644 --- a/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/ChunkParser.java +++ b/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/ChunkParser.java @@ -35,6 +35,7 @@ import org.verapdf.pd.colors.PDDeviceCMYK; import org.verapdf.pd.colors.PDDeviceGray; import org.verapdf.pd.colors.PDDeviceRGB; +import org.verapdf.pd.font.PDCIDFont; import org.verapdf.pd.font.PDFontDescriptor; import org.verapdf.pd.images.PDXForm; import org.verapdf.pd.images.PDXObject; @@ -830,21 +831,20 @@ private void putChunk(Long mcid, IChunk chunk) { } } - private TextPieces parseTextShowArgument(COSBase argument, double horizontalScalingFactor) { - TextPieces textPieces = new TextPieces(); + private TextPieces parseTextShowArgument(boolean isVertical, COSBase argument, double scalingFactor) { + TextPieces textPieces = new TextPieces(isVertical); if (argument.getType() == COSObjType.COS_STRING) { - parseString((COSString) argument.getDirectBase(), textPieces, horizontalScalingFactor); + parseString(isVertical, (COSString) argument.getDirectBase(), textPieces, scalingFactor); } else if (argument.getType() == COSObjType.COS_ARRAY) { COSArray array = (COSArray) argument; for (COSObject obj : array) { if (obj != null) { if (obj.getType() == COSObjType.COS_STRING) { - parseString((COSString) obj.getDirectBase(), textPieces, horizontalScalingFactor); + parseString(isVertical, (COSString) obj.getDirectBase(), textPieces, scalingFactor); } else if (obj.getType().isNumber()) { - double shift = - obj.getReal() * horizontalScalingFactor * - graphicsState.getTextState().getTextFontSize() * - graphicsState.getTextState().getHorizontalScaling(); - textPieces.shiftCurrentX(shift); + TextState textState = graphicsState.getTextState(); + textPieces.shiftCurrent(-obj.getReal() * scalingFactor * textState.getTextFontSize() * + (isVertical ? 1 : textState.getHorizontalScaling())); } } } @@ -856,34 +856,32 @@ private TextPieces parseTextShowArgument(COSBase argument, double horizontalScal return textPieces; } - private void parseString(COSString string, TextPieces textPieces, double horizontalScalingFactor) { + private void parseString(boolean isVertical, COSString string, TextPieces textPieces, double scalingFactor) { byte[] bytes = string.get(); try (InputStream inputStream = new ByteArrayInputStream(bytes)) { while (inputStream.available() > 0) { - int code = graphicsState.getTextState().getTextFont().readCode(inputStream); - Double width = graphicsState.getTextState().getTextFont().getWidth(code); - if (width == null) { - LOGGER.log(Level.SEVERE, "Missing width of glyph with code " + code + - " in font" + graphicsState.getTextState().getTextFont().getName()); + double current = textPieces.getCurrent(); + TextState textState = graphicsState.getTextState(); + org.verapdf.pd.font.PDFont font = textState.getTextFont(); + int code = font.readCode(inputStream); + String value = font.toUnicode(code); + double scaling = isVertical ? 1 : textState.getHorizontalScaling(); + double shift = (textState.getCharacterSpacing() + (code == 32 ? textState.getWordSpacing() : 0)) * scaling; + Double width = isVertical ? ((PDCIDFont) font).getVerticalWidth(code) : font.getWidth(code); + if (width != null) { + width = width * textState.getTextFontSize() * scalingFactor * scaling; + } else { + LOGGER.log(Level.SEVERE, "Missing width of glyph with code " + code + " in font " + font.getName()); width = 0.0; } - double shift = (graphicsState.getTextState().getCharacterSpacing() + (code == 32 ? - graphicsState.getTextState().getWordSpacing() : 0)) * - graphicsState.getTextState().getHorizontalScaling(); - width = width * - graphicsState.getTextState().getTextFontSize() * horizontalScalingFactor * - graphicsState.getTextState().getHorizontalScaling(); - String value = graphicsState.getTextState().getTextFont().toUnicode(code); - String result = value; - if (result == null) { - result = StaticContainers.getIsIgnoreCharactersWithoutUnicode() ? "" : REPLACEMENT_CHARACTER_STRING; + if (value == null) { + value = StaticContainers.getIsIgnoreCharactersWithoutUnicode() ? "" : REPLACEMENT_CHARACTER_STRING; if (StaticContainers.isDataLoader()) { LOGGER.log(Level.WARNING, "The glyph can not be mapped to Unicode"); } } - textPieces.add(new TextPieces.TextPiece(result, textPieces.getCurrentX(), - textPieces.getCurrentX() + width)); - textPieces.shiftCurrentX(shift); + textPieces.add(new TextPieces.TextPiece(value, current, current + width)); + textPieces.shiftCurrent(shift); } } catch (IOException e) { LOGGER.log(Level.SEVERE, "Error processing text show operator's string argument : " + new String(bytes), e); @@ -892,39 +890,60 @@ private void parseString(COSString string, TextPieces textPieces, double horizon private TextChunk createTextChunk(List arguments, String operatorType, int operatorIndex) { org.verapdf.pd.font.PDFont font = graphicsState.getTextState().getTextFont(); + boolean isVertical = font instanceof PDCIDFont && ((PDCIDFont) font).isVertical(); COSBase argument = TextChunksHelper.getArgument(arguments, operatorType); if (font != null && argument != null && (argument.getType() == COSObjType.COS_STRING || argument.getType() == COSObjType.COS_ARRAY) && this.textMatrix != null) { - double horizontalScalingFactor = TextChunksHelper.getHorizontalScalingFactor(font); - TextPieces textPieces = parseTextShowArgument(argument, horizontalScalingFactor); + double scalingFactor = isVertical ? TextChunksHelper.getVerticalScalingFactor(font) : + TextChunksHelper.getHorizontalScalingFactor(font); + TextPieces textPieces = parseTextShowArgument(isVertical, argument, scalingFactor); if (textPieces.isEmpty()) { - textMatrix.concatenate(Matrix.getTranslateInstance(textPieces.getCurrentX(), 0)); + textMatrix.concatenate( + textPieces.isVertical + ? Matrix.getTranslateInstance(0, textPieces.getCurrent()) + : Matrix.getTranslateInstance(textPieces.getCurrent(), 0) + ); return null; } - textMatrix.concatenate(Matrix.getTranslateInstance(textPieces.getStartX(), 0)); + double startX = 0; + double startY = 0; + double currentX = 0; + double currentY = 0; + double endX = 0; + double endY = 0; + if (isVertical) { + currentY = textPieces.getCurrent(); + startY = textPieces.getStart(); + endY = textPieces.getEnd(); + } else { + currentX = textPieces.getCurrent(); + startX = textPieces.getStart(); + endX = textPieces.getEnd(); + } + textMatrix.concatenate(Matrix.getTranslateInstance(startX, startY)); Matrix textRenderingMatrixBefore = calculateTextRenderingMatrix(); - textMatrix.concatenate(Matrix.getTranslateInstance(textPieces.getEndX() - textPieces.getStartX(), 0)); + textMatrix.concatenate(Matrix.getTranslateInstance(endX - startX, endY - startY)); Matrix textRenderingMatrixAfter = calculateTextRenderingMatrix(); - textMatrix.concatenate(Matrix.getTranslateInstance(textPieces.getCurrentX() - textPieces.getEndX(), 0)); - PDFontDescriptor descriptor = font.getFontDescriptor(); - String fontNameWithoutSubset = font.getNameWithoutSubset(); - TextChunk textChunk = new TextChunk(TextChunksHelper.calculateTextBoundingBox(textRenderingMatrixBefore, - textRenderingMatrixAfter, font, pageNumber, horizontalScalingFactor), textPieces.getValue(), - fontNameWithoutSubset, TextChunksHelper.calculateTextSize(textRenderingMatrixAfter), - TextChunksHelper.calculateFontWeight(graphicsState.getTextState().getRenderingMode(), font), - descriptor.getItalicAngle(), TextChunksHelper.calculateTextBaseLine(textRenderingMatrixAfter), + textMatrix.concatenate(Matrix.getTranslateInstance(currentX - endX, currentY - endY)); + PDFontDescriptor descriptor = font.getFontDescriptor(); + String fontNameWithoutSubset = font.getNameWithoutSubset(); + TextChunk textChunk = new TextChunk( + TextChunksHelper.calculateTextBoundingBox(isVertical, textRenderingMatrixBefore, textRenderingMatrixAfter, font, pageNumber), + textPieces.getValue(), fontNameWithoutSubset, TextChunksHelper.calculateTextSize(textRenderingMatrixAfter), + TextChunksHelper.calculateFontWeight(graphicsState.getTextState().getRenderingMode(), font), + descriptor.getItalicAngle(), TextChunksHelper.calculateTextBaseLine(textRenderingMatrixAfter), graphicsState.getFillColor(), textRenderingMatrixAfter.getRotationDegree()); - if (StaticContainers.isDataLoader() && !fontNameToFontFamilyMap.containsKey(fontNameWithoutSubset)) { - String fontFamily = descriptor.getFontFamily(); - if (fontFamily == null || fontFamily.isEmpty()) { - fontFamily = PDFontDescriptor.extractFontFamilyFromFontName(fontNameWithoutSubset); - } - fontNameToFontFamilyMap.put(fontNameWithoutSubset, fontFamily); - } textChunk.adjustSymbolEndsToBoundingBox(textPieces.getSymbolEnds()); if (StaticContainers.isDataLoader()) { textChunk.getStreamInfos().addAll(textPieces.getStreamInfos(operatorIndex, xObjectName)); } + if (StaticContainers.isDataLoader() && !fontNameToFontFamilyMap.containsKey(fontNameWithoutSubset)) { + String fontFamily = descriptor.getFontFamily(); + if (fontFamily == null || fontFamily.isEmpty()) { + fontFamily = PDFontDescriptor.extractFontFamilyFromFontName(fontNameWithoutSubset); + } + fontNameToFontFamilyMap.put(fontNameWithoutSubset, fontFamily); + } return textChunk; } return null; diff --git a/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextChunksHelper.java b/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextChunksHelper.java index f3eaae5aa..1892a6094 100644 --- a/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextChunksHelper.java +++ b/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextChunksHelper.java @@ -45,8 +45,14 @@ protected static COSBase getArgument(List arguments, String operatorTyp return null; } - protected static BoundingBox calculateTextBoundingBox(Matrix textRenderingMatrixBefore, Matrix textRenderingMatrixAfter, - PDFont font, Integer pageNumber, double horizontalScalingFactor) { + protected static BoundingBox calculateTextBoundingBox(boolean isVertical, Matrix textRenderingMatrixBefore, + Matrix textRenderingMatrixAfter, PDFont font, Integer pageNumber) { + return isVertical ? TextChunksHelper.calculateTextBoundingBoxV(textRenderingMatrixBefore, textRenderingMatrixAfter, pageNumber) + : TextChunksHelper.calculateTextBoundingBoxH(textRenderingMatrixBefore, textRenderingMatrixAfter, font, pageNumber); + } + + private static BoundingBox calculateTextBoundingBoxH(Matrix textRenderingMatrixBefore, Matrix textRenderingMatrixAfter, + PDFont font, Integer pageNumber) { double[] fontBoundingBox = font.getBoundingBox(); Double descent = font.getDescent(); if (descent == null) { @@ -56,9 +62,9 @@ protected static BoundingBox calculateTextBoundingBox(Matrix textRenderingMatrix if (ascent == null) { ascent = fontBoundingBox[3]; } - double verticalScalingFactor = TextChunksHelper.getVerticalScalingFactor(font); double x1; double x2; + double horizontalScalingFactor = TextChunksHelper.getHorizontalScalingFactor(font); if (textRenderingMatrixBefore.getScaleX() >= 0 && textRenderingMatrixBefore.getShearX() >= 0) { x1 = textRenderingMatrixBefore.getTranslateX() + descent * textRenderingMatrixBefore.getShearX() * horizontalScalingFactor; x2 = textRenderingMatrixAfter.getTranslateX() + ascent * textRenderingMatrixAfter.getShearX() * horizontalScalingFactor; @@ -74,6 +80,7 @@ protected static BoundingBox calculateTextBoundingBox(Matrix textRenderingMatrix } double y1; double y2; + double verticalScalingFactor = TextChunksHelper.getVerticalScalingFactor(font); if (textRenderingMatrixBefore.getScaleY() >= 0 && textRenderingMatrixBefore.getShearY() >= 0) { y1 = textRenderingMatrixBefore.getTranslateY() + descent * textRenderingMatrixBefore.getScaleY() * verticalScalingFactor; y2 = textRenderingMatrixAfter.getTranslateY() + ascent * textRenderingMatrixAfter.getScaleY() * verticalScalingFactor; @@ -90,6 +97,41 @@ protected static BoundingBox calculateTextBoundingBox(Matrix textRenderingMatrix return new BoundingBox(pageNumber, x1, y1, x2, y2); } + private static BoundingBox calculateTextBoundingBoxV(Matrix textRenderingMatrixBefore, Matrix textRenderingMatrixAfter, + Integer pageNumber) { + double x1; + double x2; + if (textRenderingMatrixBefore.getScaleX() >= 0 && textRenderingMatrixBefore.getShearX() <= 0) { + x1 = textRenderingMatrixBefore.getTranslateX() - 0.5 * textRenderingMatrixBefore.getScaleX(); + x2 = textRenderingMatrixAfter.getTranslateX() + 0.5 * textRenderingMatrixAfter.getScaleX(); + } else if (textRenderingMatrixBefore.getScaleX() < 0 && textRenderingMatrixBefore.getShearX() > 0) { + x1 = textRenderingMatrixAfter.getTranslateX() + 0.5 * textRenderingMatrixAfter.getScaleX(); + x2 = textRenderingMatrixBefore.getTranslateX() - 0.5 * textRenderingMatrixBefore.getScaleX(); + } else if (textRenderingMatrixBefore.getScaleX() >= 0) { + x1 = textRenderingMatrixBefore.getTranslateX() + 0.5 * textRenderingMatrixBefore.getScaleX(); + x2 = textRenderingMatrixAfter.getTranslateX() - 0.5 * textRenderingMatrixAfter.getScaleX(); + } else { + x1 = textRenderingMatrixAfter.getTranslateX() - 0.5 * textRenderingMatrixAfter.getScaleX(); + x2 = textRenderingMatrixBefore.getTranslateX() + 0.5 * textRenderingMatrixBefore.getScaleX(); + } + double y1; + double y2; + if (textRenderingMatrixBefore.getScaleY() >= 0 && textRenderingMatrixBefore.getShearY() <= 0) { + y1 = textRenderingMatrixBefore.getTranslateY() - 0.5 * textRenderingMatrixBefore.getShearY(); + y2 = textRenderingMatrixAfter.getTranslateY() + 0.5 * textRenderingMatrixAfter.getShearY(); + } else if (textRenderingMatrixBefore.getScaleY() < 0 && textRenderingMatrixBefore.getShearY() > 0) { + y1 = textRenderingMatrixAfter.getTranslateY() + 0.5 * textRenderingMatrixAfter.getShearY(); + y2 = textRenderingMatrixBefore.getTranslateY() - 0.5 * textRenderingMatrixBefore.getShearY(); + } else if (textRenderingMatrixBefore.getScaleY() >= 0) { + y1 = textRenderingMatrixAfter.getTranslateY() - 0.5 * textRenderingMatrixAfter.getShearY(); + y2 = textRenderingMatrixBefore.getTranslateY() + 0.5 * textRenderingMatrixBefore.getShearY(); + } else { + y1 = textRenderingMatrixBefore.getTranslateY() + 0.5 * textRenderingMatrixBefore.getShearY(); + y2 = textRenderingMatrixAfter.getTranslateY() - 0.5 * textRenderingMatrixAfter.getShearY(); + } + return new BoundingBox(pageNumber, x1, y1, x2, y2); + } + public static double getHorizontalScalingFactor(PDFont font) { if (font instanceof PDType3Font) { double[] fontMatrix = ((PDType3Font) font).getFontMatrix(); diff --git a/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextPieces.java b/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextPieces.java index 1694a74b5..75019bdc1 100644 --- a/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextPieces.java +++ b/wcag-validation/src/main/java/org/verapdf/gf/model/factory/chunks/TextPieces.java @@ -28,16 +28,25 @@ * @author Maxim Plushchov */ public class TextPieces { - - private final SortedSet textPieces = new TreeSet<>(new TextPieceComparator()); - private double currentX = 0; - private int currentIndex = 0; + private final Comparator comparator; + private final SortedSet textPieces; + public final boolean isVertical; + private int currentIndex; + private double current; + + TextPieces(boolean isVertical) { + this.isVertical = isVertical; + this.comparator = (p1, p2) -> isVertical ? -Double.compare(p1, p2) : Double.compare(p1, p2); + textPieces = new TreeSet<>(isVertical ? new TextPieceComparatorV() : new TextPieceComparatorH()); + currentIndex = 0; + current = 0; + } public void add(TextPiece textPiece) { - textPieces.add(textPiece); textPiece.startIndex = currentIndex; + textPieces.add(textPiece); currentIndex += textPiece.value.length(); - currentX = textPiece.endX; + current = textPiece.end; } public String getValue() { @@ -48,60 +57,60 @@ public String getValue() { return unicodeValue.toString(); } - public double getStartX() { - return textPieces.isEmpty() ? 0.0d : textPieces.first().startX; + public double getStart() { + return textPieces.isEmpty() ? 0.0d : textPieces.first().start; } - public double getEndX() { - return textPieces.stream().map(TextPiece::getEndX).max(Double::compare).orElse(getStartX()); + public double getEnd() { + return textPieces.stream().map(tp -> tp.end).max(comparator).orElse(getStart()); } - public double getCurrentX() { - return currentX; + public double getCurrent() { + return current; } - public void shiftCurrentX(double shift) { - currentX += shift; + public void setCurrent(double current) { + this.current = current; + } + + public void shiftCurrent(double shift) { + current += shift; } public boolean isEmpty() { return textPieces.isEmpty(); } - public void setCurrentX(double currentX) { - this.currentX = currentX; - } - public List getSymbolEnds() { List ends = new ArrayList<>(); - double startX = getStartX(); + double start = getStart(); ends.add(0.0d); for (TextPiece textPiece : textPieces) { - TextChunksHelper.updateSymbolEnds(ends, textPiece.endX - textPiece.startX, textPiece.startX - startX, + TextChunksHelper.updateSymbolEnds(ends, textPiece.end - textPiece.start, textPiece.start - start, textPiece.value != null ? textPiece.value.length() : 0); } return ends; } - public void addSpaces(double threshold) { - List spaces = new ArrayList<>(); - Iterator validation = textPieces.iterator(); - if (!validation.hasNext()) { - return; - } - TextPiece prev = validation.next(); - double previousEnd = prev.getEndX(); - - while (validation.hasNext()) { - TextPiece piece = validation.next(); - double currentStart = piece.getStartX(); - if (currentStart - previousEnd > threshold) { - spaces.add(new TextPieces.TextPiece(" ", previousEnd, currentStart)); - } - previousEnd = piece.getEndX(); - } - textPieces.addAll(spaces); - } + public void addSpaces(double threshold) { + List spaces = new ArrayList<>(); + Iterator validation = textPieces.iterator(); + if (!validation.hasNext()) { + return; + } + TextPiece prev = validation.next(); + double previousEnd = prev.end; + + while (validation.hasNext()) { + TextPiece piece = validation.next(); + double currentStart = piece.start; + if ((isVertical ? previousEnd - currentStart : currentStart - previousEnd) > threshold) { + spaces.add(new TextPieces.TextPiece(" ", previousEnd, currentStart)); + } + previousEnd = piece.end; + } + textPieces.addAll(spaces); + } public List getStreamInfos(int operatorIndex, String xObjectName) { List streamInfos = new ArrayList<>(); @@ -127,38 +136,47 @@ public List getStreamInfos(int operatorIndex, String xObjectName) { public static class TextPiece { private final String value; - private final double startX; - private final double endX; + private final double start; + private final double center; + private final double end; + private Integer startIndex; - public TextPiece(String value, double startX, double endX) { + public TextPiece(String value, double start, double end) { this.value = value; - this.startX = startX; - this.endX = endX; - } - - public double getEndX() { - return endX; + this.start = start; + this.center = (start + end) / 2; + this.end = end; } + } - public double getStartX() { - return startX; - } - - public double getCenterX() { - return (getStartX() + getEndX()) / 2; + public static class TextPieceComparatorH implements Comparator { + @Override + public int compare(TextPiece textPiece1, TextPiece textPiece2){ + int res = Double.compare(textPiece1.center, textPiece2.center); + if (res != 0) { + return res; + } + res = Double.compare(textPiece1.end, textPiece2.end); + if (res != 0) { + return res; + } + return Integer.compare(textPiece1.startIndex, textPiece2.startIndex); } } - public static class TextPieceComparator implements Comparator { - + public static class TextPieceComparatorV implements Comparator { @Override public int compare(TextPiece textPiece1, TextPiece textPiece2){ - int res = Double.compare(textPiece1.getCenterX(), textPiece2.getCenterX()); + int res = -Double.compare(textPiece1.center, textPiece2.center); + if (res != 0) { + return res; + } + res = -Double.compare(textPiece1.end, textPiece2.end); if (res != 0) { return res; } - return Double.compare(textPiece1.endX, textPiece2.endX); + return Integer.compare(textPiece1.startIndex, textPiece2.startIndex); } } }