RED-9746: Document hardly editable
* revert quadtree lookup, since the lib does not seem to work reliably, also, no significant speed boost * check each individual glyph instead of only a text run and remember past overlaps in glyph * added logic to extract all glyphs exactly * check for optional content or transparency in form objects and marked content
This commit is contained in:
parent
5e00420f4d
commit
1862355a01
@ -154,10 +154,10 @@ public class InvisibleElementRemovalService {
|
|||||||
private void execute(PDFDoc pdfDoc, boolean delta, boolean removePaths, Set<String> markedContentToIgnore) {
|
private void execute(PDFDoc pdfDoc, boolean delta, boolean removePaths, Set<String> markedContentToIgnore) {
|
||||||
|
|
||||||
log.info("Start removing invisible Elements");
|
log.info("Start removing invisible Elements");
|
||||||
try (ElementWriter writer = new ElementWriter(); ElementReader reader = new ElementReader()) {
|
try (PageIterator iterator = pdfDoc.getPageIterator(); ElementWriter writer = new ElementWriter(); ElementReader reader = new ElementReader()) {
|
||||||
Set<Long> visitedXObjIds = new TreeSet<>();
|
Set<Long> visitedXObjIds = new TreeSet<>();
|
||||||
|
|
||||||
for (PageIterator iterator = pdfDoc.getPageIterator(); iterator.hasNext(); ) {
|
while (iterator.hasNext()) {
|
||||||
|
|
||||||
Page page = iterator.next();
|
Page page = iterator.next();
|
||||||
|
|
||||||
@ -182,6 +182,7 @@ public class InvisibleElementRemovalService {
|
|||||||
|
|
||||||
removeOverlappedElements(page, writer, context);
|
removeOverlappedElements(page, writer, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
log.info("Finished removing invisible Elements");
|
log.info("Finished removing invisible Elements");
|
||||||
}
|
}
|
||||||
@ -250,9 +251,8 @@ public class InvisibleElementRemovalService {
|
|||||||
if (!(context.markedContentStack.contextHasTransparency()
|
if (!(context.markedContentStack.contextHasTransparency()
|
||||||
|| imageFeatures.isTransparent()
|
|| imageFeatures.isTransparent()
|
||||||
|| imageFeatures.isImageMask()
|
|| imageFeatures.isImageMask()
|
||||||
|| imageFeatures.isSoftMask()
|
|| imageFeatures.isSoftMask())) {
|
||||||
|| imageFeatures.isMasked())) {
|
calculateOverlaps(context, imageFeatures, imageFeatures.isMasked());
|
||||||
calculateOverlaps(context, imageFeatures);
|
|
||||||
}
|
}
|
||||||
context.visibleElements().add(imageFeatures);
|
context.visibleElements().add(imageFeatures);
|
||||||
}
|
}
|
||||||
@ -387,7 +387,7 @@ public class InvisibleElementRemovalService {
|
|||||||
|
|
||||||
if (inClippingPath) {
|
if (inClippingPath) {
|
||||||
if (!context.markedContentStack.contextHasTransparency() && isFilledAndNonTransparent(pathElement)) {
|
if (!context.markedContentStack.contextHasTransparency() && isFilledAndNonTransparent(pathElement)) {
|
||||||
calculateOverlaps(context, pathFeatures);
|
calculateOverlaps(context, pathFeatures, false);
|
||||||
}
|
}
|
||||||
context.visibleElements().add(ElementFeatureFactory.extractFeatures(pathElement));
|
context.visibleElements().add(ElementFeatureFactory.extractFeatures(pathElement));
|
||||||
}
|
}
|
||||||
@ -410,9 +410,9 @@ public class InvisibleElementRemovalService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void calculateOverlaps(InvisibleElementRemovalContext context, ElementFeatures elementFeatures) {
|
private void calculateOverlaps(InvisibleElementRemovalContext context, ElementFeatures elementFeatures, boolean textOnly) {
|
||||||
|
|
||||||
List<ElementFeatures> currentOverlappedElements = context.visibleElements().findOverlapped(elementFeatures);
|
List<ElementFeatures> currentOverlappedElements = context.visibleElements().findOverlapped(elementFeatures, textOnly);
|
||||||
context.overlappedElements().addAll(currentOverlappedElements);
|
context.overlappedElements().addAll(currentOverlappedElements);
|
||||||
context.visibleElements().removeAll(currentOverlappedElements);
|
context.visibleElements().removeAll(currentOverlappedElements);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import com.pdftron.pdf.*;
|
|||||||
import com.pdftron.pdf.ocg.Group;
|
import com.pdftron.pdf.ocg.Group;
|
||||||
import com.pdftron.pdf.ocg.OCMD;
|
import com.pdftron.pdf.ocg.OCMD;
|
||||||
import com.pdftron.sdf.Obj;
|
import com.pdftron.sdf.Obj;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -27,8 +28,9 @@ public class OCGWatermarkRemovalService {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private boolean hasOCGWatermarks(PDFDoc pdfDoc) {
|
private boolean hasOCGWatermarks(PDFDoc pdfDoc) {
|
||||||
|
|
||||||
Obj ocgs = pdfDoc.getOCGs();
|
Obj ocgs = pdfDoc.getOCGs();
|
||||||
if(ocgs != null) {
|
if (ocgs != null) {
|
||||||
for (int i = 0; i < ocgs.size(); i++) {
|
for (int i = 0; i < ocgs.size(); i++) {
|
||||||
Group group = new Group(ocgs.getAt(i));
|
Group group = new Group(ocgs.getAt(i));
|
||||||
if (group.isValid() && group.getName().equals("Watermark")) {
|
if (group.isValid() && group.getName().equals("Watermark")) {
|
||||||
@ -43,24 +45,21 @@ public class OCGWatermarkRemovalService {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void removeOCGWatermarks(PDFDoc pdfDoc) {
|
private void removeOCGWatermarks(PDFDoc pdfDoc) {
|
||||||
|
|
||||||
try(ElementReader reader = new ElementReader();
|
try (PageIterator iterator = pdfDoc.getPageIterator(); ElementReader reader = new ElementReader(); ElementWriter writer = new ElementWriter()) {
|
||||||
ElementWriter writer = new ElementWriter()) {
|
|
||||||
Set<Long> visitedXObjIds = new TreeSet<>();
|
Set<Long> visitedXObjIds = new TreeSet<>();
|
||||||
|
|
||||||
for (PageIterator iterator = pdfDoc.getPageIterator(); iterator.hasNext(); ) {
|
while (iterator.hasNext()) {
|
||||||
|
|
||||||
Page page = iterator.next();
|
Page page = iterator.next();
|
||||||
writeAllElementsExceptWatermarks(page, reader, writer, visitedXObjIds);
|
writeAllElementsExceptWatermarks(page, reader, writer, visitedXObjIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void writeAllElementsExceptWatermarks(Page page,
|
private void writeAllElementsExceptWatermarks(Page page, ElementReader reader, ElementWriter writer, Set<Long> visitedXObjIds) {
|
||||||
ElementReader reader,
|
|
||||||
ElementWriter writer,
|
|
||||||
Set<Long> visitedXObjIds) {
|
|
||||||
|
|
||||||
reader.begin(page);
|
reader.begin(page);
|
||||||
writer.begin(page, ElementWriter.e_replacement, false, true, page.getResourceDict());
|
writer.begin(page, ElementWriter.e_replacement, false, true, page.getResourceDict());
|
||||||
@ -70,10 +69,7 @@ public class OCGWatermarkRemovalService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void processElements(Page page,
|
private void processElements(Page page, ElementReader reader, ElementWriter writer, Set<Long> visitedXObjIds) throws PDFNetException {
|
||||||
ElementReader reader,
|
|
||||||
ElementWriter writer,
|
|
||||||
Set<Long> visitedXObjIds) throws PDFNetException {
|
|
||||||
|
|
||||||
for (Element element = reader.next(); element != null; element = reader.next()) {
|
for (Element element = reader.next(); element != null; element = reader.next()) {
|
||||||
|
|
||||||
@ -91,6 +87,7 @@ public class OCGWatermarkRemovalService {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private boolean inOCGWatermark(Element element) {
|
private boolean inOCGWatermark(Element element) {
|
||||||
|
|
||||||
var xObj = element.getXObject();
|
var xObj = element.getXObject();
|
||||||
if (xObj != null) {
|
if (xObj != null) {
|
||||||
Obj oc = xObj.findObj("OC");
|
Obj oc = xObj.findObj("OC");
|
||||||
@ -109,12 +106,7 @@ public class OCGWatermarkRemovalService {
|
|||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void processForms(Page page,
|
private void processForms(Page page, Element element, ElementReader reader, ElementWriter writer, Set<Long> visitedXObjIds) {
|
||||||
Element element,
|
|
||||||
ElementReader reader,
|
|
||||||
ElementWriter writer,
|
|
||||||
Set<Long> visitedXObjIds) {
|
|
||||||
|
|
||||||
|
|
||||||
writer.writeElement(element);
|
writer.writeElement(element);
|
||||||
|
|
||||||
@ -122,7 +114,7 @@ public class OCGWatermarkRemovalService {
|
|||||||
visitedXObjIds.add(element.getXObject().getObjNum());
|
visitedXObjIds.add(element.getXObject().getObjNum());
|
||||||
// writer needs to be newly initialized when entering a new content stream
|
// writer needs to be newly initialized when entering a new content stream
|
||||||
// see ElementEditTest in PDFTron (https://www.pdftron.com/documentation/samples/android/java/ElementEditTest)
|
// see ElementEditTest in PDFTron (https://www.pdftron.com/documentation/samples/android/java/ElementEditTest)
|
||||||
try(ElementWriter formWriter = new ElementWriter()) {
|
try (ElementWriter formWriter = new ElementWriter()) {
|
||||||
reader.formBegin();
|
reader.formBegin();
|
||||||
formWriter.begin(element.getXObject());
|
formWriter.begin(element.getXObject());
|
||||||
|
|
||||||
|
|||||||
@ -26,10 +26,9 @@ public class PdfTextExtraction {
|
|||||||
|
|
||||||
private static String execute(PDFDoc pdfDoc) throws PDFNetException {
|
private static String execute(PDFDoc pdfDoc) throws PDFNetException {
|
||||||
|
|
||||||
try (TextExtractor extractor = new TextExtractor()) {
|
try (PageIterator iterator = pdfDoc.getPageIterator(); TextExtractor extractor = new TextExtractor()) {
|
||||||
List<String> texts = new ArrayList<>();
|
List<String> texts = new ArrayList<>();
|
||||||
|
|
||||||
PageIterator iterator = pdfDoc.getPageIterator();
|
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
Page page = iterator.next();
|
Page page = iterator.next();
|
||||||
extractor.begin(page);
|
extractor.begin(page);
|
||||||
|
|||||||
@ -95,9 +95,8 @@ public class WatermarkRemovalService {
|
|||||||
Map<Long, List<ElementFeatures>> formObjectsAndImagesForPages = new HashMap<>();
|
Map<Long, List<ElementFeatures>> formObjectsAndImagesForPages = new HashMap<>();
|
||||||
Set<Long> visitedXObjIds = new TreeSet<>();
|
Set<Long> visitedXObjIds = new TreeSet<>();
|
||||||
|
|
||||||
try (ElementReader reader = new ElementReader()) {
|
try (ElementReader reader = new ElementReader(); PageIterator iterator = pdfDoc.getPageIterator()) {
|
||||||
|
while (iterator.hasNext()) {
|
||||||
for (PageIterator iterator = pdfDoc.getPageIterator(); iterator.hasNext(); ) {
|
|
||||||
|
|
||||||
Page page = iterator.next();
|
Page page = iterator.next();
|
||||||
|
|
||||||
@ -270,10 +269,10 @@ public class WatermarkRemovalService {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void removeAllWatermarks(PDFDoc pdfDoc, List<ElementFeatures> watermarksElementFeaturesList) {
|
private void removeAllWatermarks(PDFDoc pdfDoc, List<ElementFeatures> watermarksElementFeaturesList) {
|
||||||
|
|
||||||
try (ElementReader reader = new ElementReader(); ElementWriter writer = new ElementWriter()) {
|
try (PageIterator iterator = pdfDoc.getPageIterator(); ElementReader reader = new ElementReader(); ElementWriter writer = new ElementWriter()) {
|
||||||
Set<Long> visitedXObjIds = new TreeSet<>();
|
Set<Long> visitedXObjIds = new TreeSet<>();
|
||||||
|
|
||||||
for (PageIterator iterator = pdfDoc.getPageIterator(); iterator.hasNext(); ) {
|
while (iterator.hasNext()) {
|
||||||
|
|
||||||
Page page = iterator.next();
|
Page page = iterator.next();
|
||||||
|
|
||||||
|
|||||||
@ -158,33 +158,34 @@ public class ElementFeatureFactory {
|
|||||||
|
|
||||||
List<GlyphInfo> glyphs = new ArrayList<>();
|
List<GlyphInfo> glyphs = new ArrayList<>();
|
||||||
|
|
||||||
CharIterator charIterator = textElement.getCharIterator();
|
try (CharIterator charIterator = textElement.getCharIterator()) {
|
||||||
while (charIterator.hasNext()) {
|
while (charIterator.hasNext()) {
|
||||||
CharData charData = charIterator.next();
|
CharData charData = charIterator.next();
|
||||||
long charCode = charData.getCharCode();
|
long charCode = charData.getCharCode();
|
||||||
String glyphText = new String(font.mapToUnicode(charCode));
|
String glyphText = new String(font.mapToUnicode(charCode));
|
||||||
|
|
||||||
if (Character.isWhitespace(glyphText.charAt(0))) {
|
if (Character.isWhitespace(glyphText.charAt(0))) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Matrix2D fontMatrix = computeFontMatrix(charData, textElement, font); //
|
|
||||||
Matrix2D glyphMatrix = textElement.getCTM()//
|
|
||||||
.multiply(textElement.getTextMatrix())//
|
|
||||||
.multiply(fontMatrix)) {
|
|
||||||
PathData pathData = font.getGlyphPath(charCode, true, glyphMatrix);
|
|
||||||
if (pathData.getOperators().length == 1 && pathData.getOperators()[0] == 6) {
|
|
||||||
// This happens for some chinese characters or whitespaces, don't know why...
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
GeneralPath glyphPath = Converter.convertToGeneralPath(pathData);
|
|
||||||
GlyphInfo.GlyphInfoBuilder glyphInfo = GlyphInfo.builder().unicode(glyphText).bbox(glyphPath.getBounds2D());
|
|
||||||
|
|
||||||
if (includePathData) {
|
try (Matrix2D fontMatrix = computeFontMatrix(charData, textElement, font); //
|
||||||
glyphInfo.pathData(pathData);
|
Matrix2D glyphMatrix = textElement.getCTM()//
|
||||||
|
.multiply(textElement.getTextMatrix())//
|
||||||
|
.multiply(fontMatrix)) {
|
||||||
|
PathData pathData = font.getGlyphPath(charCode, true, glyphMatrix);
|
||||||
|
if (pathData.getOperators().length == 1 && pathData.getOperators()[0] == 6) {
|
||||||
|
// This happens for some chinese characters or whitespaces, don't know why...
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
GeneralPath glyphPath = Converter.convertToGeneralPath(pathData);
|
||||||
|
GlyphInfo.GlyphInfoBuilder glyphInfo = GlyphInfo.builder().unicode(glyphText).bbox(glyphPath.getBounds2D());
|
||||||
|
|
||||||
|
if (includePathData) {
|
||||||
|
glyphInfo.pathData(pathData);
|
||||||
|
}
|
||||||
|
|
||||||
|
glyphs.add(glyphInfo.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
glyphs.add(glyphInfo.build());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,9 +67,9 @@ public class ElementFeatureLookup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<ElementFeatures> findOverlapped(ElementFeatures overlappingElement) {
|
public List<ElementFeatures> findOverlapped(ElementFeatures overlappingElement, boolean textOnly) {
|
||||||
|
|
||||||
OverlapVisitor overlapVisitor = new OverlapVisitor(overlappingElement);
|
OverlapVisitor overlapVisitor = new OverlapVisitor(overlappingElement, textOnly);
|
||||||
forEach(overlapVisitor::visitItem);
|
forEach(overlapVisitor::visitItem);
|
||||||
return overlapVisitor.getOverlappedElementFeatures();
|
return overlapVisitor.getOverlappedElementFeatures();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import com.iqser.red.pdftronlogic.commons.ComparisonUtils;
|
import com.iqser.red.pdftronlogic.commons.ComparisonUtils;
|
||||||
import com.iqser.red.pdftronlogic.commons.features.ElementFeatures;
|
import com.iqser.red.pdftronlogic.commons.features.ElementFeatures;
|
||||||
|
import com.pdftron.pdf.Element;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -17,6 +18,8 @@ public class OverlapVisitor implements ElementFeatureVisitor {
|
|||||||
|
|
||||||
ElementFeatures overlappingElement;
|
ElementFeatures overlappingElement;
|
||||||
|
|
||||||
|
boolean textOnly;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
List<ElementFeatures> overlappedElementFeatures = new LinkedList<>();
|
List<ElementFeatures> overlappedElementFeatures = new LinkedList<>();
|
||||||
|
|
||||||
@ -24,6 +27,10 @@ public class OverlapVisitor implements ElementFeatureVisitor {
|
|||||||
@Override
|
@Override
|
||||||
public void visitItem(ElementFeatures features) {
|
public void visitItem(ElementFeatures features) {
|
||||||
|
|
||||||
|
if (textOnly && features.getElementType() != Element.e_text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ComparisonUtils.padRectangle(features.getBoundingBox()).intersects(ComparisonUtils.padRectangle(overlappingElement.getBoundingBox()))) {
|
if (ComparisonUtils.padRectangle(features.getBoundingBox()).intersects(ComparisonUtils.padRectangle(overlappingElement.getBoundingBox()))) {
|
||||||
if (features.testOverlapped(overlappingElement)) {
|
if (features.testOverlapped(overlappingElement)) {
|
||||||
overlappedElementFeatures.add(features);
|
overlappedElementFeatures.add(features);
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import java.io.FileInputStream;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -24,11 +23,8 @@ import com.iqser.red.pdftronlogic.commons.rendering.ImageFile;
|
|||||||
import com.pdftron.pdf.PDFDoc;
|
import com.pdftron.pdf.PDFDoc;
|
||||||
import com.pdftron.pdf.PDFNet;
|
import com.pdftron.pdf.PDFNet;
|
||||||
import com.pdftron.sdf.SDFDoc;
|
import com.pdftron.sdf.SDFDoc;
|
||||||
import com.sun.jna.Memory;
|
|
||||||
import com.sun.jna.Native;
|
|
||||||
import com.sun.jna.NativeLibrary;
|
import com.sun.jna.NativeLibrary;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
import com.sun.jna.ptr.PointerByReference;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import net.sourceforge.lept4j.Box;
|
import net.sourceforge.lept4j.Box;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user