All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xblackcat.pdftable.PDStyledString Maven / Gradle / Ivy

The newest version!
package org.xblackcat.pdftable;

import java.util.*;
import java.util.stream.Stream;

/**
 * 22.04.2016 16:56
 *
 * @author xBlackCat
 */
public class PDStyledString implements CharSequence {
    private final CharSequence text;
    final StylePart[] styleParts;

    public PDStyledString(CharSequence str, PDTextStyle style) {
        this(str, new StylePart(0, str.length(), style));
    }

    private PDStyledString(CharSequence text, StylePart... styleParts) {
        if (text == null) {
            throw new NullPointerException("String can't be null");
        }
        if (styleParts == null || styleParts.length == 0) {
            throw new IllegalArgumentException("At least one style should be specified");
        }
        this.text = text;
        this.styleParts = styleParts;
    }

    public StylePart[] getStyle() {
        return styleParts.clone();
    }

    public String getText() {
        return text.toString();
    }

    @Override
    public int length() {
        return getText().length();
    }

    @Override
    public char charAt(int index) {
        return getText().charAt(index);
    }

    @Override
    public PDStyledString subSequence(int start, int end) {
        final List styleParts = Arrays.asList(this.styleParts);
        return getPdStyledSubstring(text, styleParts, start, end);
    }

    @Override
    public String toString() {
        return getText();
    }

    /**
     * Returns the index within this string of the first occurrence of
     * the specified character. If a character with value
     * {@code ch} occurs in the character sequence represented by
     * this {@code String} object, then the index (in Unicode
     * code units) of the first such occurrence is returned. For
     * values of {@code ch} in the range from 0 to 0xFFFF
     * (inclusive), this is the smallest value k such that:
     * 
     * this.charAt(k) == ch
     * 
* is true. For other values of {@code ch}, it is the * smallest value k such that: *
     * this.codePointAt(k) == ch
     * 
* is true. In either case, if no such character occurs in this * string, then {@code -1} is returned. * * @param ch a character (Unicode code point). * @return the index of the first occurrence of the character in the * character sequence represented by this object, or * {@code -1} if the character does not occur. */ public int indexOf(int ch) { return indexOf(ch, 0); } /** * Returns the index within this string of the first occurrence of the * specified character, starting the search at the specified index. *

* If a character with value {@code ch} occurs in the * character sequence represented by this {@code String} * object at an index no smaller than {@code fromIndex}, then * the index of the first such occurrence is returned. For values * of {@code ch} in the range from 0 to 0xFFFF (inclusive), * this is the smallest value k such that: *

     * (this.charAt(k) == ch) {@code &&} (k >= fromIndex)
     * 
* is true. For other values of {@code ch}, it is the * smallest value k such that: *
     * (this.codePointAt(k) == ch) {@code &&} (k >= fromIndex)
     * 
* is true. In either case, if no such character occurs in this * string at or after position {@code fromIndex}, then * {@code -1} is returned. *

*

* There is no restriction on the value of {@code fromIndex}. If it * is negative, it has the same effect as if it were zero: this entire * string may be searched. If it is greater than the length of this * string, it has the same effect as if it were equal to the length of * this string: {@code -1} is returned. *

*

All indices are specified in {@code char} values * (Unicode code units). * * @param ch a character (Unicode code point). * @param fromIndex the index to start the search from. * @return the index of the first occurrence of the character in the * character sequence represented by this object that is greater * than or equal to {@code fromIndex}, or {@code -1} * if the character does not occur. */ public int indexOf(int ch, int fromIndex) { final int max = text.length(); if (fromIndex < 0) { fromIndex = 0; } else if (fromIndex >= max) { // Note: fromIndex might be near -1>>>1. return -1; } if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { // handle most cases here (ch is a BMP code point or a // negative value (invalid code point)) for (int i = fromIndex; i < max; i++) { if (text.charAt(i) == ch) { return i; } } return -1; } else { return indexOfSupplementary(ch, fromIndex); } } /** * Handles (rare) calls of indexOf with a supplementary character. */ private int indexOfSupplementary(int ch, int fromIndex) { if (Character.isValidCodePoint(ch)) { final char hi = Character.highSurrogate(ch); final char lo = Character.lowSurrogate(ch); final int max = text.length() - 1; for (int i = fromIndex; i < max; i++) { if (text.charAt(i) == hi && text.charAt(i + 1) == lo) { return i; } } } return -1; } public PDStyledString[] split(char separator) { int separatorIdx = indexOf(separator); if (separatorIdx == -1) { return new PDStyledString[]{this}; } Collection lines = new ArrayList<>(); final List styleParts = Arrays.asList(this.styleParts); int idxOffset = 0; do { // Avoid producing empty strings if (idxOffset != separatorIdx + 1) { lines.add(getPdStyledSubstring(text, styleParts, idxOffset, separatorIdx)); } idxOffset = separatorIdx + 1; separatorIdx = indexOf(separator, idxOffset); } while (separatorIdx != -1); if (idxOffset < text.length() - 1) { lines.add(getPdStyledSubstring(text, styleParts, idxOffset, text.length())); } return lines.stream().toArray(PDStyledString[]::new); } protected static PDStyledString getPdStyledSubstring( CharSequence string, Collection styleParts, int start, int end ) { CharSequence text = string.subSequence(start, end); Collection newParts = new ArrayList<>(); final Iterator it = styleParts.iterator(); StylePart next; if (it.hasNext()) { do { next = it.next(); if (next.endIdx > start) { break; } } while (it.hasNext()); if (next.endIdx >= end) { // One-style substring newParts.add(new StylePart(0, end - start, next.style)); } else { newParts.add(new StylePart(0, next.endIdx - start, next.style)); while (it.hasNext() && (next = it.next()).endIdx < end) { newParts.add(new StylePart(0, next.endIdx - start, next.style)); } newParts.add(new StylePart(next.beginIdx - start, end - start, next.style)); } } return new PDStyledString(text, newParts.stream().toArray(StylePart[]::new)); } public final static class StylePart { private final int beginIdx; private final int endIdx; private final PDTextStyle style; StylePart(int beginIdx, int endIdx, PDTextStyle style) { this.beginIdx = beginIdx; this.endIdx = endIdx; this.style = style; } private StylePart shift(int offset) { return new StylePart(beginIdx + offset, endIdx + offset, style); } public StylePart enlarge(int sizeShift) { return new StylePart(beginIdx, endIdx + sizeShift, style); } public StylePart cutHead(int idx) { return new StylePart(beginIdx, idx, style); } public StylePart cutTail(int idx) { return new StylePart(0, endIdx - idx, style); } public StylePart setEnd(int endIdx) { if (endIdx <= beginIdx) { throw new IllegalArgumentException("End index should be greater than begin index"); } return new StylePart(beginIdx, endIdx, style); } public PDTextStyle getStyle() { return style; } public int getEndIdx() { return endIdx; } public int getBeginIdx() { return beginIdx; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final StylePart stylePart = (StylePart) o; return beginIdx == stylePart.beginIdx && endIdx == stylePart.endIdx && Objects.equals(style, stylePart.style); } @Override public int hashCode() { return Objects.hash(beginIdx, endIdx, style); } @Override public String toString() { return "[" + beginIdx + ", " + endIdx + ") for style " + style; } } public final static class Builder implements Appendable, CharSequence { final StringBuilder str = new StringBuilder(); final List parts = new ArrayList<>(); private final PDTextStyle defaultStyle; public Builder(PDTextStyle defaultStyle) { if (defaultStyle == null) { throw new NullPointerException("Default string style can't be null"); } this.defaultStyle = defaultStyle; } @Override public Builder append(CharSequence csq) { if (csq instanceof PDStyledString) { return append((PDStyledString) csq); } return append(csq, defaultStyle); } @Override public Builder append(CharSequence csq, int start, int end) { if (csq instanceof PDStyledString) { return append((PDStyledString) csq, start, end); } return append(csq, defaultStyle, start, end); } public Builder append(CharSequence csq, PDTextStyle style) { return append(new PDStyledString(csq, style)); } public Builder append(CharSequence csq, PDTextStyle style, int start, int end) { return append(new PDStyledString(csq, style), start, end); } @Override public Builder append(char c) { return append(c, defaultStyle); } public Builder append(PDStyledString csq) { if (csq.length() == 0) { // Got nothing - do nothing return this; } final int initLength = str.length(); str.append(csq); if (parts.isEmpty()) { parts.addAll(Arrays.asList(csq.styleParts)); } else { final int lastIdx = parts.size() - 1; final StylePart lastPart = parts.get(lastIdx); final StylePart firstNewPart = csq.styleParts[0]; if (lastPart.style.equals(firstNewPart.style)) { parts.set(lastIdx, lastPart.enlarge(firstNewPart.endIdx)); Stream.of(csq.styleParts).skip(1).map(s -> s.shift(initLength)).forEachOrdered(parts::add); } else { Stream.of(csq.styleParts).map(s -> s.shift(initLength)).forEachOrdered(parts::add); } } return this; } public Builder append(PDStyledString csq, int start, int end) { return append(csq.subSequence(start, end)); } public Builder append(char c, PDTextStyle style) { final int initLength = str.length(); str.append(c); if (parts.isEmpty()) { parts.add(new StylePart(0, 1, style)); } else { final int lastIdx = parts.size() - 1; final StylePart lastPart = parts.get(lastIdx); if (lastPart.style.equals(style)) { parts.set(lastIdx, lastPart.enlarge(1)); } else { parts.add(new StylePart(initLength, initLength + 1, style)); } } return this; } /** * Apply default style to entire string. * * @return this builder object */ public Builder clearStyle() { return setStyle(defaultStyle); } /** * Apply default style to specified range * * @param beginIndex begin index (inclusive) * @param endIndex end index (exclusive) * @return this builder object */ public Builder clearStyle(int beginIndex, int endIndex) { return setStyle(defaultStyle, beginIndex, endIndex); } /** * Apply a style to entire string. * * @param style style to set * @return this builder object */ public Builder setStyle(PDTextStyle style) { parts.clear(); parts.add(new StylePart(0, str.length(), style)); return this; } /** * Apply a style to specified range * * @param style style to set * @param beginIndex begin index (inclusive) * @param endIndex end index (exclusive) * @return this builder object */ public Builder setStyle(PDTextStyle style, int beginIndex, int endIndex) { if (style == null) { throw new NullPointerException("Style can't be null"); } if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > str.length()) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } if (beginIndex == 0 && endIndex == str.length()) { return setStyle(style); } List newParts = new ArrayList<>(); Iterator it = parts.iterator(); StylePart p = null; while (it.hasNext()) { p = it.next(); if (beginIndex < p.endIdx) { break; } newParts.add(p); } if (p != null) { if (p.beginIdx < beginIndex) { newParts.add(new StylePart(p.beginIdx, beginIndex, p.style)); } newParts.add(new StylePart(beginIndex, endIndex, style)); if (endIndex > p.endIdx) { while (it.hasNext()) { p = it.next(); if (endIndex <= p.endIdx) { break; } } } if (endIndex < p.endIdx) { newParts.add(new StylePart(endIndex, p.endIdx, p.style)); } it.forEachRemaining(newParts::add); parts.clear(); parts.addAll(newParts); } return this; } @Override public int length() { return str.length(); } @Override public char charAt(int index) { return str.charAt(index); } @Override public PDStyledString subSequence(int start, int end) { return getPdStyledSubstring(str, parts, start, end); } public PDStyledString toStyledString() { // Merge parts before building string List pp = new ArrayList<>(); StylePart lastPart = null; for (StylePart p : parts) { if (lastPart == null) { lastPart = p; continue; } if (lastPart.style.equals(p.style)) { lastPart = lastPart.setEnd(p.endIdx); } else { pp.add(lastPart); lastPart = p; } } pp.add(lastPart); return new PDStyledString(str.toString(), pp.stream().toArray(StylePart[]::new)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy