class FontState
    {
        private final List<String> stateList = new ArrayList<>();
        private final Set<String> stateSet = new HashSet<>();

        /**
         * Pushes new {@link TextPosition TextPositions} into the font state. The state is only
         * preserved correctly for each letter if the number of letters in <code>text</code> matches
         * the number of {@link TextPosition} objects. Otherwise, it's done once for the complete
         * array (just by looking at its first entry).
         *
         * @return A string that contains the text including tag changes caused by its font state.
         */
        public String push(String text, List<TextPosition> textPositions)
        {
            StringBuilder buffer = new StringBuilder();

            if (text.length() == textPositions.size())
            {
                // There is a 1:1 mapping, and we can use the TextPositions directly
                for (int i = 0; i < text.length(); i++)
                {
                    push(buffer, text.charAt(i), textPositions.get(i));
                }
            }
            else if (!text.isEmpty())
            {
                // The normalized text does not match the number of TextPositions, so we'll just
                // have a look at its first entry.
                // TODO change PDFTextStripper.normalize() such that it maintains the 1:1 relation
                if (textPositions.isEmpty())
                {
                    return text;
                }
                push(buffer, text.charAt(0), textPositions.get(0));
                buffer.append(escape(text.substring(1)));
            }
            return buffer.toString();
        }

        /**
         * Closes all open Markdown formatting.
         *
         * @return A string that contains the closing tags of all currently open Markdown
         * formatting.
         */
        public String clear()
        {
            StringBuilder buffer = new StringBuilder();
            closeUntil(buffer, null);
            stateList.clear();
            stateSet.clear();
            return buffer.toString();
        }

        protected String push(StringBuilder buffer, char character, TextPosition textPosition)
        {
            boolean bold = false;
            boolean italics = false;

            PDFontDescriptor descriptor = textPosition.getFont().getFontDescriptor();
            if (descriptor != null)
            {
                bold = isBold(descriptor);
                italics = isItalic(descriptor);
            }

            buffer.append(bold ? open("**") : close("**"));
            buffer.append(italics ? open("*") : close("*"));

            appendEscaped(buffer, character);

            return buffer.toString();
        }

        private String open(String tag)
        {
            if (stateSet.contains(tag))
            {
                return "";
            }
            stateList.add(tag);
            stateSet.add(tag);

            return openTag(tag);
        }

        private String close(String tag)
        {
            if (!stateSet.contains(tag))
            {
                return "";
            }
            // Close all tags until (but including) the one we should close
            StringBuilder tagsBuilder = new StringBuilder();
            int index = closeUntil(tagsBuilder, tag);

            // Remove from state
            stateList.remove(index);
            stateSet.remove(tag);

            // Now open the states that were closed but should remain open again
            for (; index < stateList.size(); index++)
            {
                tagsBuilder.append(openTag(stateList.get(index)));
            }
            return tagsBuilder.toString();
        }

        private int closeUntil(StringBuilder tagsBuilder, String endTag)
        {
            for (int i = stateList.size(); i-- > 0;)
            {
                String tag = stateList.get(i);
                tagsBuilder.append(closeTag(tag));
                if (endTag != null && tag.equals(endTag))
                {
                    return i;
                }
            }
            return -1;
        }

        private String openTag(String tag)
        {
            return tag;
        }

        private String closeTag(String tag)
        {
            return tag;
        }

        private boolean isBold(PDFontDescriptor descriptor)
        {
            if (descriptor.isForceBold())
            {
                return true;
            }
            return descriptor.getFontName().toLowerCase().contains("bold");
        }

        private boolean isItalic(PDFontDescriptor descriptor)
        {<FILL_FUNCTION_BODY>}
    }

            if (descriptor.isItalic())
            {
                return true;
            }
            String fontName = descriptor.getFontName().toLowerCase();
            return fontName.contains("italic") || fontName.contains("oblique");
        
<methods>public void <init>() ,public boolean getAddMoreFormatting() ,public java.lang.String getArticleEnd() ,public java.lang.String getArticleStart() ,public float getAverageCharTolerance() ,public float getDropThreshold() ,public org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem getEndBookmark() ,public int getEndPage() ,public float getIndentThreshold() ,public java.lang.String getLineSeparator() ,public java.lang.String getPageEnd() ,public java.lang.String getPageStart() ,public java.lang.String getParagraphEnd() ,public java.lang.String getParagraphStart() ,public boolean getSeparateByBeads() ,public boolean getSortByPosition() ,public float getSpacingTolerance() ,public org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem getStartBookmark() ,public int getStartPage() ,public boolean getSuppressDuplicateOverlappingText() ,public java.lang.String getText(org.apache.pdfbox.pdmodel.PDDocument) throws java.io.IOException,public java.lang.String getWordSeparator() ,public void processPage(org.apache.pdfbox.pdmodel.PDPage) throws java.io.IOException,public void setAddMoreFormatting(boolean) ,public void setArticleEnd(java.lang.String) ,public void setArticleStart(java.lang.String) ,public void setAverageCharTolerance(float) ,public void setDropThreshold(float) ,public void setEndBookmark(org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem) ,public void setEndPage(int) ,public void setIndentThreshold(float) ,public void setLineSeparator(java.lang.String) ,public void setPageEnd(java.lang.String) ,public void setPageStart(java.lang.String) ,public void setParagraphEnd(java.lang.String) ,public void setParagraphStart(java.lang.String) ,public void setShouldSeparateByBeads(boolean) ,public void setSortByPosition(boolean) ,public void setSpacingTolerance(float) ,public void setStartBookmark(org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem) ,public void setStartPage(int) ,public void setSuppressDuplicateOverlappingText(boolean) ,public void setWordSeparator(java.lang.String) ,public void writeText(org.apache.pdfbox.pdmodel.PDDocument, java.io.Writer) throws java.io.IOException<variables>private static final float END_OF_LAST_TEXT_X_RESET_VALUE,private static final float EXPECTED_START_OF_NEXT_WORD_X_RESET_VALUE,private static final float LAST_WORD_SPACING_RESET_VALUE,protected static final java.lang.String LINE_SEPARATOR,private static final java.lang.String[] LIST_ITEM_EXPRESSIONS,private static final Logger LOG,private static final float MAX_HEIGHT_FOR_LINE_RESET_VALUE,private static final float MAX_Y_FOR_LINE_RESET_VALUE,private static final float MIN_Y_TOP_FOR_LINE_RESET_VALUE,private static final Map<java.lang.Character,java.lang.Character> MIRRORING_CHAR_MAP,private boolean addMoreFormatting,private java.lang.String articleEnd,private java.lang.String articleStart,private float averageCharTolerance,private List<org.apache.pdfbox.pdmodel.common.PDRectangle> beadRectangles,private final Map<java.lang.String,TreeMap<java.lang.Float,TreeSet<java.lang.Float>>> characterListMapping,protected ArrayList<List<org.apache.pdfbox.text.TextPosition>> charactersByArticle,private int currentPageNo,private static float defaultDropThreshold,private static float defaultIndentThreshold,protected org.apache.pdfbox.pdmodel.PDDocument document,private float dropThreshold,private org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem endBookmark,private int endBookmarkPageNumber,private int endPage,private boolean inParagraph,private float indentThreshold,private java.lang.String lineSeparator,private List<java.util.regex.Pattern> listOfPatterns,protected java.io.Writer output,private java.lang.String pageEnd,private java.lang.String pageStart,private java.lang.String paragraphEnd,private java.lang.String paragraphStart,private boolean shouldSeparateByBeads,private boolean sortByPosition,private float spacingTolerance,private org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem startBookmark,private int startBookmarkPageNumber,private int startPage,private boolean suppressDuplicateOverlappingText,private java.lang.String wordSeparator