//----------------------------------------------------------------------------//
// //
// T e x t P a t t e r n //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.text;
import omr.glyph.facets.Glyph;
import omr.glyph.pattern.GlyphPattern;
import omr.lag.Section;
import omr.sheet.SystemInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Class {@code TextPattern} is in charge of updating the structure of
* text sentences.
*
* @author Hervé Bitteur
*/
public class TextPattern
extends GlyphPattern
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(TextPattern.class);
//~ Instance fields --------------------------------------------------------
//
/** Text utility for this system */
TextBuilder textBuilder = system.getTextBuilder();
//~ Constructors -----------------------------------------------------------
//
//-------------//
// TextPattern //
//-------------//
public TextPattern (
SystemInfo system)
{
super("Text", system);
}
//~ Methods ----------------------------------------------------------------
//
//------------//
// runPattern //
//------------//
/**
* Update sentences and text glyphs.
*
* @return nothing
*/
@Override
public int runPattern ()
{
List<TextLine> toRemove = new ArrayList<>();
// Check each sentence for inactive, non-text glyph, modified value
// Use a input copy of sentences collection, since it can get modified
for (TextLine line : new LinkedHashSet<>(system.getSentences())) {
checkModifiedValue(line);
purgeWords(line);
if (line.getWords().isEmpty()) {
logger.debug("Removing empty line");
toRemove.add(line);
}
}
system.getSentences().removeAll(toRemove);
// Look for active text glyphs left over (no word, or no line)
checkOrphanGlyphs();
// Global recomposition of all lines
List<TextLine> lines = textBuilder.recomposeLines(system.getSentences());
system.getSentences().clear();
system.getSentences().addAll(lines);
// Purge lines
purgeLines(system.getSentences());
textBuilder.purgeSentences();
return 0; // Useless
}
//-------------------//
// checkOrphanGlyphs //
//-------------------//
/**
* Look for orphan text glyphs and create the proper TextLine
* structure.
* Strategy: create a TextLine for each of these orphan glyphs then
* look for potential merge with other TextLine instances.
*/
private void checkOrphanGlyphs ()
{
String language = system.getSheet().getPage().getTextParam().getTarget();
for (Glyph glyph : system.getGlyphs()) {
if (!isOrphan(glyph)) {
continue;
}
if (glyph.getManualValue() != null) {
// Build a TextLine/TextWord manually
TextWord word = TextWord.createManualWord(
glyph, glyph.getManualValue());
glyph.setTextWord(language, word);
TextLine line = new TextLine(system, Arrays.asList(word));
List<TextLine> lines = Arrays.asList(line);
lines = textBuilder.recomposeLines(lines);
system.getSentences().addAll(lines);
textBuilder.purgeSentences();
} else {
// Use OCR on this glyph
logger.debug("Orphan text {}", glyph.idString());
List<TextLine> lines = textBuilder.retrieveOcrLine(glyph,
language);
if (lines != null && !lines.isEmpty()) {
lines = textBuilder.recomposeLines(lines);
if (!lines.isEmpty()) {
textBuilder.mapGlyphs(lines,
glyph.getMembers(),
language);
}
}
if (lines == null || lines.isEmpty()) {
logger.debug("{} No valid text in {}",
system.idString(), glyph.idString());
if (!glyph.isManualShape()) {
glyph.setShape(null);
}
}
}
}
}
//----------//
// isOrphan //
//----------//
/**
* Check whether the provided glyph is a text orphan.
*
* @param glyph the glyph to check
* @return true if orphan
*/
private boolean isOrphan (Glyph glyph)
{
if (glyph == null || !glyph.isText() || !glyph.isActive()) {
return false;
}
TextWord word = glyph.getTextWord();
if (word == null) {
return true;
}
TextLine line = word.getTextLine();
if (line == null) {
return true;
}
if (!line.getWords().contains(word)) {
return true;
}
return false;
}
//--------------------//
// checkModifiedValue //
//--------------------//
/**
* Check a TextLine for a modified (manual) value and align the
* internal structure accordingly.
*
* @param line the TextLine to check and update if needed
*/
private void checkModifiedValue (TextLine line)
{
String language = system.getSheet().getPage().getTextParam().getTarget();
boolean altered = false;
List<Section> lineSections = new ArrayList<>();
// Use a copy to avoid concurrent modifs
List<TextWord> words = new ArrayList<>(line.getWords());
for (TextWord word : words) {
Glyph glyph = word.getGlyph();
if (glyph == null) {
continue;
} else {
lineSections.addAll(glyph.getMembers());
}
if (!glyph.isActive() || !glyph.isText()) {
continue;
}
if (!glyph.getTextValue().equals(word.getInternalValue())) {
// Here the glyph (manual) value has been modified
altered = true;
textBuilder.splitWords(Arrays.asList(word), line);
}
}
// Remap glyphs if line has been altered
if (altered) {
textBuilder.mapGlyphs(Arrays.asList(line), lineSections, language);
}
}
//------------//
// purgeWords //
//------------//
/**
* Purge a TextLine of its former words no longer linked to an
* active text glyph.
*
* @param line the TextLine to purge
*/
private void purgeWords (TextLine line)
{
List<TextWord> toRemove = new ArrayList<>();
for (TextWord word : line.getWords()) {
Glyph glyph = word.getGlyph();
if (glyph == null || !glyph.isActive() || !glyph.isText()) {
logger.debug("Purging word {}", word);
toRemove.add(word);
}
}
if (!toRemove.isEmpty()) {
line.removeWords(toRemove);
}
}
//------------//
// purgeLines //
//------------//
/**
* Remove the merged lines from the provided collection.
*
* @param sentences the collection to purge
*/
private void purgeLines (Set<TextLine> lines)
{
for (Iterator<TextLine> it = lines.iterator(); it.hasNext();) {
TextLine line = it.next();
if (line.isProcessed()) {
logger.debug("Purging line {}", line);
it.remove();
}
}
}
}