//----------------------------------------------------------------------------// // // // T e x t L i n e // // // //----------------------------------------------------------------------------// // <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.score.entity.PartNode; import omr.score.entity.Staff; import omr.score.entity.SystemPart; import omr.sheet.SystemInfo; import omr.util.Navigable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Class {@code TextLine} defines a non-mutable structure to report all * information on one OCR-decoded line. * * @author Hervé Bitteur */ public class TextLine extends TextBasedItem { //~ Static fields/initializers --------------------------------------------- /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(TextLine.class); /** Line comparator by deskewed abscissa. */ public static final Comparator<TextLine> byAbscissa = new Comparator<TextLine>() { @Override public int compare (TextLine o1, TextLine o2) { return Double.compare(o1.getDskOrigin().getX(), o2.getDskOrigin().getX()); } }; /** Line comparator by deskewed ordinate. */ public static final Comparator<TextLine> byOrdinate = new Comparator<TextLine>() { @Override public int compare (TextLine o1, TextLine o2) { return Double.compare(o1.getDskOrigin().getY(), o2.getDskOrigin().getY()); } }; //~ Instance fields -------------------------------------------------------- // /** Containing system. */ @Navigable(false) private final SystemInfo system; /** Words that compose this line. */ private final List<TextWord> words = new ArrayList<>(); /** Unmodifiable view of the words sequence. */ @Navigable(false) private final List<TextWord> wordsView = Collections.unmodifiableList(words); /** Deskewed origin. */ private Point2D dskOrigin; /** Average font for the line. */ private FontInfo meanFont; /** * Role of this text line. * Lazily computed, since it depends for a part on the contained words. */ private TextRoleInfo roleInfo; /** Temporary processed flag. */ private boolean processed; //~ Constructors ----------------------------------------------------------- // //----------// // TextLine // //----------// /** * Creates a new TextLine object. * * @param system the containing system * @param words the sequence of words */ public TextLine (SystemInfo system, List<TextWord> words) { this(system); this.words.addAll(words); for (TextWord word : words) { word.setTextLine(this); } } //----------// // TextLine // //----------// /** * Creates a new TextLine object, without its contained words which * are assumed to be added later. * * @param system the containing system */ public TextLine (SystemInfo system) { super(null, null, null, null); this.system = system; } //~ Methods ---------------------------------------------------------------- // //------------// // appendWord // //------------// /** * Append a word at the end of the word sequence of the line. * * @param word the word to append */ public void appendWord (TextWord word) { words.add(word); word.setTextLine(this); invalidateCache(); } //----------// // addWords // //----------// /** * Add a few words. * * @param words the words to add */ public void addWords (Collection<TextWord> words) { if (words != null && !words.isEmpty()) { this.words.addAll(words); for (TextWord word : words) { word.setTextLine(this); } Collections.sort(this.words, TextWord.byAbscissa); invalidateCache(); } } //------// // dump // //------// /** * Print out internals. */ public void dump () { logger.info("{}", this); for (TextWord word : words) { logger.info(" {}", word); } } //-------------// // getBaseline // //-------------// /** * Overridden to recompute baseline from contained words * * @return the line baseline */ @Override public Line2D getBaseline () { if (super.getBaseline() == null) { if (words.isEmpty()) { return null; } else { setBaseline(baselineOf(words)); } } return super.getBaseline(); } //-----------// // getBounds // //-----------// /** * Overridden to recompute the bounds from contained words. * * @return the line bounds */ @Override public Rectangle getBounds () { if (super.getBounds() == null) { setBounds(boundsOf(getWords())); } return super.getBounds(); } //----------// // getChars // //----------// /** * Report the sequence of chars descriptors (of words). * * @return the chars */ public List<TextChar> getChars () { List<TextChar> chars = new ArrayList<>(); for (TextWord word : words) { chars.addAll(word.getChars()); } return chars; } //---------------// // getConfidence // //---------------// /** * Overridden to recompute the confidence from contained words. * * @return the line confidence */ @Override public Integer getConfidence () { if (super.getConfidence() == null) { setConfidence(confidenceOf(getWords())); } return super.getConfidence(); } //--------------// // getDskOrigin // //--------------// /** * Report the deskewed origin of this text line * * @return the deskewed origin */ public Point2D getDskOrigin () { if (dskOrigin == null) { Line2D base = getBaseline(); if (base != null) { dskOrigin = system.getSkew().deskewed(base.getP1()); } } return dskOrigin; } //--------------// // getFirstWord // //--------------// /** * Report the first word of the sentence. * * @return the first word */ public TextWord getFirstWord () { if (!words.isEmpty()) { return words.get(0); } else { return null; } } //-------------// // getMeanFont // //-------------// /** * Build a mean font (size, bold, serif) on representative words. * * @return the most representative font, or null if not available */ public FontInfo getMeanFont () { if (meanFont == null) { int charCount = 0; // Number of (representative) characters int boldCount = 0; // Number of rep chars with bold attribute int italicCount = 0; // Number of rep chars with italic attribute int serifCount = 0; // Number of rep chars with serif attribute int monospaceCount = 0; // Number of rep chars with monospace attribute int smallcapsCount = 0; // Number of rep chars with smallcaps attribute int underlinedCount = 0; // Number of rep chars with underlined attribute float sizeTotal = 0; // Total of font sizes on rep chars for (TextWord word : words) { int length = word.getLength(); // Discard one-char words, they are not reliable if (length > 1) { charCount += length; sizeTotal += word.getPreciseFontSize() * length; FontInfo info = word.getFontInfo(); if (info.isBold) { boldCount += length; } if (info.isItalic) { italicCount += length; } if (info.isUnderlined) { underlinedCount += length; } if (info.isMonospace) { monospaceCount += length; } if (info.isSerif) { serifCount += word.getLength(); } if (info.isSmallcaps) { smallcapsCount += length; } } } if (charCount > 0) { int quorum = charCount / 2; meanFont = new FontInfo(boldCount >= quorum, // isBold, italicCount >= quorum, // isItalic, underlinedCount >= quorum, // isUnderlined, monospaceCount >= quorum, // isMonospace, serifCount >= quorum, // isSerif, smallcapsCount >= quorum, // isSmallcaps, (int) Math.rint((double) sizeTotal / charCount), "DummyFont"); } else { // We have no representative data, let's use the first word if (getFirstWord() != null) { meanFont = getFirstWord().getFontInfo(); } else { logger.error("TextLine with no first word {}", this); } } } return meanFont; } //---------// // getRole // //---------// /** * Lazily compute the line role. * * @return the roleInfo */ public TextRoleInfo getRole () { if (roleInfo == null) { // Guess role roleInfo = TextRole.guessRole(this, system); } return roleInfo; } //---------------// // getSystemPart // //---------------// /** * Report the containing system part. * * @return the containing system part */ public SystemPart getSystemPart () { final TextRole role = getRole().role; final Point location = getFirstWord().getLocation(); final Staff staff = system.getScoreSystem().getTextStaff(role, location); return staff.getPart(); } //----------// // getValue // //----------// /** * Overridden to return the concatenation of word values. * * @return the value to be used */ @Override public String getValue () { StringBuilder sb = null; // Use each word value for (TextWord word : words) { String str = word.getValue(); if (sb == null) { sb = new StringBuilder(str); } else { sb.append(" ").append(str); } } if (sb == null) { return ""; } else { return sb.toString(); } } //---------------// // getWordGlyphs // //---------------// /** * Report the sequence of glyphs (parallel to the sequence of words) * * @return the sequence of word glyphs */ public List<Glyph> getWordGlyphs () { List<Glyph> glyphs = new ArrayList<>(words.size()); for (TextWord word : words) { Glyph glyph = word.getGlyph(); if (glyph != null) { glyphs.add(glyph); } else { logger.warn("Word {} with no related glyph", word); } } return glyphs; } //----------// // getWords // //----------// /** * Report an <b>unmodifiable</b> view of the sequence of words. * * @return the words view */ public List<TextWord> getWords () { return wordsView; } //-----------------// // internalsString // //-----------------// /** * {@inheritDoc} */ @Override protected String internalsString () { StringBuilder sb = new StringBuilder(super.internalsString()); if (getDskOrigin() != null) { sb.append(String.format( " dsk[%.0f,%.0f]", getDskOrigin().getX(), getDskOrigin().getY())); } if (getRole() != null) { sb.append(getRole()); } return sb.toString(); } //-----------------// // invalidateCache // //-----------------// private void invalidateCache () { setBounds(null); setBaseline(null); setConfidence(null); dskOrigin = null; roleInfo = null; meanFont = null; } //----------// // isLyrics // //----------// /** * Report whether this line is flagged as a Lyrics line * * @return true for lyrics line */ public boolean isLyrics () { return getRole().role == TextRole.Lyrics; } //---------// // isChord // //---------// /** * Report whether this line has the Chord role * * @return true for chord line */ public boolean isChord () { return getRole().role == TextRole.Chord; } //-------------// // isProcessed // //-------------// public boolean isProcessed () { return processed; } //-------------// // removeWords // //-------------// /** * Remove a few words * * @param words the words to remove */ public void removeWords (Collection<TextWord> words) { if (words != null && !words.isEmpty()) { this.words.removeAll(words); invalidateCache(); } } //----------------------// // setGlyphsTranslation // //----------------------// /** * Forward the informationto all the words that compose this line. * * @param entity the same score entity for all sentence items */ public void setGlyphsTranslation (PartNode entity) { for (TextWord word : words) { Glyph glyph = word.getGlyph(); if (glyph != null) { glyph.setTranslation(entity); } } } //--------------// // setProcessed // //--------------// public void setProcessed (boolean processed) { this.processed = processed; } //---------// // setRole // //---------// /** * Assign role information. * * @param roleInfo the roleInfo to set */ public void setRole (TextRoleInfo roleInfo) { this.roleInfo = roleInfo; } //-----------// // translate // //-----------// /** * Apply a translation to the coordinates of words descriptors. * * @param dx abscissa translation * @param dy ordinate translation */ @Override public void translate (int dx, int dy) { // Translate line bounds and baseline super.translate(dx, dy); // Update the deskewed origin getDskOrigin().setLocation(system.getSkew().deskewed( getBaseline().getP1())); // Translate contained descriptors for (TextWord word : words) { word.translate(dx, dy); } } }