TextChunkLocationDefaultImp.java
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2025 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itextpdf.kernel.pdf.canvas.parser.listener;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.kernel.geom.LineSegment;
import com.itextpdf.kernel.geom.Vector;
class TextChunkLocationDefaultImp implements ITextChunkLocation {
private static final float DIACRITICAL_MARKS_ALLOWED_VERTICAL_DEVIATION = 2;
/**
* The starting location of the chunk.
*/
private final Vector startLocation;
/**
* The ending location of the chunk.
*/
private final Vector endLocation;
/**
* Unit vector in the orientation of the chunk.
*/
private final Vector orientationVector;
/**
* The orientation as a scalar for quick sorting.
*/
private final int orientationMagnitude;
/**
* Perpendicular distance to the orientation unit vector (i.e. the Y position in an unrotated coordinate system).
* We round to the nearest integer to handle the fuzziness of comparing floats.
*/
private final int distPerpendicular;
/**
* Distance of the start of the chunk parallel to the orientation unit vector (i.e. the X position in an unrotated coordinate system).
*/
private final float distParallelStart;
/**
* Distance of the end of the chunk parallel to the orientation unit vector (i.e. the X position in an unrotated coordinate system).
*/
private final float distParallelEnd;
/**
* The width of a single space character in the font of the chunk.
*/
private final float charSpaceWidth;
public TextChunkLocationDefaultImp(Vector startLocation, Vector endLocation, float charSpaceWidth) {
this.startLocation = startLocation;
this.endLocation = endLocation;
this.charSpaceWidth = charSpaceWidth;
Vector oVector = endLocation.subtract(startLocation);
if (oVector.length() == 0) {
oVector = new Vector(1, 0, 0);
}
orientationVector = oVector.normalize();
orientationMagnitude = (int) FontProgram.convertGlyphSpaceToTextSpace(
Math.atan2(orientationVector.get(Vector.I2), orientationVector.get(Vector.I1)));
// see http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
// the two vectors we are crossing are in the same plane, so the result will be purely
// in the z-axis (out of plane) direction, so we just take the I3 component of the result
Vector origin = new Vector(0, 0, 1);
distPerpendicular = (int) (startLocation.subtract(origin)).cross(orientationVector).get(Vector.I3);
distParallelStart = orientationVector.dot(startLocation);
distParallelEnd = orientationVector.dot(endLocation);
}
public int orientationMagnitude() {
return orientationMagnitude;
}
public int distPerpendicular() {
return distPerpendicular;
}
public float distParallelStart() {
return distParallelStart;
}
public float distParallelEnd() {
return distParallelEnd;
}
/**
* @return the start location of the text
*/
public Vector getStartLocation() {
return startLocation;
}
/**
* @return the end location of the text
*/
public Vector getEndLocation() {
return endLocation;
}
/**
* @return the width of a single space character as rendered by this chunk
*/
public float getCharSpaceWidth() {
return charSpaceWidth;
}
/**
* @param as the location to compare to
* @return true is this location is on the the same line as the other
*/
public boolean sameLine(ITextChunkLocation as) {
if (orientationMagnitude() != as.orientationMagnitude()) {
return false;
}
float distPerpendicularDiff = distPerpendicular() - as.distPerpendicular();
if (distPerpendicularDiff == 0) {
return true;
}
LineSegment mySegment = new LineSegment(startLocation, endLocation);
LineSegment otherSegment = new LineSegment(as.getStartLocation(), as.getEndLocation());
return Math.abs(distPerpendicularDiff) <= DIACRITICAL_MARKS_ALLOWED_VERTICAL_DEVIATION && (mySegment.getLength() == 0 || otherSegment.getLength() == 0);
}
/**
* Computes the distance between the end of 'other' and the beginning of this chunk
* in the direction of this chunk's orientation vector. Note that it's a bad idea
* to call this for chunks that aren't on the same line and orientation, but we don't
* explicitly check for that condition for performance reasons.
*
* @param other
* @return the number of spaces between the end of 'other' and the beginning of this chunk
*/
public float distanceFromEndOf(ITextChunkLocation other) {
return distParallelStart() - other.distParallelEnd();
}
public boolean isAtWordBoundary(ITextChunkLocation previous) {
// In case a text chunk is of zero length, this probably means this is a mark character,
// and we do not actually want to insert a space in such case
if (startLocation.equals(endLocation) || previous.getEndLocation().equals(previous.getStartLocation())) {
return false;
}
float dist = distanceFromEndOf(previous);
if (dist < 0) {
dist = previous.distanceFromEndOf(this);
//The situation when the chunks intersect. We don't need to add space in this case
if (dist < 0) {
return false;
}
}
return dist > getCharSpaceWidth() / 2.0f;
}
static boolean containsMark(ITextChunkLocation baseLocation, ITextChunkLocation markLocation) {
return baseLocation.getStartLocation().get(Vector.I1) <= markLocation.getStartLocation().get(Vector.I1) && baseLocation.getEndLocation().get(Vector.I1) >= markLocation.getEndLocation().get(Vector.I1) &&
Math.abs(baseLocation.distPerpendicular() - markLocation.distPerpendicular()) <= DIACRITICAL_MARKS_ALLOWED_VERTICAL_DEVIATION;
}
}