private void consumeHighlightedSection(final TextRange textRange,
                                               final Consumer<Byte> consumer) {
            // range is inclusive at both ends by default unless specified differently by rangeMode
            final int lineFrom = textRange.getFrom().getLineNo();
            final int colFrom = textRange.getFrom().getColNo();
            final int lineTo = textRange.getTo().getLineNo();
            final int colTo = textRange.getTo().getColNo();

            boolean found = false;
            boolean inRecord = false;
            boolean isEndInclusive = rangeMode.isEndInclusive();

            int advance = 0;
            final MutableInt offset = new MutableInt(0);

            final Supplier<Byte> byteSupplier = () ->
                    byteBuffer.getByte(offset.getAndIncrement());

            final ByteStreamDecoder byteStreamDecoder = new ByteStreamDecoder(
                    encoding,
                    ByteStreamDecoder.Mode.LENIENT,
                    byteSupplier);

            // TODO This could be made more efficient if scans the stream to look for the
            //  byte value (which may differ for different encodings) of the \n 'chars' until
            //  we get to the line of interest.  From that point we need to decode each char to
            //  see how may bytes it occupies.
            //  Need a kind of jumpToLine method
            while (byteStreamDecoder.getBytesConsumedCount() < length() && !found) {

                if (Thread.currentThread().isInterrupted()) {
                    throw new TaskTerminatedException();
                }

                // This will move offset by the number of bytes in the 'character', i.e. 1-4
                final DecodedChar decodedChar = byteStreamDecoder.decodeNextChar();

                // The offset where our potentially multi-byte char starts
                final int startOffset = byteStreamDecoder.getLastSuppliedByteOffset();

                if (NullSafe.test(decodedChar, DecodedChar::isUnknown)) {
                    final String msg =
                            "Found un-decodable " +
                            StringUtil.plural("byte", decodedChar.getByteCount()) +
                            " [" +
                            ByteArrayUtils.byteArrayToHex(decodedChar.getMalFormedBytes()) +
                            "] at byte offset " +
                            (byteStreamDecoder.getLastSuppliedByteOffset() - decodedChar.getByteCount() - 1) +
                            ". Replacing with '" + DecodedChar.UNKNOWN_CHAR_REPLACEMENT_STRING + "'.";

                    errorReceiverProxy.log(
                            Severity.ERROR,
                            DefaultLocation.of(lineNo, colNo),
                            elementId,
                            msg,
                            ErrorType.INPUT, // Un-decodable is an issue of the input stream
                            null);
                }

                // Inclusive
                if (!inRecord) {
                    if (lineNo > lineFrom ||
                        (lineNo == lineFrom && colNo >= colFrom)) {

                        // This is the start of the range but we don't want to show any leading line breaks
                        if (!decodedChar.isLineBreak()) {
                            inRecord = true;
                        } else {
                            LOGGER.debug("Ignoring line break at start of range");
                        }
                    }
                }

                // Inclusive or Exclusive depending on rangeMode
                if (inRecord) {
                    if (lineNo > lineTo ||
                        (lineNo == lineTo && (
                                (!isEndInclusive && colNo >= colTo) || (isEndInclusive && colNo > colTo)))) {
                        // Gone past the desired range
                        inRecord = false;
                        found = true;
                        // Work out offset of the first char outside the range
                        advance = startOffset + decodedChar.getByteCount() - 1;
                    }
                }

                if (inRecord) {
                    // Pass all the bytes that make up our char onto the consumer
//                    LOGGER.info("Found [{}] at {}:{}", decodedChar.getAsString(), lineNo, colNo);
                    for (int j = 0; j < decodedChar.getByteCount(); j++) {
                        final byte b = byteAt(startOffset + j);
                        consumer.accept(b);
                    }
                }

                // Advance the line or column number if we haven't found the record.
                if (!found) {
                    if (decodedChar.isLineBreak()) {
                        lineNo++;
                        colNo = BASE_COL_NO;
                    } else if (decodedChar.isByteOrderMark() || decodedChar.isNonVisibleCharacter()) {
                        // We don't want to advance the line/col position if it is a non visual char
                        // but we still need to pass the non visual char on to the consumer
                        // as they might want to do something with it.
                        LOGGER.debug("BOM found at [{}:{}]", lineNo, colNo);
                    } else {
                        final int charCount = decodedChar.getCharCount();
                        if (LOGGER.isDebugEnabled() && charCount > 1) {
                            LOGGER.debug("Found multi-char 'character' [{}] with char count {} at [{}:{}]",
                                    decodedChar.getAsString(),
                                    charCount,
                                    lineNo,
                                    colNo);
                        }
                        // Some 'characters', e.g. emoji are not only multi-byte but are
                        // represented as more than one char so we need
                        // to increment the colNo by the right number of chars
                        colNo += decodedChar.getCharCount();
                    }
                }
            }

            // If we went through the whole buffer and didn't find what we are looking for then clear it.
            if (!found) {
                clear();
            } else {
                // Move the filter buffer forward and discard any content we no longer need.
                move(advance);
            }
        }
Buffer for reading byte data with a provided encoding
stroom-pipeline/src/main/java/stroom/pipeline/reader/ReaderRecorder.java