RED-7074: Design Subsection section tree structure algorithm

* first draft: further implementations
This commit is contained in:
maverickstuder 2024-04-18 17:52:33 +02:00
parent e9d1bdc94f
commit 77ee8dd5bd
7 changed files with 272 additions and 108 deletions

View File

@ -282,13 +282,6 @@ public class LayoutParsingPipeline {
case CLARIFYND, CLARIFYND_PARAGRAPH_DEBUG -> docstrumBlockificationService.blockify(stripper.getTextPositionSequences(), emptyTableCells, false); case CLARIFYND, CLARIFYND_PARAGRAPH_DEBUG -> docstrumBlockificationService.blockify(stripper.getTextPositionSequences(), emptyTableCells, false);
}; };
List<OutlineObject> outlineObjects = classificationDocument.getOutlineObjectTree().getOutlineObjectsPerPage()
.get(pageNumber - 1);
if (outlineObjects != null) {
classificationPage.setOutlineObjects(outlineObjects);
blockificationPostprocessingService.sanitizeOutlineBlocksWithKdTree(classificationPage);
}
classificationPage.setCleanRulings(cleanRulings); classificationPage.setCleanRulings(cleanRulings);
classificationPage.setRotation(rotation); classificationPage.setRotation(rotation);
classificationPage.setLandscape(isLandscape); classificationPage.setLandscape(isLandscape);
@ -296,6 +289,13 @@ public class LayoutParsingPipeline {
classificationPage.setPageWidth(cropbox.getWidth()); classificationPage.setPageWidth(cropbox.getWidth());
classificationPage.setPageHeight(cropbox.getHeight()); classificationPage.setPageHeight(cropbox.getHeight());
List<OutlineObject> outlineObjects = classificationDocument.getOutlineObjectTree().getOutlineObjectsPerPage()
.get(pageNumber - 1);
if (outlineObjects != null) {
classificationPage.setOutlineObjects(outlineObjects);
blockificationPostprocessingService.sanitizeOutlineBlocks(classificationPage);
}
// MarkedContent needs to be converted at this point, otherwise it leads to GC Problems in Pdfbox. // MarkedContent needs to be converted at this point, otherwise it leads to GC Problems in Pdfbox.
classificationPage.setMarkedContentBboxPerType(convertMarkedContents(stripper.getMarkedContents())); classificationPage.setMarkedContentBboxPerType(convertMarkedContents(stripper.getMarkedContents()));

View File

@ -52,9 +52,10 @@ public class OutlineValidationService {
private void addItemAtCorrectPosition(TableOfContents toc, TableOfContentItem tocItem, TableOfContentItem lastHeadlineFromOutlines) { private void addItemAtCorrectPosition(TableOfContents toc, TableOfContentItem tocItem, TableOfContentItem lastHeadlineFromOutlines) {
if(!tocItem.getChildren().isEmpty()) { //if (lastHeadlineFromOutlines == null || tocItem.g)
//if(!tocItem.getChildren().isEmpty()) {
} //
//}
} }
public TableOfContents createToC(List<TextPageBlock> headlines) { public TableOfContents createToC(List<TextPageBlock> headlines) {

View File

@ -65,14 +65,14 @@ public class TextPageBlock extends AbstractPageBlock {
@JsonIgnore @JsonIgnore
private float getPageHeight() { public float getPageHeight() {
return sequences.get(0).getPageHeight(); return sequences.get(0).getPageHeight();
} }
@JsonIgnore @JsonIgnore
private float getPageWidth() { public float getPageWidth() {
return sequences.get(0).getPageWidth(); return sequences.get(0).getPageWidth();
} }

View File

@ -4,7 +4,10 @@ import static com.knecon.fforesight.service.layoutparser.processor.services.bloc
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.function.Function; import java.util.function.Function;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -36,39 +39,9 @@ public class BlockificationPostprocessingService {
.collect(RectangleTransformations.collectBBox()); .collect(RectangleTransformations.collectBBox());
public void sanitizeOutlineBlocksWithKdTree(ClassificationPage classificationPage) { public void sanitizeOutlineBlocks(ClassificationPage classificationPage) {
List<OutlineObject> outlineObjects = classificationPage.getOutlineObjects(); List<OutlineObject> outlineObjects = classificationPage.getOutlineObjects();
if (classificationPage.getTextBlocks().isEmpty() || outlineObjects.isEmpty()) {
return;
}
KDTree<TextPageBlock> kdTree = createKdTree(classificationPage);
for (OutlineObject outlineObject : outlineObjects) {
KDIterator<TextPageBlock> successorIterator = kdTree.query(new double[]{ //
0, //
outlineObject.getPoint().getY() - BLOCK_TO_OUTLINE_DISTANCE_THRESHOLD //
}, //
new double[]{Double.MAX_VALUE, Double.MAX_VALUE});
boolean matchedExactly = false;
OutlineProcessionContext context = new OutlineProcessionContext(outlineObject);
while (successorIterator.hasNext() && !matchedExactly) {
TextPageBlock pageBlock = successorIterator.next().value();
matchedExactly = processOutlineForTextBlock(pageBlock, context);
}
if (!matchedExactly) {
selectMatch(classificationPage, kdTree, context);
}
}
}
private static KDTree<TextPageBlock> createKdTree(ClassificationPage classificationPage) {
List<TextPageBlock> textBlocks = classificationPage.getTextBlocks() List<TextPageBlock> textBlocks = classificationPage.getTextBlocks()
.stream() .stream()
@ -78,97 +51,149 @@ public class BlockificationPostprocessingService {
.map(block -> (TextPageBlock) block) .map(block -> (TextPageBlock) block)
.toList(); .toList();
KDTree<TextPageBlock> kdTree = KDTree.create(2); if (textBlocks.isEmpty() || outlineObjects.isEmpty()) {
textBlocks.forEach(block -> { return;
var boundingBox = blockToBoundingBox.apply(block); }
kdTree.insert(new double[]{boundingBox.getMinX(), boundingBox.getMaxY()}, block);
}); float pageHeight = classificationPage.getPageHeight();
return kdTree;
for (OutlineObject outlineObject : outlineObjects) {
OutlineProcessionContext context = new OutlineProcessionContext(outlineObject);
ListIterator<TextPageBlock> iterator = textBlocks.listIterator();
while (iterator.hasNext()) {
TextPageBlock pageBlock = iterator.next();
if (pageHeight - outlineObject.getPoint().getY() - BLOCK_TO_OUTLINE_DISTANCE_THRESHOLD <= pageBlock.getMaxY()) {
break;
}
}
if (iterator.hasPrevious()) {
iterator.previous();
}
boolean earlyStop = false;
while (iterator.hasNext() && !earlyStop) {
TextPageBlock pageBlock = iterator.next();
earlyStop = processOutlineForTextBlock(pageBlock, context);
}
selectMatch(classificationPage, context);
}
} }
private void selectMatch(ClassificationPage classificationPage, KDTree<TextPageBlock> kdTree, OutlineProcessionContext context) { private void selectMatch(ClassificationPage classificationPage, OutlineProcessionContext context) {
OutlineObject outlineObject = context.outlineObject; OutlineObject outlineObject = context.outlineObject;
TextPageBlock directMatch = context.directMatch;
List<TextPageBlock> mergeCandidates = context.mergeCandidates; List<TextPageBlock> mergeCandidates = context.mergeCandidates;
TextPageBlock splitCandidate = context.splitCandidate; TextPageBlock splitCandidate = context.splitCandidate;
PageBlockType headlineType = PageBlockType.getHeadlineType(outlineObject.getTreeDepth()); PageBlockType headlineType = PageBlockType.getHeadlineType(outlineObject.getTreeDepth());
double distanceToDirectMatch = directMatch != null ? calculateDistance(outlineObject, directMatch) : Double.MAX_VALUE;
double distanceToSplitCandidate = splitCandidate != null ? calculateDistance(outlineObject, splitCandidate) : Double.MAX_VALUE;
double distanceToBestMergeCandidates = Double.MAX_VALUE;
List<TextPageBlock> bestMergeCandidateCombination = new ArrayList<>();
if (!mergeCandidates.isEmpty()) { if (!mergeCandidates.isEmpty()) {
List<TextPageBlock> allMergeCandidates = new ArrayList<>(mergeCandidates); // with this code adjacent blocks to the first and last merge candidate get added, this could be useful for some edge cases:
addNeighborsOfCandidate(kdTree, mergeCandidates.get(0), allMergeCandidates); //List<TextPageBlock> allMergeCandidates = new ArrayList<>(mergeCandidates);
if (mergeCandidates.size() > 1) { //addNeighborsOfCandidate(kdTree, mergeCandidates.get(0), allMergeCandidates);
addNeighborsOfCandidate(kdTree, mergeCandidates.get(mergeCandidates.size() - 1), allMergeCandidates); //if (mergeCandidates.size() > 1) {
} // addNeighborsOfCandidate(kdTree, mergeCandidates.get(mergeCandidates.size() - 1), allMergeCandidates);
allMergeCandidates = allMergeCandidates.stream() //}
.distinct() //allMergeCandidates = allMergeCandidates.stream()
.toList(); // .distinct()
// .toList();
List<List<TextPageBlock>> combinations = findCombinations(outlineObject.getTitle(), mergeCandidates);
List<List<TextPageBlock>> combinations = findCombinations(outlineObject.getTitle(), allMergeCandidates);
double maxDistance = Double.MAX_VALUE;
List<TextPageBlock> bestCombination = new ArrayList<>();
for (List<TextPageBlock> combination : combinations) { for (List<TextPageBlock> combination : combinations) {
double averageDistance = combination.stream() double averageDistance = combination.stream()
.map(block -> calculateDistance(outlineObject, block)) .map(block -> calculateDistance(outlineObject, block))
.mapToDouble(Double::doubleValue).average() .mapToDouble(Double::doubleValue).average()
.orElse(Double.MAX_VALUE); .orElse(Double.MAX_VALUE);
if (maxDistance > averageDistance) { if (distanceToBestMergeCandidates > averageDistance) {
maxDistance = averageDistance; distanceToBestMergeCandidates = averageDistance;
bestCombination = combination; bestMergeCandidateCombination = combination;
} }
} }
var merged = mergeBlocks(classificationPage, bestCombination); }
double minDistance = Math.min(distanceToDirectMatch, Math.min(distanceToSplitCandidate, distanceToBestMergeCandidates));
if(minDistance == Double.MAX_VALUE) {
return;
}
if (minDistance == distanceToDirectMatch) {
directMatch.setClassification(headlineType);
} else if (minDistance == distanceToSplitCandidate) {
List<TextPageBlock> others = splitBlock(classificationPage, splitCandidate, outlineObject.getTitle());
splitCandidate.setClassification(headlineType);
others.forEach(other -> other.setClassification(headlineType));
} else {
var merged = mergeBlocks(classificationPage, bestMergeCandidateCombination);
merged.setClassification(headlineType); merged.setClassification(headlineType);
} }
}
if (splitCandidate != null) {
TextPageBlock other = splitBlock(classificationPage, splitCandidate, outlineObject.getTitle()); private List<TextPageBlock> splitBlock(ClassificationPage classificationPage, TextPageBlock blockToSplit, String text) {
splitCandidate.setClassification(headlineType);
other.setClassification(headlineType); List<TextPageBlock> otherBlocks = new ArrayList<>();
int blockToSplitIdx = classificationPage.getTextBlocks().indexOf(blockToSplit);
WordSequenceResult wordSequenceResult = findWordSequence(blockToSplit.getSequences(), text);
List<TextPositionSequence> postSequence = blockToSplit.getSequences();
postSequence.removeAll(wordSequenceResult.inSequence);
postSequence.removeAll(wordSequenceResult.preSequence);
blockToSplit.setSequences(wordSequenceResult.inSequence);
if (!wordSequenceResult.preSequence.isEmpty()) {
TextPageBlock block = buildTextBlock(wordSequenceResult.preSequence, 0);
classificationPage.getTextBlocks().add(blockToSplitIdx, block);
otherBlocks.add(block);
blockToSplitIdx++;
} }
if (!postSequence.isEmpty()) {
TextPageBlock block = buildTextBlock(postSequence, 0);
classificationPage.getTextBlocks().add(blockToSplitIdx + 1, block);
otherBlocks.add(block);
}
return otherBlocks;
} }
private TextPageBlock splitBlock(ClassificationPage classificationPage, TextPageBlock blockToSplit, String text) { private static WordSequenceResult findWordSequence(List<TextPositionSequence> textPositionSequences, String text) {
List<TextPositionSequence> wordSequence = findWordSequence(blockToSplit.getSequences(), text); String target = sanitizeString(text);
List<TextPositionSequence> remaining = blockToSplit.getSequences();
remaining.removeAll(wordSequence);
blockToSplit.setSequences(wordSequence);
TextPageBlock other = buildTextBlock(remaining, 0);
classificationPage.getTextBlocks().add(other);
return other;
}
private static List<TextPositionSequence> findWordSequence(List<TextPositionSequence> textPositionSequences, String text) {
String target = text.replaceAll("\\s", "");
List<TextPositionSequence> inSequence = new ArrayList<>(); List<TextPositionSequence> inSequence = new ArrayList<>();
List<TextPositionSequence> preSequence = new ArrayList<>();
StringBuilder currentSequence = new StringBuilder(); StringBuilder currentSequence = new StringBuilder();
for (TextPositionSequence sequence : textPositionSequences) { for (TextPositionSequence sequence : textPositionSequences) {
if (currentSequence.toString().equals(target)) { currentSequence.append(sanitizeString(sequence.toString()));
return inSequence;
}
currentSequence.append(sequence.toString());
inSequence.add(sequence); inSequence.add(sequence);
if (currentSequence.length() > target.length()) { if (currentSequence.length() > target.length()) {
TextPositionSequence removed = inSequence.remove(0); TextPositionSequence removed = inSequence.remove(0);
currentSequence.delete(0, removed.toString().length()); currentSequence.delete(0, removed.toString().length());
preSequence.add(removed);
while (currentSequence.length() > target.length()) { while (currentSequence.length() > target.length()) {
removed = inSequence.remove(0); removed = inSequence.remove(0);
currentSequence.delete(0, removed.toString().length()); currentSequence.delete(0, removed.toString().length());
preSequence.add(removed);
} }
} }
if (currentSequence.toString().equals(target)) {
return new WordSequenceResult(inSequence, preSequence);
}
} }
return new ArrayList<>(); return new WordSequenceResult(new ArrayList<>(), new ArrayList<>());
} }
@ -209,7 +234,7 @@ public class BlockificationPostprocessingService {
private static void findCombinations(String title, List<TextPageBlock> blocks, List<TextPageBlock> current, List<List<TextPageBlock>> combinations) { private static void findCombinations(String title, List<TextPageBlock> blocks, List<TextPageBlock> current, List<List<TextPageBlock>> combinations) {
String target = title.replaceAll("\\s", ""); String target = sanitizeString(title);
if (target.isEmpty()) { if (target.isEmpty()) {
combinations.add(new ArrayList<>(current)); combinations.add(new ArrayList<>(current));
return; return;
@ -219,10 +244,10 @@ public class BlockificationPostprocessingService {
.filter(block -> !current.contains(block)) .filter(block -> !current.contains(block))
.toList(); .toList();
for (TextPageBlock block : remaining) { for (TextPageBlock block : remaining) {
String prefix = block.getText().replaceAll("\\s", ""); String prefix = sanitizeString(block.getText());
if (target.startsWith(prefix)) { if (target.startsWith(prefix)) {
current.add(block); current.add(block);
findCombinations(target.substring(prefix.length()), blocks, current, combinations); findCombinations(target.substring(prefix.length()), blocks.subList(blocks.indexOf(block) + 1, blocks.size()), current, combinations);
current.remove(current.size() - 1); current.remove(current.size() - 1);
} }
} }
@ -232,7 +257,7 @@ public class BlockificationPostprocessingService {
private double calculateDistance(OutlineObject outlineObject, TextPageBlock pageBlock) { private double calculateDistance(OutlineObject outlineObject, TextPageBlock pageBlock) {
double deltaX = outlineObject.getPoint().getX() - pageBlock.getMinX(); double deltaX = outlineObject.getPoint().getX() - pageBlock.getMinX();
double deltaY = outlineObject.getPoint().getY() - pageBlock.getMinY(); double deltaY = pageBlock.getPageHeight() - outlineObject.getPoint().getY() - pageBlock.getMinY();
return Math.sqrt(deltaX * deltaX + deltaY * deltaY); return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
} }
@ -255,8 +280,8 @@ public class BlockificationPostprocessingService {
private boolean processOutlineForTextBlock(TextPageBlock pageBlock, OutlineProcessionContext context) { private boolean processOutlineForTextBlock(TextPageBlock pageBlock, OutlineProcessionContext context) {
OutlineObject outlineObject = context.getOutlineObject(); OutlineObject outlineObject = context.getOutlineObject();
String blockText = pageBlock.getText(); String blockText = sanitizeString(pageBlock.getText());
String outlineTitle = outlineObject.getTitle(); String outlineTitle = sanitizeString(outlineObject.getTitle());
boolean blockTextContainsOutlineTitle = blockText.contains(outlineTitle); boolean blockTextContainsOutlineTitle = blockText.contains(outlineTitle);
boolean outlineTitleContainsBlockText = outlineTitle.contains(blockText); boolean outlineTitleContainsBlockText = outlineTitle.contains(blockText);
@ -265,8 +290,8 @@ public class BlockificationPostprocessingService {
return false; return false;
} }
if (blockText.equals(outlineTitle)) { if (blockText.equals(outlineTitle) && context.directMatch == null) {
pageBlock.setClassification(PageBlockType.getHeadlineType(outlineObject.getTreeDepth())); context.directMatch = pageBlock;
return true; return true;
} }
@ -274,17 +299,27 @@ public class BlockificationPostprocessingService {
context.mergeCandidates.add(pageBlock); context.mergeCandidates.add(pageBlock);
} }
if (blockTextContainsOutlineTitle && context.splitCandidate != null) { if (blockTextContainsOutlineTitle && context.splitCandidate == null) {
context.splitCandidate = pageBlock; context.splitCandidate = pageBlock;
} }
return false; return false;
} }
private static String sanitizeString(String text) {
return text.replaceAll("\\s", "").toLowerCase(Locale.ROOT);
}
private record WordSequenceResult(List<TextPositionSequence> inSequence, List<TextPositionSequence> preSequence) {
}
@Data @Data
private static class OutlineProcessionContext { private static class OutlineProcessionContext {
private TextPageBlock directMatch;
private OutlineObject outlineObject; private OutlineObject outlineObject;
private List<TextPageBlock> mergeCandidates; private List<TextPageBlock> mergeCandidates;
private TextPageBlock splitCandidate; private TextPageBlock splitCandidate;
@ -293,10 +328,65 @@ public class BlockificationPostprocessingService {
public OutlineProcessionContext(OutlineObject outlineObject) { public OutlineProcessionContext(OutlineObject outlineObject) {
this.outlineObject = outlineObject; this.outlineObject = outlineObject;
this.directMatch = null;
this.mergeCandidates = new ArrayList<>(); this.mergeCandidates = new ArrayList<>();
this.splitCandidate = null; this.splitCandidate = null;
} }
} }
@Deprecated
public void sanitizeOutlineBlocksWithKdTree(ClassificationPage classificationPage) {
List<OutlineObject> outlineObjects = classificationPage.getOutlineObjects();
if (classificationPage.getTextBlocks().isEmpty() || outlineObjects.isEmpty()) {
return;
}
KDTree<TextPageBlock> kdTree = createKdTree(classificationPage);
float pageHeight = classificationPage.getPageHeight();
for (OutlineObject outlineObject : outlineObjects) {
// kd tree contains yx coordinates
KDIterator<TextPageBlock> successorIterator = kdTree.query(new double[]{ //
pageHeight - outlineObject.getPoint().getY() - BLOCK_TO_OUTLINE_DISTANCE_THRESHOLD, 0, //
//
}, //
new double[]{Double.MAX_VALUE, Double.MAX_VALUE});
OutlineProcessionContext context = new OutlineProcessionContext(outlineObject);
boolean earlyStop = false;
while (successorIterator.hasNext() && !earlyStop) {
TextPageBlock pageBlock = successorIterator.next().value();
earlyStop = processOutlineForTextBlock(pageBlock, context);
processOutlineForTextBlock(pageBlock, context);
}
selectMatch(classificationPage, context);
}
}
@Deprecated
private static KDTree<TextPageBlock> createKdTree(ClassificationPage classificationPage) {
List<TextPageBlock> textBlocks = classificationPage.getTextBlocks()
.stream()
.filter(block -> block instanceof TextPageBlock)
.toList()
.stream()
.map(block -> (TextPageBlock) block)
.toList();
KDTree<TextPageBlock> kdTree = KDTree.create(2);
// insert y first then x, use pdf max y so that the page height is subtracted so that the order is inverted
textBlocks.forEach(block -> {
//var boundingBox = blockToBoundingBox.apply(block);
kdTree.insert(new double[]{block.getMinY(), block.getMinX()}, block);
});
return kdTree;
}
} }

View File

@ -17,6 +17,7 @@ import com.knecon.fforesight.service.layoutparser.processor.model.PageBlockType;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock; import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.utils.PositionUtils; import com.knecon.fforesight.service.layoutparser.processor.utils.PositionUtils;
import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -42,15 +43,17 @@ public class RedactManagerClassificationService {
.map(tb -> (TextPageBlock) tb)) .map(tb -> (TextPageBlock) tb))
.toList(); .toList();
HeadLineClassificationContext headLineClassificationContext = new HeadLineClassificationContext();
for (ClassificationPage page : document.getPages()) { for (ClassificationPage page : document.getPages()) {
classifyPage(page, document, headlineFontSizes); classifyPage(page, document, headlineFontSizes, headLineClassificationContext);
} }
List<TextPageBlock> allHeadlines = document.getPages() List<TextPageBlock> allHeadlines = document.getPages()
.stream() .stream()
.flatMap(classificationPage -> classificationPage.getTextBlocks() .flatMap(classificationPage -> classificationPage.getTextBlocks()
.stream() .stream()
.filter(tb -> tb instanceof TextPageBlock && tb.getClassification() != null && tb.getClassification().isHeadline()) .filter(tb -> tb instanceof TextPageBlock && tb.getClassification() != null && tb.getClassification().isHeadline())
.map(tb -> (TextPageBlock) tb)) .map(tb -> (TextPageBlock) tb))
.toList(); .toList();
@ -65,21 +68,26 @@ public class RedactManagerClassificationService {
} }
private void classifyPage(ClassificationPage page, ClassificationDocument document, List<Float> headlineFontSizes) { private void classifyPage(ClassificationPage page, ClassificationDocument document, List<Float> headlineFontSizes, HeadLineClassificationContext headLineClassificationContext) {
for (AbstractPageBlock textBlock : page.getTextBlocks()) { for (AbstractPageBlock textBlock : page.getTextBlocks()) {
if (textBlock instanceof TextPageBlock) { if (textBlock instanceof TextPageBlock) {
classifyBlock((TextPageBlock) textBlock, page, document, headlineFontSizes); classifyBlock((TextPageBlock) textBlock, page, document, headlineFontSizes, headLineClassificationContext);
} }
} }
} }
private void classifyBlock(TextPageBlock textBlock, ClassificationPage page, ClassificationDocument document, List<Float> headlineFontSizes) { private void classifyBlock(TextPageBlock textBlock,
ClassificationPage page,
ClassificationDocument document,
List<Float> headlineFontSizes,
HeadLineClassificationContext headLineClassificationContext) {
var bodyTextFrame = page.getBodyTextFrame(); var bodyTextFrame = page.getBodyTextFrame();
if (textBlock.getClassification() != null && textBlock.getClassification().isHeadline()) { if (textBlock.getClassification() != null && textBlock.getClassification().isHeadline()) {
headLineClassificationContext.setLastHeadlineFromOutline(textBlock);
return; return;
} }
if (document.getFontSizeCounter().getMostPopular() == null) { if (document.getFontSizeCounter().getMostPopular() == null) {
@ -113,7 +121,8 @@ public class RedactManagerClassificationService {
for (int i = 1; i <= headlineFontSizes.size(); i++) { for (int i = 1; i <= headlineFontSizes.size(); i++) {
if (textBlock.getMostPopularWordFontSize() == headlineFontSizes.get(i - 1)) { if (textBlock.getMostPopularWordFontSize() == headlineFontSizes.get(i - 1)) {
textBlock.setClassification(PageBlockType.getHeadlineType(i)); PageBlockType headlineType = PageBlockType.getHeadlineType(i);
classifyHeadline(textBlock, headLineClassificationContext, headlineType);
document.setHeadlines(true); document.setHeadlines(true);
} }
} }
@ -125,7 +134,8 @@ public class RedactManagerClassificationService {
&& textBlock.getSequences() && textBlock.getSequences()
.get(0).getTextPositions() .get(0).getTextPositions()
.get(0).getFontSizeInPt() >= textBlock.getMostPopularWordFontSize()) { .get(0).getFontSizeInPt() >= textBlock.getMostPopularWordFontSize()) {
textBlock.setClassification(PageBlockType.getHeadlineType(headlineFontSizes.size() + 1)); PageBlockType headlineType = PageBlockType.getHeadlineType(headlineFontSizes.size() + 1);
classifyHeadline(textBlock, headLineClassificationContext, headlineType);
document.setHeadlines(true); document.setHeadlines(true);
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) } else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock)
&& textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter().getMostPopular() && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter().getMostPopular()
@ -150,4 +160,66 @@ public class RedactManagerClassificationService {
} }
} }
private static void classifyHeadline(TextPageBlock textBlock, HeadLineClassificationContext headLineClassificationContext, PageBlockType headlineType) {
TextPageBlock lastHeadline = headLineClassificationContext.getLastHeadline();
TextPageBlock lastHeadlineFromOutline = headLineClassificationContext.getLastHeadlineFromOutline();
PageBlockType originalClassifiedBlockType = headLineClassificationContext.getOriginalClassifiedBlockType();
if (lastHeadline != null) {
if (lastHeadline.equals(lastHeadlineFromOutline)) {
headlineType = getNextType(lastHeadline.getClassification());
} else if (originalClassifiedBlockType != null && lastHeadline.getClassification() != originalClassifiedBlockType) {
PageBlockType lastHeadlineType = lastHeadline.getClassification();
int difference = getHeadlineNumber(originalClassifiedBlockType) - getHeadlineNumber(lastHeadlineType);
headlineType = PageBlockType.getHeadlineType(getHeadlineNumber(headlineType) + difference);
}
}
headLineClassificationContext.setOriginalClassifiedBlockType(headlineType);
textBlock.setClassification(headlineType);
headLineClassificationContext.setLastHeadline(textBlock);
}
private static PageBlockType getNextType(PageBlockType pageBlockType) {
return PageBlockType.getHeadlineType(getHeadlineNumber(pageBlockType) + 1);
}
private static int getHeadlineNumber(PageBlockType pageBlockType) {
return switch (pageBlockType) {
case H1 -> 1;
case H2 -> 2;
case H3 -> 3;
case H4 -> 4;
case H5 -> 5;
default -> 6;
};
}
@Data
static class HeadLineClassificationContext {
TextPageBlock lastHeadline;
PageBlockType originalClassifiedBlockType;
TextPageBlock lastHeadlineFromOutline;
public void setLastHeadlineFromOutline(TextPageBlock lastHeadlineFromOutline) {
this.lastHeadlineFromOutline = lastHeadlineFromOutline;
this.setLastHeadline(lastHeadlineFromOutline);
}
}
} }

View File

@ -32,7 +32,8 @@ public class ViewerDocumentTest extends BuildDocumentTest {
//String fileName = "files/new/abschlussarbeiten-template-institut-fur-informatik-padagogische-hochschule-karlsruhe.pdf"; //String fileName = "files/new/abschlussarbeiten-template-institut-fur-informatik-padagogische-hochschule-karlsruhe.pdf";
//String fileName = "files/new/kaust-official-thesis-template.pdf"; //String fileName = "files/new/kaust-official-thesis-template.pdf";
//String fileName = "files/new/$100m Offers.pdf"; //String fileName = "files/new/$100m Offers.pdf";
String fileName = "files/new/18-Curacron_ToxicidadeOcularInVitro.pdf"; //String fileName = "files/new/18-Curacron_ToxicidadeOcularInVitro.pdf";
String fileName = "files/new/UTT-Books-53.pdf";
//String fileName = "files/new/mistitled_outlines_example.pdf"; //String fileName = "files/new/mistitled_outlines_example.pdf";
//String fileName = "files/bdr/Plenarprotokoll 1 (keine Druchsache!) (1) 1.pdf"; //String fileName = "files/bdr/Plenarprotokoll 1 (keine Druchsache!) (1) 1.pdf";
String tmpFileName = "/tmp/" + Path.of(fileName).getFileName() + "_VIEWER.pdf"; String tmpFileName = "/tmp/" + Path.of(fileName).getFileName() + "_VIEWER.pdf";