private RawResult extractDataRange(final SourceLocation sourceLocation,
                                       final InputStream inputStream,
                                       final String encoding,
                                       final long streamSizeBytes,
                                       final boolean limitChars) throws IOException {
        // We could have:
        // One potentially VERY long line, too big to display
        // Lots of small lines
        // Multiple long lines that are too big to display
        final StringBuilder strBuilderRange = new StringBuilder();
        final CharReader charReader = new CharReader(inputStream, false, encoding);
        final DataRange dataRange = sourceLocation.getDataRange();

        final NonSegmentedIncludeCharPredicate inclusiveFromPredicate = buildInclusiveFromPredicate(
                dataRange, true);
        final NonSegmentedIncludeCharPredicate exclusiveToPredicate = buildExclusiveToPredicate(
                dataRange, limitChars);

        // Currently, only support a single highlight for text data
        final DataRange highlight = sourceLocation.getFirstHighlight();
        // A previous call may have already decorated the highlight byte offsets.
        final boolean isHighlightDecorationRequired = highlight != null
                                                      && highlight.getOptByteOffsetFrom().isEmpty()
                                                      && highlight.getOptByteOffsetTo().isEmpty();

        final NonSegmentedIncludeCharPredicate highlightInclusiveFromPredicate = buildInclusiveFromPredicate(
                highlight, false);
        final NonSegmentedIncludeCharPredicate highlightExclusiveToPredicate = buildExclusiveToPredicate(
                highlight, false);

        // Ideally we would jump to the requested offset, but if we do, we can't
        // track the line/colcharOffset info for the requested range, i.e.
        // to show the right line numbers in the editor. Thus we need
        // to advance through char by char

        final ExtractionTracker tracker = new ExtractionTracker(charReader);
        while (true) {
            // Read the next char if there is one
            if (!tracker.readChar()) {
                break;
            }
            final DecodedChar decodedChar = tracker.optDecodeChar.get();

            if (isHighlightDecorationRequired) {
                // As we read the data mark the start/end of the highlight so we can establish the highlight
                // in terms of byte offsets rather than line/col, which allows us to highlight hex dumps.
                if (tracker.highlightStartByteOffsetInc > -1 || highlightInclusiveFromPredicate.test(tracker)) {
                    // Inside the highlighted section
                    if (tracker.highlightStartByteOffsetInc == -1) {
                        tracker.markHighlightStart();
                    }
                    if (tracker.highlightEndByteOffsetExc == -1 && highlightExclusiveToPredicate.test(tracker)) {
                        // Just past the end of highlight section
                        tracker.markHighlightEnd();
                    }
                }
            }

            if (inclusiveFromPredicate.test(tracker)) {

                // On or after the first requested char
                tracker.foundRange = true;

//                LOGGER.info("{}:{} {}",
//                        currLineNo,
//                        currColNo,
//                        decodedChar.isLineBreak() ? "\\n" : decodedChar.getAsString());

                boolean isCharAfterRequestedRange = exclusiveToPredicate.test(tracker);

                if (isCharAfterRequestedRange) {
                    tracker.extraCharCount++;
                }

                // For multi-line data continue past the desired range a bit (up to a limit) to try
                // to complete the line to make it look better in the UI.
                if (isCharAfterRequestedRange
                    && tracker.hasReachedLimitOfLineContinuation(sourceConfig)) {
                    // This is the char after our requested range
                    // or requested range continued to the end of the line
                    // or we have blown the max chars limit
                    if (decodedChar.isLineBreak()) {
                        // need to ensure we count the line break in our offset position.
                        tracker.currCharOffset += decodedChar.getCharCount();
                    }
                    break;
                } else {
                    // Inside the requested range (or continuing past it a bit to the end of the line)
                    tracker.markInsideRange();
                    strBuilderRange.append(decodedChar.getAsString());
                }
            } else {
                // Hold the chars for the line so far up to the requested range so we can
                // tack it on when we find our range
                tracker.appendCharToCurrentLine();
            }

            tracker.prepareForNextRead();
        }

        final StringBuilder strBuilderResultRange = new StringBuilder();

        if (tracker.isMultiLine && strBuilderRange.length() > 0 && strBuilderRange.charAt(0) != '\n') {
            tracker.startCharOffset = tracker.startOfCurrLineCharOffset;
            tracker.startColNo = 1;
            // Tack on the beginning of the line, up to the range
            tracker.addCurrentLine(strBuilderResultRange);
            strBuilderResultRange.append(strBuilderRange);
        } else {
            strBuilderResultRange.append(strBuilderRange);
        }

        final Location locationTo = tracker.getLocationTo();

        if (!tracker.totalCharCount.isExact() && charReader.getLastCharOffsetRead().isPresent()) {
            // Estimate the total char count based on the ratio of chars to bytes seen so far.
            // The estimate will improve as we fetch further into the stream.
            final double avgCharsPerByte = charReader.getLastCharOffsetRead().get()
                                           / (double) charReader.getLastByteOffsetRead().get();

            tracker.totalCharCount = Count.approximately((long) (avgCharsPerByte * streamSizeBytes));
        }

        // The range returned may differ to that requested if we have continued to the end of the line
        final DataRange actualDataRange;
        final String charData;
        DataRange actualHighlight;
        // At this point currByteOffset is the offset of the first byte of the char outside our range
        // so subtract one to get the offset of the last byte (may be mult-byte) of the last 'char'
        // in our range
        final long byteOffsetToInc = tracker.currByteOffset > 0
                ? tracker.currByteOffset - 1
                : tracker.currByteOffset;
        if (tracker.foundRange) {
            charData = strBuilderResultRange.toString();
            actualDataRange = DataRange.builder()
                    .fromCharOffset(tracker.startCharOffset)
                    .fromByteOffset(tracker.startByteOffset)
                    .fromLocation(DefaultLocation.of(tracker.startLineNo, tracker.startColNo))
                    .toLocation(locationTo)
                    .toCharOffset(tracker.currCharOffset)
                    .toByteOffset(byteOffsetToInc)
//                    .toByteOffset(charReader.getLastByteOffsetRead()
//                            .orElseThrow())
                    .withLength((long) charData.length())
                    .build();
            actualHighlight = highlight;
        } else if (strBuilderResultRange.length() == 0) {
            actualDataRange = null;
            charData = "## No Data ##";
            actualHighlight = null;
        } else {
            actualDataRange = null;
            charData = "## Error: Requested range not found ##";
            actualHighlight = null;
        }

        // Decorate the input highlight with byte offsets in case future calls are in hex mode
        // which can only work in byte terms.
        actualHighlight = isHighlightDecorationRequired && tracker.foundHighlight
                ? NullSafe.get(highlight, highlight2 ->
                highlight2.copy()
                        .fromByteOffset(tracker.highlightStartByteOffsetInc)
                        .toByteOffset(tracker.highlightEndByteOffsetExc - 1)
                        .build())
                : highlight;

        LOGGER.debug("highlight: {}", actualHighlight);

        // Define the range that we are actually returning, which may be bigger or smaller than requested
        // e.g. if we have continued to the end of the line or we have hit a char limit
        final SourceLocation.Builder builder = SourceLocation.builder(sourceLocation.getMetaId())
                .withPartIndex(sourceLocation.getPartIndex())
                .withChildStreamType(sourceLocation.getOptChildType()
                        .orElse(null))
                .withHighlight(actualHighlight) // pass the requested highlight back
                .withDataRange(actualDataRange);

        if (recordIndex != null) {
            builder.withRecordIndex(recordIndex);
        }

        final SourceLocation resultLocation = builder.build();
        LOGGER.debug(() -> LogUtil.message(
                "resultLocation {}, charData [{}]",
                resultLocation,
                charData.substring(0, Math.min(charData.length(), 100))));

        final int byteOrderMarkLength = charReader.getByteOrderMark()
                .map(ByteOrderMark::length)
                .orElse(0);

        final RawResult rawResult = new RawResult(
                resultLocation,
                charData,
                byteOrderMarkLength,
                DisplayMode.TEXT);
        rawResult.setTotalCharacterCount(tracker.totalCharCount);
        return rawResult;
    }
How big our buffers are. This should always be a multiple of 8.
stroom-data/stroom-data-store-impl/src/main/java/stroom/data/store/impl/DataFetcher.java