<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PDAbstractContentStream.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Apache PDFBox</a> &gt; <a href="index.source.html" class="el_package">org.apache.pdfbox.pdmodel</a> &gt; <span class="el_source">PDAbstractContentStream.java</span></div><h1>PDAbstractContentStream.java</h1><pre class="source lang-java linenums">/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the &quot;License&quot;); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.pdfbox.pdmodel;

import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.text.NumberFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fontbox.ttf.CmapLookup;
import org.apache.fontbox.ttf.gsub.CompoundCharacterTokenizer;
import org.apache.fontbox.ttf.gsub.GsubWorker;
import org.apache.fontbox.ttf.gsub.GsubWorkerFactory;
import org.apache.fontbox.ttf.model.GsubData;
import org.apache.pdfbox.contentstream.operator.OperatorName;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNumber;
import org.apache.pdfbox.pdfwriter.COSWriter;
import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDPropertyList;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDInlineImage;
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.pdmodel.graphics.state.RenderingMode;
import org.apache.pdfbox.util.Matrix;
import org.apache.pdfbox.util.NumberFormatUtil;

/**
 * Provides the ability to write to a content stream.
 *
 * @author Ben Litchfield
 */
abstract class PDAbstractContentStream implements Closeable
{
<span class="fc" id="L79">    private static final Log LOG = LogFactory.getLog(PDAbstractContentStream.class);</span>

    protected final PDDocument document; // may be null

    protected final OutputStream outputStream;
    protected final PDResources resources;

<span class="fc" id="L86">    protected boolean inTextMode = false;</span>
<span class="fc" id="L87">    protected final Deque&lt;PDFont&gt; fontStack = new ArrayDeque&lt;&gt;();</span>

<span class="fc" id="L89">    protected final Deque&lt;PDColorSpace&gt; nonStrokingColorSpaceStack = new ArrayDeque&lt;&gt;();</span>
<span class="fc" id="L90">    protected final Deque&lt;PDColorSpace&gt; strokingColorSpaceStack = new ArrayDeque&lt;&gt;();</span>

    // number format
<span class="fc" id="L93">    private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US);</span>
<span class="fc" id="L94">    private final byte[] formatBuffer = new byte[32];</span>

<span class="fc" id="L96">    private final Map&lt;PDType0Font, GsubWorker&gt; gsubWorkers = new HashMap&lt;&gt;();</span>
<span class="fc" id="L97">    private final GsubWorkerFactory gsubWorkerFactory = new GsubWorkerFactory();</span>

    /**
     * Create a new appearance stream.
     *
     * @param document may be null
     * @param outputStream The appearances output stream to write to.
     * @param resources The resources to use
     */
    PDAbstractContentStream(PDDocument document, OutputStream outputStream, PDResources resources)
<span class="fc" id="L107">    {</span>
<span class="fc" id="L108">        this.document = document;</span>
<span class="fc" id="L109">        this.outputStream = outputStream;</span>
<span class="fc" id="L110">        this.resources = resources;</span>

<span class="fc" id="L112">        formatDecimal.setMaximumFractionDigits(4);</span>
<span class="fc" id="L113">        formatDecimal.setGroupingUsed(false);</span>
<span class="fc" id="L114">    }</span>

    /**
     * Sets the maximum number of digits allowed for fractional numbers.
     * 
     * @see NumberFormat#setMaximumFractionDigits(int)
     * @param fractionDigitsNumber
     */
    protected void setMaximumFractionDigits(int fractionDigitsNumber)
    {
<span class="fc" id="L124">        formatDecimal.setMaximumFractionDigits(fractionDigitsNumber);</span>
<span class="fc" id="L125">    }</span>

    /**
     * Begin some text operations.
     *
     * @throws IOException If there is an error writing to the stream or if you attempt to
     *         nest beginText calls.
     * @throws IllegalStateException If the method was not allowed to be called at this time.
     */
    public void beginText() throws IOException
    {
<span class="pc bpc" id="L136" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L138">            throw new IllegalStateException(&quot;Error: Nested beginText() calls are not allowed.&quot;);</span>
        }
<span class="fc" id="L140">        writeOperator(OperatorName.BEGIN_TEXT);</span>
<span class="fc" id="L141">        inTextMode = true;</span>
<span class="fc" id="L142">    }</span>

    /**
     * End some text operations.
     *
     * @throws IOException If there is an error writing to the stream or if you attempt to
     *         nest endText calls.
     * @throws IllegalStateException If the method was not allowed to be called at this time.
     */
    public void endText() throws IOException
    {
<span class="pc bpc" id="L153" title="1 of 2 branches missed.">        if (!inTextMode)</span>
        {
<span class="nc" id="L155">            throw new IllegalStateException(&quot;Error: You must call beginText() before calling endText.&quot;);</span>
        }
<span class="fc" id="L157">        writeOperator(OperatorName.END_TEXT);</span>
<span class="fc" id="L158">        inTextMode = false;</span>
<span class="fc" id="L159">    }</span>
    
    /**
     * Set the font and font size to draw text with.
     *
     * @param font The font to use.
     * @param fontSize The font size to draw the text.
     * @throws IOException If there is an error writing the font information.
     */
    public void setFont(PDFont font, float fontSize) throws IOException
    {
<span class="fc bfc" id="L170" title="All 2 branches covered.">        if (fontStack.isEmpty())</span>
        {
<span class="fc" id="L172">            fontStack.add(font);</span>
        }
        else
        {
<span class="fc" id="L176">            fontStack.pop();</span>
<span class="fc" id="L177">            fontStack.push(font);</span>
        }

        // keep track of fonts which are configured for subsetting
<span class="fc bfc" id="L181" title="All 2 branches covered.">        if (font.willBeSubset())</span>
        {
<span class="pc bpc" id="L183" title="1 of 2 branches missed.">            if (document != null)</span>
            {
<span class="fc" id="L185">                document.getFontsToSubset().add(font);</span>
            }
            else
            {
<span class="nc" id="L189">                LOG.warn(&quot;Using the subsetted font '&quot; + font.getName() +</span>
                        &quot;' without a PDDocument context; call subset() before saving&quot;);
            }
        }
<span class="fc bfc" id="L193" title="All 4 branches covered.">        else if (!font.isEmbedded() &amp;&amp; !font.isStandard14())</span>
        {
<span class="fc" id="L195">            LOG.warn(&quot;attempting to use font '&quot; + font.getName() + &quot;' that isn't embedded&quot;);</span>
        }

        // complex text layout
<span class="fc bfc" id="L199" title="All 2 branches covered.">        if (font instanceof PDType0Font)</span>
        {
<span class="fc" id="L201">            PDType0Font pdType0Font = (PDType0Font) font;</span>
<span class="fc" id="L202">            GsubData gsubData = pdType0Font.getGsubData();</span>
<span class="fc bfc" id="L203" title="All 2 branches covered.">            if (gsubData != GsubData.NO_DATA_FOUND)</span>
            {
<span class="fc" id="L205">                GsubWorker gsubWorker = gsubWorkerFactory.getGsubWorker(pdType0Font.getCmapLookup(),</span>
                        gsubData);
<span class="fc" id="L207">                gsubWorkers.put((PDType0Font) font, gsubWorker);</span>
            }
        }

<span class="fc" id="L211">        writeOperand(resources.add(font));</span>
<span class="fc" id="L212">        writeOperand(fontSize);</span>
<span class="fc" id="L213">        writeOperator(OperatorName.SET_FONT_AND_SIZE);</span>
<span class="fc" id="L214">    }</span>

    /**
     * Shows the given text at the location specified by the current text matrix with the given
     * interspersed positioning. This allows the user to efficiently position each glyph or sequence
     * of glyphs.
     *
     * @param textWithPositioningArray An array consisting of String and Float types. Each String is
     * output to the page using the current text matrix. Using the default coordinate system, each
     * interspersed number adjusts the current text matrix by translating to the left or down for
     * horizontal and vertical text respectively. The number is expressed in thousands of a text
     * space unit, and may be negative.
     *
     * @throws IOException if an io exception occurs.
     */
    public void showTextWithPositioning(Object[] textWithPositioningArray) throws IOException
    {
<span class="nc" id="L231">        write(&quot;[&quot;);</span>
<span class="nc bnc" id="L232" title="All 2 branches missed.">        for (Object obj : textWithPositioningArray)</span>
        {
<span class="nc bnc" id="L234" title="All 2 branches missed.">            if (obj instanceof String)</span>
            {
<span class="nc" id="L236">                showTextInternal((String) obj);</span>
            }
<span class="nc bnc" id="L238" title="All 2 branches missed.">            else if (obj instanceof Float)</span>
            {
<span class="nc" id="L240">                writeOperand((Float) obj);</span>
            }
            else
            {
<span class="nc" id="L244">                throw new IllegalArgumentException(&quot;Argument must consist of array of Float and String types&quot;);</span>
            }
        }
<span class="nc" id="L247">        write(&quot;] &quot;);</span>
<span class="nc" id="L248">        writeOperator(OperatorName.SHOW_TEXT_ADJUSTED);</span>
<span class="nc" id="L249">    }</span>

    /**
     * Shows the given text at the location specified by the current text matrix.
     *
     * @param text The Unicode text to show.
     * @throws IOException If an io exception occurs.
     * @throws IllegalArgumentException if a character isn't supported by the current font
     */
    public void showText(String text) throws IOException
    {
<span class="fc" id="L260">        showTextInternal(text);</span>
<span class="fc" id="L261">        write(&quot; &quot;);</span>
<span class="fc" id="L262">        writeOperator(OperatorName.SHOW_TEXT);</span>
<span class="fc" id="L263">    }</span>

    /**
     * Outputs a string using the correct encoding and subsetting as required.
     *
     * @param text The Unicode text to show.
     * 
     * @throws IOException If an io exception occurs.
     */
    protected void showTextInternal(String text) throws IOException
    {
<span class="pc bpc" id="L274" title="1 of 2 branches missed.">        if (!inTextMode)</span>
        {
<span class="nc" id="L276">            throw new IllegalStateException(&quot;Must call beginText() before showText()&quot;);</span>
        }

<span class="pc bpc" id="L279" title="1 of 2 branches missed.">        if (fontStack.isEmpty())</span>
        {
<span class="nc" id="L281">            throw new IllegalStateException(&quot;Must call setFont() before showText()&quot;);</span>
        }

<span class="fc" id="L284">        PDFont font = fontStack.peek();</span>

        // complex text layout
<span class="fc" id="L287">        byte[] encodedText = null;</span>
<span class="fc bfc" id="L288" title="All 2 branches covered.">        if (font instanceof PDType0Font)</span>
        {

<span class="fc" id="L291">            GsubWorker gsubWorker = gsubWorkers.get(font);</span>
<span class="fc bfc" id="L292" title="All 2 branches covered.">            if (gsubWorker != null)</span>
            {
<span class="fc" id="L294">                PDType0Font pdType0Font = (PDType0Font) font;</span>
<span class="fc" id="L295">                Set&lt;Integer&gt; glyphIds = new HashSet&lt;&gt;();</span>
<span class="fc" id="L296">                encodedText = encodeForGsub(gsubWorker, glyphIds, pdType0Font, text);</span>
<span class="pc bpc" id="L297" title="1 of 2 branches missed.">                if (pdType0Font.willBeSubset())</span>
                {
<span class="fc" id="L299">                    pdType0Font.addGlyphsToSubset(glyphIds);</span>
                }
            }
        }

<span class="fc bfc" id="L304" title="All 2 branches covered.">        if (encodedText == null)</span>
        {
<span class="fc" id="L306">            encodedText = font.encode(text);</span>
        }

        // Unicode code points to keep when subsetting
<span class="fc bfc" id="L310" title="All 2 branches covered.">        if (font.willBeSubset())</span>
        {
<span class="fc" id="L312">            int offset = 0;</span>
<span class="fc bfc" id="L313" title="All 2 branches covered.">            while (offset &lt; text.length())</span>
            {
<span class="fc" id="L315">                int codePoint = text.codePointAt(offset);</span>
<span class="fc" id="L316">                font.addToSubset(codePoint);</span>
<span class="fc" id="L317">                offset += Character.charCount(codePoint);</span>
<span class="fc" id="L318">            }</span>
        }

<span class="fc" id="L321">        COSWriter.writeString(encodedText, outputStream);</span>
<span class="fc" id="L322">    }</span>

    /**
     * Sets the text leading.
     *
     * @param leading The leading in unscaled text units.
     * @throws IOException If there is an error writing to the stream.
     */
    public void setLeading(float leading) throws IOException
    {
<span class="nc" id="L332">        writeOperand(leading);</span>
<span class="nc" id="L333">        writeOperator(OperatorName.SET_TEXT_LEADING);</span>
<span class="nc" id="L334">    }</span>

    /**
     * Move to the start of the next line of text. Requires the leading (see {@link #setLeading})
     * to have been set.
     *
     * @throws IOException If there is an error writing to the stream.
     */
    public void newLine() throws IOException
    {
<span class="nc bnc" id="L344" title="All 2 branches missed.">        if (!inTextMode)</span>
        {
<span class="nc" id="L346">            throw new IllegalStateException(&quot;Must call beginText() before newLine()&quot;);</span>
        }
<span class="nc" id="L348">        writeOperator(OperatorName.NEXT_LINE);</span>
<span class="nc" id="L349">    }</span>

    /**
     * The Td operator.
     * Move to the start of the next line, offset from the start of the current line by (tx, ty).
     *
     * @param tx The x translation.
     * @param ty The y translation.
     * @throws IOException If there is an error writing to the stream.
     * @throws IllegalStateException If the method was not allowed to be called at this time.
     */
    public void newLineAtOffset(float tx, float ty) throws IOException
    {
<span class="pc bpc" id="L362" title="1 of 2 branches missed.">        if (!inTextMode)</span>
        {
<span class="nc" id="L364">            throw new IllegalStateException(&quot;Error: must call beginText() before newLineAtOffset()&quot;);</span>
        }
<span class="fc" id="L366">        writeOperand(tx);</span>
<span class="fc" id="L367">        writeOperand(ty);</span>
<span class="fc" id="L368">        writeOperator(OperatorName.MOVE_TEXT);</span>
<span class="fc" id="L369">    }</span>

    /**
     * The Tm operator. Sets the text matrix to the given values.
     * A current text matrix will be replaced with the new one.
     *
     * @param matrix the transformation matrix
     * @throws IOException If there is an error writing to the stream.
     * @throws IllegalStateException If the method was not allowed to be called at this time.
     */
    public void setTextMatrix(Matrix matrix) throws IOException
    {
<span class="pc bpc" id="L381" title="1 of 2 branches missed.">        if (!inTextMode)</span>
        {
<span class="nc" id="L383">            throw new IllegalStateException(&quot;Error: must call beginText() before setTextMatrix&quot;);</span>
        }
<span class="fc" id="L385">        writeAffineTransform(matrix.createAffineTransform());</span>
<span class="fc" id="L386">        writeOperator(OperatorName.SET_MATRIX);</span>
<span class="fc" id="L387">    }</span>

    /**
     * Draw an image at the x,y coordinates, with the default size of the image.
     *
     * @param image The image to draw.
     * @param x The x-coordinate to draw the image.
     * @param y The y-coordinate to draw the image.
     *
     * @throws IOException If there is an error writing to the stream.
     */
    public void drawImage(PDImageXObject image, float x, float y) throws IOException
    {
<span class="fc" id="L400">        drawImage(image, x, y, image.getWidth(), image.getHeight());</span>
<span class="fc" id="L401">    }</span>

    /**
     * Draw an image at the x,y coordinates, with the given size.
     *
     * @param image The image to draw.
     * @param x The x-coordinate to draw the image.
     * @param y The y-coordinate to draw the image.
     * @param width The width to draw the image.
     * @param height The height to draw the image.
     *
     * @throws IOException If there is an error writing to the stream.
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void drawImage(PDImageXObject image, float x, float y, float width, float height) throws IOException
    {
<span class="pc bpc" id="L417" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L419">            throw new IllegalStateException(&quot;Error: drawImage is not allowed within a text block.&quot;);</span>
        }

<span class="fc" id="L422">        saveGraphicsState();</span>

<span class="fc" id="L424">        AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y);</span>
<span class="fc" id="L425">        transform(new Matrix(transform));</span>

<span class="fc" id="L427">        writeOperand(resources.add(image));</span>
<span class="fc" id="L428">        writeOperator(OperatorName.DRAW_OBJECT);</span>

<span class="fc" id="L430">        restoreGraphicsState();</span>
<span class="fc" id="L431">    }</span>

    /**
     * Draw an image at the origin with the given transformation matrix.
     *
     * @param image The image to draw.
     * @param matrix The transformation matrix to apply to the image.
     *
     * @throws IOException If there is an error writing to the stream.
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void drawImage(PDImageXObject image, Matrix matrix) throws IOException
    {
<span class="nc bnc" id="L444" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L446">            throw new IllegalStateException(&quot;Error: drawImage is not allowed within a text block.&quot;);</span>
        }

<span class="nc" id="L449">        saveGraphicsState();</span>

<span class="nc" id="L451">        AffineTransform transform = matrix.createAffineTransform();</span>
<span class="nc" id="L452">        transform(new Matrix(transform));</span>

<span class="nc" id="L454">        writeOperand(resources.add(image));</span>
<span class="nc" id="L455">        writeOperator(OperatorName.DRAW_OBJECT);</span>

<span class="nc" id="L457">        restoreGraphicsState();</span>
<span class="nc" id="L458">    }</span>

    /**
     * Draw an inline image at the x,y coordinates, with the default size of the image.
     *
     * @param inlineImage The inline image to draw.
     * @param x The x-coordinate to draw the inline image.
     * @param y The y-coordinate to draw the inline image.
     *
     * @throws IOException If there is an error writing to the stream.
     */
    public void drawImage(PDInlineImage inlineImage, float x, float y) throws IOException
    {
<span class="fc" id="L471">        drawImage(inlineImage, x, y, inlineImage.getWidth(), inlineImage.getHeight());</span>
<span class="fc" id="L472">    }</span>

    /**
     * Draw an inline image at the x,y coordinates and a certain width and height.
     *
     * @param inlineImage The inline image to draw.
     * @param x The x-coordinate to draw the inline image.
     * @param y The y-coordinate to draw the inline image.
     * @param width The width of the inline image to draw.
     * @param height The height of the inline image to draw.
     *
     * @throws IOException If there is an error writing to the stream.
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void drawImage(PDInlineImage inlineImage, float x, float y, float width, float height) throws IOException
    {
<span class="pc bpc" id="L488" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L490">            throw new IllegalStateException(&quot;Error: drawImage is not allowed within a text block.&quot;);</span>
        }

<span class="fc" id="L493">        saveGraphicsState();</span>
<span class="fc" id="L494">        transform(new Matrix(width, 0, 0, height, x, y));</span>

        // create the image dictionary
<span class="fc" id="L497">        StringBuilder sb = new StringBuilder();</span>
<span class="fc" id="L498">        sb.append(OperatorName.BEGIN_INLINE_IMAGE);</span>

<span class="fc" id="L500">        sb.append(&quot;\n /W &quot;);</span>
<span class="fc" id="L501">        sb.append(inlineImage.getWidth());</span>

<span class="fc" id="L503">        sb.append(&quot;\n /H &quot;);</span>
<span class="fc" id="L504">        sb.append(inlineImage.getHeight());</span>

<span class="fc" id="L506">        sb.append(&quot;\n /CS &quot;);</span>
<span class="fc" id="L507">        sb.append(&quot;/&quot;);</span>
<span class="fc" id="L508">        sb.append(inlineImage.getColorSpace().getName());</span>

<span class="fc" id="L510">        COSArray decodeArray = inlineImage.getDecode();</span>
<span class="pc bpc" id="L511" title="1 of 4 branches missed.">        if (decodeArray != null &amp;&amp; decodeArray.size() &gt; 0)</span>
        {
<span class="fc" id="L513">            sb.append(&quot;\n /D &quot;);</span>
<span class="fc" id="L514">            sb.append(&quot;[&quot;);</span>
<span class="fc bfc" id="L515" title="All 2 branches covered.">            for (COSBase base : decodeArray)</span>
            {
<span class="fc" id="L517">                sb.append(((COSNumber) base).intValue());</span>
<span class="fc" id="L518">                sb.append(&quot; &quot;);</span>
<span class="fc" id="L519">            }</span>
<span class="fc" id="L520">            sb.append(&quot;]&quot;);</span>
        }

<span class="pc bpc" id="L523" title="1 of 2 branches missed.">        if (inlineImage.isStencil())</span>
        {
<span class="fc" id="L525">            sb.append(&quot;\n /IM true&quot;);</span>
        }

<span class="fc" id="L528">        sb.append(&quot;\n /BPC &quot;);</span>
<span class="fc" id="L529">        sb.append(inlineImage.getBitsPerComponent());</span>

        // image dictionary
<span class="fc" id="L532">        write(sb.toString());</span>
<span class="fc" id="L533">        writeLine();</span>

        // binary data
<span class="fc" id="L536">        writeOperator(OperatorName.BEGIN_INLINE_IMAGE_DATA);</span>
<span class="fc" id="L537">        writeBytes(inlineImage.getData());</span>
<span class="fc" id="L538">        writeLine();</span>
<span class="fc" id="L539">        writeOperator(OperatorName.END_INLINE_IMAGE);</span>

<span class="fc" id="L541">        restoreGraphicsState();</span>
<span class="fc" id="L542">    }</span>

    /**
     * Draws the given Form XObject at the current location.
     *
     * @param form Form XObject
     * @throws IOException if the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void drawForm(PDFormXObject form) throws IOException
    {
<span class="pc bpc" id="L553" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L555">            throw new IllegalStateException(&quot;Error: drawForm is not allowed within a text block.&quot;);</span>
        }

<span class="fc" id="L558">        writeOperand(resources.add(form));</span>
<span class="fc" id="L559">        writeOperator(OperatorName.DRAW_OBJECT);</span>
<span class="fc" id="L560">    }</span>

    /**
     * The cm operator. Concatenates the given matrix with the current transformation matrix (CTM),
     * which maps user space coordinates used within a PDF content stream into output device
     * coordinates. More details on coordinates can be found in the PDF 32000 specification, 8.3.2
     * Coordinate Spaces.
     *
     * @param matrix the transformation matrix
     * @throws IOException If there is an error writing to the stream.
     */
    public void transform(Matrix matrix) throws IOException
    {
<span class="pc bpc" id="L573" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L575">            throw new IllegalStateException(&quot;Error: Modifying the current transformation matrix is not allowed within text objects.&quot;);</span>
        }

<span class="fc" id="L578">        writeAffineTransform(matrix.createAffineTransform());</span>
<span class="fc" id="L579">        writeOperator(OperatorName.CONCAT);</span>
<span class="fc" id="L580">    }</span>

    /**
     * q operator. Saves the current graphics state.
     * @throws IOException If an error occurs while writing to the stream.
     */
    public void saveGraphicsState() throws IOException
    {
<span class="pc bpc" id="L588" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L590">            throw new IllegalStateException(&quot;Error: Saving the graphics state is not allowed within text objects.&quot;);</span>
        }

<span class="pc bpc" id="L593" title="1 of 2 branches missed.">        if (!fontStack.isEmpty())</span>
        {
<span class="nc" id="L595">            fontStack.push(fontStack.peek());</span>
        }
<span class="pc bpc" id="L597" title="1 of 2 branches missed.">        if (!strokingColorSpaceStack.isEmpty())</span>
        {
<span class="nc" id="L599">            strokingColorSpaceStack.push(strokingColorSpaceStack.peek());</span>
        }
<span class="fc bfc" id="L601" title="All 2 branches covered.">        if (!nonStrokingColorSpaceStack.isEmpty())</span>
        {
<span class="fc" id="L603">            nonStrokingColorSpaceStack.push(nonStrokingColorSpaceStack.peek());</span>
        }
<span class="fc" id="L605">        writeOperator(OperatorName.SAVE);</span>
<span class="fc" id="L606">    }</span>

    /**
     * Q operator. Restores the current graphics state.
     * @throws IOException If an error occurs while writing to the stream.
     */
    public void restoreGraphicsState() throws IOException
    {
<span class="pc bpc" id="L614" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L616">            throw new IllegalStateException(&quot;Error: Restoring the graphics state is not allowed within text objects.&quot;);</span>
        }

<span class="fc bfc" id="L619" title="All 2 branches covered.">        if (!fontStack.isEmpty())</span>
        {
<span class="fc" id="L621">            fontStack.pop();</span>
        }
<span class="pc bpc" id="L623" title="1 of 2 branches missed.">        if (!strokingColorSpaceStack.isEmpty())</span>
        {
<span class="nc" id="L625">            strokingColorSpaceStack.pop();</span>
        }
<span class="fc bfc" id="L627" title="All 2 branches covered.">        if (!nonStrokingColorSpaceStack.isEmpty())</span>
        {
<span class="fc" id="L629">            nonStrokingColorSpaceStack.pop();</span>
        }
<span class="fc" id="L631">        writeOperator(OperatorName.RESTORE);</span>
<span class="fc" id="L632">    }</span>

    protected COSName getName(PDColorSpace colorSpace)
    {
<span class="pc bpc" id="L636" title="3 of 6 branches missed.">        if (colorSpace instanceof PDDeviceGray ||</span>
            colorSpace instanceof PDDeviceRGB ||
            colorSpace instanceof PDDeviceCMYK)
        {
<span class="fc" id="L640">            return COSName.getPDFName(colorSpace.getName());</span>
        }
        else
        {
<span class="nc" id="L644">            return resources.add(colorSpace);</span>
        }
    }

    /**
     * Sets the stroking color and, if necessary, the stroking color space.
     *
     * @param color Color in a specific color space.
     * @throws IOException If an IO error occurs while writing to the stream.
     */
    public void setStrokingColor(PDColor color) throws IOException
    {
<span class="pc bpc" id="L656" title="1 of 2 branches missed.">        if (strokingColorSpaceStack.isEmpty() ||</span>
<span class="nc bnc" id="L657" title="All 2 branches missed.">            strokingColorSpaceStack.peek() != color.getColorSpace())</span>
        {
<span class="fc" id="L659">            writeOperand(getName(color.getColorSpace()));</span>
<span class="fc" id="L660">            writeOperator(OperatorName.STROKING_COLORSPACE);</span>
<span class="fc" id="L661">            setStrokingColorSpaceStack(color.getColorSpace());</span>
        }

<span class="fc bfc" id="L664" title="All 2 branches covered.">        for (float value : color.getComponents())</span>
        {
<span class="fc" id="L666">            writeOperand(value);</span>
        }

<span class="pc bpc" id="L669" title="1 of 2 branches missed.">        if (color.getColorSpace() instanceof PDPattern)</span>
        {
<span class="nc" id="L671">            writeOperand(color.getPatternName());</span>
        }

<span class="pc bpc" id="L674" title="1 of 2 branches missed.">        if (color.getColorSpace() instanceof PDPattern ||</span>
<span class="pc bpc" id="L675" title="1 of 2 branches missed.">            color.getColorSpace() instanceof PDSeparation ||</span>
<span class="pc bpc" id="L676" title="1 of 2 branches missed.">            color.getColorSpace() instanceof PDDeviceN ||</span>
<span class="pc bpc" id="L677" title="1 of 2 branches missed.">            color.getColorSpace() instanceof PDICCBased)</span>
        {
<span class="nc" id="L679">            writeOperator(OperatorName.STROKING_COLOR_N);</span>
        }
        else
        {
<span class="fc" id="L683">            writeOperator(OperatorName.STROKING_COLOR);</span>
        }
<span class="fc" id="L685">    }</span>

    /**
     * Set the stroking color using an AWT color. Conversion uses the default sRGB color space.
     *
     * @param color The color to set.
     * @throws IOException If an IO error occurs while writing to the stream.
     */
    public void setStrokingColor(Color color) throws IOException
    {
<span class="nc" id="L695">        float[] components = new float[] {</span>
<span class="nc" id="L696">                color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f };</span>
<span class="nc" id="L697">        PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE);</span>
<span class="nc" id="L698">        setStrokingColor(pdColor);</span>
<span class="nc" id="L699">    }</span>

    /**
     * Set the stroking color in the DeviceRGB color space. Range is 0..1.
     *
     * @param r The red value
     * @param g The green value.
     * @param b The blue value.
     * @throws IOException If an IO error occurs while writing to the stream.
     * @throws IllegalArgumentException If the parameters are invalid.
     */
    public void setStrokingColor(float r, float g, float b) throws IOException
    {
<span class="pc bpc" id="L712" title="3 of 6 branches missed.">        if (isOutsideOneInterval(r) || isOutsideOneInterval(g) || isOutsideOneInterval(b))</span>
        {
<span class="nc" id="L714">            throw new IllegalArgumentException(&quot;Parameters must be within 0..1, but are &quot;</span>
<span class="nc" id="L715">                    + String.format(&quot;(%.2f,%.2f,%.2f)&quot;, r, g, b));</span>
        }
<span class="fc" id="L717">        writeOperand(r);</span>
<span class="fc" id="L718">        writeOperand(g);</span>
<span class="fc" id="L719">        writeOperand(b);</span>
<span class="fc" id="L720">        writeOperator(OperatorName.STROKING_COLOR_RGB);</span>
<span class="fc" id="L721">        setStrokingColorSpaceStack(PDDeviceRGB.INSTANCE);</span>
<span class="fc" id="L722">    }</span>

    /**
     * Set the stroking color in the DeviceCMYK color space. Range is 0..1
     *
     * @param c The cyan value.
     * @param m The magenta value.
     * @param y The yellow value.
     * @param k The black value.
     * @throws IOException If an IO error occurs while writing to the stream.
     * @throws IllegalArgumentException If the parameters are invalid.
     */
    public void setStrokingColor(float c, float m, float y, float k) throws IOException
    {
<span class="pc bpc" id="L736" title="4 of 8 branches missed.">        if (isOutsideOneInterval(c) || isOutsideOneInterval(m) || isOutsideOneInterval(y) || isOutsideOneInterval(k))</span>
        {
<span class="nc" id="L738">            throw new IllegalArgumentException(&quot;Parameters must be within 0..1, but are &quot;</span>
<span class="nc" id="L739">                    + String.format(&quot;(%.2f,%.2f,%.2f,%.2f)&quot;, c, m, y, k));</span>
        }
<span class="fc" id="L741">        writeOperand(c);</span>
<span class="fc" id="L742">        writeOperand(m);</span>
<span class="fc" id="L743">        writeOperand(y);</span>
<span class="fc" id="L744">        writeOperand(k);</span>
<span class="fc" id="L745">        writeOperator(OperatorName.STROKING_COLOR_CMYK);</span>
<span class="fc" id="L746">        setStrokingColorSpaceStack(PDDeviceCMYK.INSTANCE);</span>
<span class="fc" id="L747">    }</span>

    /**
     * Set the stroking color in the DeviceGray color space. Range is 0..1.
     *
     * @param g The gray value.
     * @throws IOException If an IO error occurs while writing to the stream.
     * @throws IllegalArgumentException If the parameter is invalid.
     */
    public void setStrokingColor(float g) throws IOException
    {
<span class="pc bpc" id="L758" title="1 of 2 branches missed.">        if (isOutsideOneInterval(g))</span>
        {
<span class="nc" id="L760">            throw new IllegalArgumentException(&quot;Parameter must be within 0..1, but is &quot; + g);</span>
        }
<span class="fc" id="L762">        writeOperand(g);</span>
<span class="fc" id="L763">        writeOperator(OperatorName.STROKING_COLOR_GRAY);</span>
<span class="fc" id="L764">        setStrokingColorSpaceStack(PDDeviceGray.INSTANCE);</span>
<span class="fc" id="L765">    }</span>

    /**
     * Sets the non-stroking color and, if necessary, the non-stroking color space.
     *
     * @param color Color in a specific color space.
     * @throws IOException If an IO error occurs while writing to the stream.
     */
    public void setNonStrokingColor(PDColor color) throws IOException
    {
<span class="fc bfc" id="L775" title="All 2 branches covered.">        if (nonStrokingColorSpaceStack.isEmpty() ||</span>
<span class="pc bpc" id="L776" title="1 of 2 branches missed.">            nonStrokingColorSpaceStack.peek() != color.getColorSpace())</span>
        {
<span class="fc" id="L778">            writeOperand(getName(color.getColorSpace()));</span>
<span class="fc" id="L779">            writeOperator(OperatorName.NON_STROKING_COLORSPACE);</span>
<span class="fc" id="L780">            setNonStrokingColorSpaceStack(color.getColorSpace());</span>
        }

<span class="fc bfc" id="L783" title="All 2 branches covered.">        for (float value : color.getComponents())</span>
        {
<span class="fc" id="L785">            writeOperand(value);</span>
        }

<span class="pc bpc" id="L788" title="1 of 2 branches missed.">        if (color.getColorSpace() instanceof PDPattern)</span>
        {
<span class="nc" id="L790">            writeOperand(color.getPatternName());</span>
        }

<span class="pc bpc" id="L793" title="1 of 2 branches missed.">        if (color.getColorSpace() instanceof PDPattern ||</span>
<span class="pc bpc" id="L794" title="1 of 2 branches missed.">            color.getColorSpace() instanceof PDSeparation ||</span>
<span class="pc bpc" id="L795" title="1 of 2 branches missed.">            color.getColorSpace() instanceof PDDeviceN ||</span>
<span class="pc bpc" id="L796" title="1 of 2 branches missed.">            color.getColorSpace() instanceof PDICCBased)</span>
        {
<span class="nc" id="L798">            writeOperator(OperatorName.NON_STROKING_COLOR_N);</span>
        }
        else
        {
<span class="fc" id="L802">            writeOperator(OperatorName.NON_STROKING_COLOR);</span>
        }
<span class="fc" id="L804">    }</span>

    /**
     * Set the non-stroking color using an AWT color. Conversion uses the default sRGB color space.
     *
     * @param color The color to set.
     * @throws IOException If an IO error occurs while writing to the stream.
     */
    public void setNonStrokingColor(Color color) throws IOException
    {
<span class="fc" id="L814">        float[] components = new float[] {</span>
<span class="fc" id="L815">                color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f };</span>
<span class="fc" id="L816">        PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE);</span>
<span class="fc" id="L817">        setNonStrokingColor(pdColor);</span>
<span class="fc" id="L818">    }</span>

    /**
     * Set the non-stroking color in the DeviceRGB color space. Range is 0..1.
     *
     * @param r The red value.
     * @param g The green value.
     * @param b The blue value.
     * @throws IOException If an IO error occurs while writing to the stream.
     * @throws IllegalArgumentException If the parameters are invalid.
     */
    public void setNonStrokingColor(float r, float g, float b) throws IOException
    {
<span class="pc bpc" id="L831" title="3 of 6 branches missed.">        if (isOutsideOneInterval(r) || isOutsideOneInterval(g) || isOutsideOneInterval(b))</span>
        {
<span class="nc" id="L833">            throw new IllegalArgumentException(&quot;Parameters must be within 0..1, but are &quot;</span>
<span class="nc" id="L834">                    + String.format(&quot;(%.2f,%.2f,%.2f)&quot;, r, g, b));</span>
        }
<span class="fc" id="L836">        writeOperand(r);</span>
<span class="fc" id="L837">        writeOperand(g);</span>
<span class="fc" id="L838">        writeOperand(b);</span>
<span class="fc" id="L839">        writeOperator(OperatorName.NON_STROKING_RGB);</span>
<span class="fc" id="L840">        setNonStrokingColorSpaceStack(PDDeviceRGB.INSTANCE);</span>
<span class="fc" id="L841">    }</span>

    /**
     * Set the non-stroking color in the DeviceCMYK color space. Range is 0..1.
     *
     * @param c The cyan value.
     * @param m The magenta value.
     * @param y The yellow value.
     * @param k The black value.
     * @throws IOException If an IO error occurs while writing to the stream.
     */
    public void setNonStrokingColor(float c, float m, float y, float k) throws IOException
    {
<span class="pc bpc" id="L854" title="4 of 8 branches missed.">        if (isOutsideOneInterval(c) || isOutsideOneInterval(m) || isOutsideOneInterval(y) || isOutsideOneInterval(k))</span>
        {
<span class="nc" id="L856">            throw new IllegalArgumentException(&quot;Parameters must be within 0..1, but are &quot;</span>
<span class="nc" id="L857">                    + String.format(&quot;(%.2f,%.2f,%.2f,%.2f)&quot;, c, m, y, k));</span>
        }
<span class="fc" id="L859">        writeOperand(c);</span>
<span class="fc" id="L860">        writeOperand(m);</span>
<span class="fc" id="L861">        writeOperand(y);</span>
<span class="fc" id="L862">        writeOperand(k);</span>
<span class="fc" id="L863">        writeOperator(OperatorName.NON_STROKING_CMYK);</span>
<span class="fc" id="L864">        setNonStrokingColorSpaceStack(PDDeviceCMYK.INSTANCE);</span>
<span class="fc" id="L865">    }</span>

    /**
     * Set the non-stroking color in the DeviceGray color space. Range is 0..1.
     *
     * @param g The gray value.
     * @throws IOException If an IO error occurs while writing to the stream.
     * @throws IllegalArgumentException If the parameter is invalid.
     */
    public void setNonStrokingColor(float g) throws IOException
    {
<span class="pc bpc" id="L876" title="1 of 2 branches missed.">        if (isOutsideOneInterval(g))</span>
        {
<span class="nc" id="L878">            throw new IllegalArgumentException(&quot;Parameter must be within 0..1, but is &quot; + g);</span>
        }
<span class="fc" id="L880">        writeOperand(g);</span>
<span class="fc" id="L881">        writeOperator(OperatorName.NON_STROKING_GRAY);</span>
<span class="fc" id="L882">        setNonStrokingColorSpaceStack(PDDeviceGray.INSTANCE);</span>
<span class="fc" id="L883">    }</span>

    /**
     * Add a rectangle to the current path.
     *
     * @param x The lower left x coordinate.
     * @param y The lower left y coordinate.
     * @param width The width of the rectangle.
     * @param height The height of the rectangle.
     * @throws IOException If the content stream could not be written.
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void addRect(float x, float y, float width, float height) throws IOException
    {
<span class="pc bpc" id="L897" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L899">            throw new IllegalStateException(&quot;Error: addRect is not allowed within a text block.&quot;);</span>
        }
<span class="fc" id="L901">        writeOperand(x);</span>
<span class="fc" id="L902">        writeOperand(y);</span>
<span class="fc" id="L903">        writeOperand(width);</span>
<span class="fc" id="L904">        writeOperand(height);</span>
<span class="fc" id="L905">        writeOperator(OperatorName.APPEND_RECT);</span>
<span class="fc" id="L906">    }</span>

    /**
     * Append a cubic Bézier curve to the current path. The curve extends from the current point to
     * the point (x3, y3), using (x1, y1) and (x2, y2) as the Bézier control points.
     *
     * @param x1 x coordinate of the point 1
     * @param y1 y coordinate of the point 1
     * @param x2 x coordinate of the point 2
     * @param y2 y coordinate of the point 2
     * @param x3 x coordinate of the point 3
     * @param y3 y coordinate of the point 3
     * @throws IOException If the content stream could not be written.
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
    {
<span class="nc bnc" id="L923" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L925">            throw new IllegalStateException(&quot;Error: curveTo is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L927">        writeOperand(x1);</span>
<span class="nc" id="L928">        writeOperand(y1);</span>
<span class="nc" id="L929">        writeOperand(x2);</span>
<span class="nc" id="L930">        writeOperand(y2);</span>
<span class="nc" id="L931">        writeOperand(x3);</span>
<span class="nc" id="L932">        writeOperand(y3);</span>
<span class="nc" id="L933">        writeOperator(OperatorName.CURVE_TO);</span>
<span class="nc" id="L934">    }</span>

    /**
     * Append a cubic Bézier curve to the current path. The curve extends from the current point to
     * the point (x3, y3), using the current point and (x2, y2) as the Bézier control points.
     *
     * @param x2 x coordinate of the point 2
     * @param y2 y coordinate of the point 2
     * @param x3 x coordinate of the point 3
     * @param y3 y coordinate of the point 3
     * @throws IllegalStateException If the method was called within a text block.
     * @throws IOException If the content stream could not be written.
     */
    public void curveTo2(float x2, float y2, float x3, float y3) throws IOException
    {
<span class="nc bnc" id="L949" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L951">            throw new IllegalStateException(&quot;Error: curveTo2 is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L953">        writeOperand(x2);</span>
<span class="nc" id="L954">        writeOperand(y2);</span>
<span class="nc" id="L955">        writeOperand(x3);</span>
<span class="nc" id="L956">        writeOperand(y3);</span>
<span class="nc" id="L957">        writeOperator(OperatorName.CURVE_TO_REPLICATE_INITIAL_POINT);</span>
<span class="nc" id="L958">    }</span>

    /**
     * Append a cubic Bézier curve to the current path. The curve extends from the current point to
     * the point (x3, y3), using (x1, y1) and (x3, y3) as the Bézier control points.
     *
     * @param x1 x coordinate of the point 1
     * @param y1 y coordinate of the point 1
     * @param x3 x coordinate of the point 3
     * @param y3 y coordinate of the point 3
     * @throws IOException If the content stream could not be written.
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void curveTo1(float x1, float y1, float x3, float y3) throws IOException
    {
<span class="nc bnc" id="L973" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L975">            throw new IllegalStateException(&quot;Error: curveTo1 is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L977">        writeOperand(x1);</span>
<span class="nc" id="L978">        writeOperand(y1);</span>
<span class="nc" id="L979">        writeOperand(x3);</span>
<span class="nc" id="L980">        writeOperand(y3);</span>
<span class="nc" id="L981">        writeOperator(OperatorName.CURVE_TO_REPLICATE_FINAL_POINT);</span>
<span class="nc" id="L982">    }</span>

    /**
     * Move the current position to the given coordinates.
     *
     * @param x The x coordinate.
     * @param y The y coordinate.
     * @throws IOException If the content stream could not be written.
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void moveTo(float x, float y) throws IOException
    {
<span class="pc bpc" id="L994" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L996">            throw new IllegalStateException(&quot;Error: moveTo is not allowed within a text block.&quot;);</span>
        }
<span class="fc" id="L998">        writeOperand(x);</span>
<span class="fc" id="L999">        writeOperand(y);</span>
<span class="fc" id="L1000">        writeOperator(OperatorName.MOVE_TO);</span>
<span class="fc" id="L1001">    }</span>

    /**
     * Draw a line from the current position to the given coordinates.
     *
     * @param x The x coordinate.
     * @param y The y coordinate.
     * @throws IOException If the content stream could not be written.
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void lineTo(float x, float y) throws IOException
    {
<span class="pc bpc" id="L1013" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1015">            throw new IllegalStateException(&quot;Error: lineTo is not allowed within a text block.&quot;);</span>
        }
<span class="fc" id="L1017">        writeOperand(x);</span>
<span class="fc" id="L1018">        writeOperand(y);</span>
<span class="fc" id="L1019">        writeOperator(OperatorName.LINE_TO);</span>
<span class="fc" id="L1020">    }</span>

    /**
     * Stroke the path.
     * 
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void stroke() throws IOException
    {
<span class="pc bpc" id="L1030" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1032">            throw new IllegalStateException(&quot;Error: stroke is not allowed within a text block.&quot;);</span>
        }
<span class="fc" id="L1034">        writeOperator(OperatorName.STROKE_PATH);</span>
<span class="fc" id="L1035">    }</span>

    /**
     * Close and stroke the path.
     * 
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void closeAndStroke() throws IOException
    {
<span class="pc bpc" id="L1045" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1047">            throw new IllegalStateException(&quot;Error: closeAndStroke is not allowed within a text block.&quot;);</span>
        }
<span class="fc" id="L1049">        writeOperator(OperatorName.CLOSE_AND_STROKE);</span>
<span class="fc" id="L1050">    }</span>

    /**
     * Fills the path using the nonzero winding number rule.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void fill() throws IOException
    {
<span class="pc bpc" id="L1060" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1062">            throw new IllegalStateException(&quot;Error: fill is not allowed within a text block.&quot;);</span>
        }
<span class="fc" id="L1064">        writeOperator(OperatorName.FILL_NON_ZERO);</span>
<span class="fc" id="L1065">    }</span>

    /**
     * Fills the path using the even-odd winding rule.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void fillEvenOdd() throws IOException
    {
<span class="nc bnc" id="L1075" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1077">            throw new IllegalStateException(&quot;Error: fillEvenOdd is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L1079">        writeOperator(OperatorName.FILL_EVEN_ODD);</span>
<span class="nc" id="L1080">    }</span>

    /**
     * Fill and then stroke the path, using the nonzero winding number rule to determine the region
     * to fill. This shall produce the same result as constructing two identical path objects,
     * painting the first with {@link #fill() } and the second with {@link #stroke() }.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void fillAndStroke() throws IOException
    {
<span class="nc bnc" id="L1092" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1094">            throw new IllegalStateException(&quot;Error: fillAndStroke is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L1096">        writeOperator(OperatorName.FILL_NON_ZERO_AND_STROKE);</span>
<span class="nc" id="L1097">    }</span>

    /**
     * Fill and then stroke the path, using the even-odd rule to determine the region to
     * fill. This shall produce the same result as constructing two identical path objects, painting
     * the first with {@link #fillEvenOdd() } and the second with {@link #stroke() }.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void fillAndStrokeEvenOdd() throws IOException
    {
<span class="nc bnc" id="L1109" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1111">            throw new IllegalStateException(&quot;Error: fillAndStrokeEvenOdd is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L1113">        writeOperator(OperatorName.FILL_EVEN_ODD_AND_STROKE);</span>
<span class="nc" id="L1114">    }</span>

    /**
     * Close, fill, and then stroke the path, using the nonzero winding number rule to determine the
     * region to fill. This shall have the same effect as the sequence {@link #closePath() }
     * and then {@link #fillAndStroke() }.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void closeAndFillAndStroke() throws IOException
    {
<span class="nc bnc" id="L1126" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1128">            throw new IllegalStateException(&quot;Error: closeAndFillAndStroke is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L1130">        writeOperator(OperatorName.CLOSE_FILL_NON_ZERO_AND_STROKE);</span>
<span class="nc" id="L1131">    }</span>

    /**
     * Close, fill, and then stroke the path, using the even-odd rule to determine the region to
     * fill. This shall have the same effect as the sequence {@link #closePath() }
     * and then {@link #fillAndStrokeEvenOdd() }.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void closeAndFillAndStrokeEvenOdd() throws IOException
    {
<span class="nc bnc" id="L1143" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1145">            throw new IllegalStateException(&quot;Error: closeAndFillAndStrokeEvenOdd is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L1147">        writeOperator(OperatorName.CLOSE_FILL_EVEN_ODD_AND_STROKE);</span>
<span class="nc" id="L1148">    }</span>

    /**
     * Fills the clipping area with the given shading.
     *
     * @param shading Shading resource
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void shadingFill(PDShading shading) throws IOException
    {
<span class="nc bnc" id="L1159" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1161">            throw new IllegalStateException(&quot;Error: shadingFill is not allowed within a text block.&quot;);</span>
        }

<span class="nc" id="L1164">        writeOperand(resources.add(shading));</span>
<span class="nc" id="L1165">        writeOperator(OperatorName.SHADING_FILL);</span>
<span class="nc" id="L1166">    }</span>

    /**
     * Closes the current subpath.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void closePath() throws IOException
    {
<span class="pc bpc" id="L1176" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1178">            throw new IllegalStateException(&quot;Error: closePath is not allowed within a text block.&quot;);</span>
        }
<span class="fc" id="L1180">        writeOperator(OperatorName.CLOSE_PATH);</span>
<span class="fc" id="L1181">    }</span>

    /**
     * Intersects the current clipping path with the current path, using the nonzero rule.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void clip() throws IOException
    {
<span class="pc bpc" id="L1191" title="1 of 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1193">            throw new IllegalStateException(&quot;Error: clip is not allowed within a text block.&quot;);</span>
        }
<span class="fc" id="L1195">        writeOperator(OperatorName.CLIP_NON_ZERO);</span>
        
        // end path without filling or stroking
<span class="fc" id="L1198">        writeOperator(OperatorName.ENDPATH);</span>
<span class="fc" id="L1199">    }</span>

    /**
     * Intersects the current clipping path with the current path, using the even-odd rule.
     *
     * @throws IOException If the content stream could not be written
     * @throws IllegalStateException If the method was called within a text block.
     */
    public void clipEvenOdd() throws IOException
    {
<span class="nc bnc" id="L1209" title="All 2 branches missed.">        if (inTextMode)</span>
        {
<span class="nc" id="L1211">            throw new IllegalStateException(&quot;Error: clipEvenOdd is not allowed within a text block.&quot;);</span>
        }
<span class="nc" id="L1213">        writeOperator(OperatorName.CLIP_EVEN_ODD);</span>
        
        // end path without filling or stroking
<span class="nc" id="L1216">        writeOperator(OperatorName.ENDPATH);</span>
<span class="nc" id="L1217">    }</span>

    /**
     * Set line width to the given value.
     *
     * @param lineWidth The width which is used for drawing.
     * @throws IOException If the content stream could not be written
     */
    public void setLineWidth(float lineWidth) throws IOException
    {
<span class="fc" id="L1227">        writeOperand(lineWidth);</span>
<span class="fc" id="L1228">        writeOperator(OperatorName.SET_LINE_WIDTH);</span>
<span class="fc" id="L1229">    }</span>

    /**
     * Set the line join style.
     *
     * @param lineJoinStyle 0 for miter join, 1 for round join, and 2 for bevel join.
     * @throws IOException If the content stream could not be written.
     * @throws IllegalArgumentException If the parameter is not a valid line join style.
     */
    public void setLineJoinStyle(int lineJoinStyle) throws IOException
    {
<span class="pc bpc" id="L1240" title="2 of 4 branches missed.">        if (lineJoinStyle &gt;= 0 &amp;&amp; lineJoinStyle &lt;= 2)</span>
        {
<span class="fc" id="L1242">            writeOperand(lineJoinStyle);</span>
<span class="fc" id="L1243">            writeOperator(OperatorName.SET_LINE_JOINSTYLE);</span>
        }
        else
        {
<span class="nc" id="L1247">            throw new IllegalArgumentException(&quot;Error: unknown value for line join style&quot;);</span>
        }
<span class="fc" id="L1249">    }</span>

    /**
     * Set the line cap style.
     *
     * @param lineCapStyle 0 for butt cap, 1 for round cap, and 2 for projecting square cap.
     * @throws IOException If the content stream could not be written.
     * @throws IllegalArgumentException If the parameter is not a valid line cap style.
     */
    public void setLineCapStyle(int lineCapStyle) throws IOException
    {
<span class="pc bpc" id="L1260" title="2 of 4 branches missed.">        if (lineCapStyle &gt;= 0 &amp;&amp; lineCapStyle &lt;= 2)</span>
        {
<span class="fc" id="L1262">            writeOperand(lineCapStyle);</span>
<span class="fc" id="L1263">            writeOperator(OperatorName.SET_LINE_CAPSTYLE);</span>
        }
        else
        {
<span class="nc" id="L1267">            throw new IllegalArgumentException(&quot;Error: unknown value for line cap style&quot;);</span>
        }
<span class="fc" id="L1269">    }</span>

    /**
     * Set the line dash pattern.
     *
     * @param pattern The pattern array
     * @param phase The phase of the pattern
     * @throws IOException If the content stream could not be written.
     */
    public void setLineDashPattern(float[] pattern, float phase) throws IOException
    {
<span class="fc" id="L1280">        write(&quot;[&quot;);</span>
<span class="fc bfc" id="L1281" title="All 2 branches covered.">        for (float value : pattern)</span>
        {
<span class="fc" id="L1283">            writeOperand(value);</span>
        }
<span class="fc" id="L1285">        write(&quot;] &quot;);</span>
<span class="fc" id="L1286">        writeOperand(phase);</span>
<span class="fc" id="L1287">        writeOperator(OperatorName.SET_LINE_DASHPATTERN);</span>
<span class="fc" id="L1288">    }</span>

    /**
     * Set the miter limit.
     *
     * @param miterLimit the new miter limit.
     * @throws IOException If the content stream could not be written.
     * @throws IllegalArgumentException If the parameter is \u2264 0.
     */
    public void setMiterLimit(float miterLimit) throws IOException
    {
<span class="pc bpc" id="L1299" title="1 of 2 branches missed.">        if (miterLimit &lt;= 0.0)</span>
        {
<span class="nc" id="L1301">            throw new IllegalArgumentException(&quot;A miter limit &lt;= 0 is invalid and will not render in Acrobat Reader&quot;);</span>
        }
<span class="fc" id="L1303">        writeOperand(miterLimit);</span>
<span class="fc" id="L1304">        writeOperator(OperatorName.SET_LINE_MITERLIMIT);</span>
<span class="fc" id="L1305">    }</span>

    /**
     * Begin a marked content sequence.
     *
     * @param tag the tag
     * @throws IOException If the content stream could not be written
     */
    public void beginMarkedContent(COSName tag) throws IOException
    {
<span class="nc" id="L1315">        writeOperand(tag);</span>
<span class="nc" id="L1316">        writeOperator(OperatorName.BEGIN_MARKED_CONTENT);</span>
<span class="nc" id="L1317">    }</span>

    /**
     * Begin a marked content sequence with a reference to an entry in the page resources'
     * Properties dictionary.
     *
     * @param tag the tag
     * @param propertyList property list
     * @throws IOException If the content stream could not be written
     */
    public void beginMarkedContent(COSName tag, PDPropertyList propertyList) throws IOException
    {
<span class="fc" id="L1329">        writeOperand(tag);</span>
<span class="fc" id="L1330">        writeOperand(resources.add(propertyList));</span>
<span class="fc" id="L1331">        writeOperator(OperatorName.BEGIN_MARKED_CONTENT_SEQ);</span>
<span class="fc" id="L1332">    }</span>

    /**
     * End a marked content sequence.
     *
     * @throws IOException If the content stream could not be written
     */
    public void endMarkedContent() throws IOException
    {
<span class="fc" id="L1341">        writeOperator(OperatorName.END_MARKED_CONTENT);</span>
<span class="fc" id="L1342">    }</span>

    /**
     * Set an extended graphics state.
     * 
     * @param state The extended graphics state.
     * @throws IOException If the content stream could not be written.
     */
    public void setGraphicsStateParameters(PDExtendedGraphicsState state) throws IOException
    {
<span class="fc" id="L1352">        writeOperand(resources.add(state));</span>
<span class="fc" id="L1353">        writeOperator(OperatorName.SET_GRAPHICS_STATE_PARAMS);</span>
<span class="fc" id="L1354">    }</span>

    /**
     * Write a comment line.
     *
     * @param comment
     * @throws IOException If the content stream could not be written.
     * @throws IllegalArgumentException If the comment contains a newline. This is not allowed,
     * because the next line could be ordinary PDF content.
     */
    public void addComment(String comment) throws IOException
    {
<span class="nc bnc" id="L1366" title="All 4 branches missed.">        if (comment.indexOf('\n') &gt;= 0 || comment.indexOf('\r') &gt;= 0)</span>
        {
<span class="nc" id="L1368">            throw new IllegalArgumentException(&quot;comment should not include a newline&quot;);</span>
        }
<span class="nc" id="L1370">        outputStream.write('%');</span>
<span class="nc" id="L1371">        outputStream.write(comment.getBytes(StandardCharsets.US_ASCII));</span>
<span class="nc" id="L1372">        outputStream.write('\n');</span>
<span class="nc" id="L1373">    }</span>

    /**
     * Writes a real number to the content stream.
     * @param real
     * @throws java.io.IOException
     * @throws IllegalArgumentException if the parameter is not a finite number
     */
    protected void writeOperand(float real) throws IOException
    {
<span class="pc bpc" id="L1383" title="1 of 2 branches missed.">        if (!Float.isFinite(real))</span>
        {
<span class="nc" id="L1385">            throw new IllegalArgumentException(real + &quot; is not a finite number&quot;);</span>
        }
<span class="fc" id="L1387">        int byteCount = NumberFormatUtil.formatFloatFast(real, formatDecimal.getMaximumFractionDigits(), formatBuffer);</span>

<span class="pc bpc" id="L1389" title="1 of 2 branches missed.">        if (byteCount == -1)</span>
        {
            //Fast formatting failed
<span class="nc" id="L1392">            write(formatDecimal.format(real));</span>
        }
        else
        {
<span class="fc" id="L1396">            outputStream.write(formatBuffer, 0, byteCount);</span>
        }
<span class="fc" id="L1398">        outputStream.write(' ');</span>
<span class="fc" id="L1399">    }</span>

    /**
     * Writes an integer number to the content stream.
     * @param integer
     * @throws java.io.IOException
     */
    protected void writeOperand(int integer) throws IOException
    {
<span class="fc" id="L1408">        write(formatDecimal.format(integer));</span>
<span class="fc" id="L1409">        outputStream.write(' ');</span>
<span class="fc" id="L1410">    }</span>

    /**
     * Writes a COSName to the content stream.
     * @param name
     * @throws java.io.IOException
     */
    protected void writeOperand(COSName name) throws IOException
    {
<span class="fc" id="L1419">        name.writePDF(outputStream);</span>
<span class="fc" id="L1420">        outputStream.write(' ');</span>
<span class="fc" id="L1421">    }</span>

    /**
     * Writes a string to the content stream as ASCII.
     * @param text
     * @throws java.io.IOException
     */
    protected void writeOperator(String text) throws IOException
    {
<span class="fc" id="L1430">        outputStream.write(text.getBytes(StandardCharsets.US_ASCII));</span>
<span class="fc" id="L1431">        outputStream.write('\n');</span>
<span class="fc" id="L1432">    }</span>

    /**
     * Writes a string to the content stream as ASCII.
     * @param text
     * @throws java.io.IOException
     */
    protected void write(String text) throws IOException
    {
<span class="fc" id="L1441">        outputStream.write(text.getBytes(StandardCharsets.US_ASCII));</span>
<span class="fc" id="L1442">    }</span>

    /**
     * Writes a byte[] to the content stream.
     * @param data
     * @throws java.io.IOException
     */
    protected void write(byte[] data) throws IOException
    {
<span class="nc" id="L1451">        outputStream.write(data);</span>
<span class="nc" id="L1452">    }</span>
    
    /**
     * Writes a newline to the content stream as ASCII.
     * @throws java.io.IOException
     */
    protected void writeLine() throws IOException
    {
<span class="fc" id="L1460">        outputStream.write('\n');</span>
<span class="fc" id="L1461">    }</span>

    /**
     * Writes binary data to the content stream.
     * @param data
     * @throws java.io.IOException
     */
    protected void writeBytes(byte[] data) throws IOException
    {
<span class="fc" id="L1470">        outputStream.write(data);</span>
<span class="fc" id="L1471">    }</span>

    /**
     * Writes an AffineTransform to the content stream as an array.
     */
    private void writeAffineTransform(AffineTransform transform) throws IOException
    {
<span class="fc" id="L1478">        double[] values = new double[6];</span>
<span class="fc" id="L1479">        transform.getMatrix(values);</span>
<span class="fc bfc" id="L1480" title="All 2 branches covered.">        for (double v : values)</span>
        {
<span class="fc" id="L1482">            writeOperand((float) v);</span>
        }
<span class="fc" id="L1484">    }</span>

    /**
     * Close the content stream.  This must be called when you are done with this object.
     *
     * @throws IOException If the underlying stream has a problem being written to.
     */
    @Override
    public void close() throws IOException
    {
<span class="fc bfc" id="L1494" title="All 2 branches covered.">        if (inTextMode)</span>
        {
<span class="fc" id="L1496">            LOG.warn(&quot;You did not call endText(), some viewers won't display your text&quot;);</span>
        }
<span class="fc" id="L1498">        outputStream.close();</span>
<span class="fc" id="L1499">    }</span>

    protected boolean isOutside255Interval(int val)
    {
<span class="nc bnc" id="L1503" title="All 4 branches missed.">        return val &lt; 0 || val &gt; 255;</span>
    }

    private boolean isOutsideOneInterval(double val)
    {
<span class="pc bpc" id="L1508" title="2 of 4 branches missed.">        return val &lt; 0 || val &gt; 1;</span>
    }

    protected void setStrokingColorSpaceStack(PDColorSpace colorSpace)
    {
<span class="fc bfc" id="L1513" title="All 2 branches covered.">        if (strokingColorSpaceStack.isEmpty())</span>
        {
<span class="fc" id="L1515">            strokingColorSpaceStack.add(colorSpace);</span>
        }
        else
        {
<span class="fc" id="L1519">            strokingColorSpaceStack.pop();</span>
<span class="fc" id="L1520">            strokingColorSpaceStack.push(colorSpace);</span>
        }
<span class="fc" id="L1522">    }</span>

    protected void setNonStrokingColorSpaceStack(PDColorSpace colorSpace)
    {
<span class="fc bfc" id="L1526" title="All 2 branches covered.">        if (nonStrokingColorSpaceStack.isEmpty())</span>
        {
<span class="fc" id="L1528">            nonStrokingColorSpaceStack.add(colorSpace);</span>
        }
        else
        {
<span class="fc" id="L1532">            nonStrokingColorSpaceStack.pop();</span>
<span class="fc" id="L1533">            nonStrokingColorSpaceStack.push(colorSpace);</span>
        }
<span class="fc" id="L1535">    }</span>

    /**
     * Set the character spacing. The value shall be added to the horizontal or vertical component
     * of the glyph's displacement, depending on the writing mode.
     *
     * @param spacing character spacing
     * @throws IOException If the content stream could not be written.
     */
    public void setCharacterSpacing(float spacing) throws IOException
    {
<span class="nc" id="L1546">        writeOperand(spacing);</span>
<span class="nc" id="L1547">        writeOperator(OperatorName.SET_CHAR_SPACING);</span>
<span class="nc" id="L1548">    }</span>

    /**
     * Set the word spacing. The value shall be added to the horizontal or vertical component of the
     * ASCII SPACE character, depending on the writing mode.
     * &lt;p&gt;
     * This will have an effect only with Type1 and TrueType fonts, not with Type0 fonts. The PDF
     * specification tells why: &quot;Word spacing shall be applied to every occurrence of the
     * single-byte character code 32 in a string when using a simple font or a composite font that
     * defines code 32 as a single-byte code. It shall not apply to occurrences of the byte value 32
     * in multiple-byte codes.&quot;
     *
     * @param spacing word spacing
     * @throws IOException If the content stream could not be written.
     */
    public void setWordSpacing(float spacing) throws IOException
    {
<span class="nc" id="L1565">        writeOperand(spacing);</span>
<span class="nc" id="L1566">        writeOperator(OperatorName.SET_WORD_SPACING);</span>
<span class="nc" id="L1567">    }</span>

    /**
     * Set the horizontal scaling to scale / 100.
     *
     * @param scale number specifying the percentage of the normal width. Default value: 100 (normal
     * width).
     * @throws IOException If the content stream could not be written.
     */
    public void setHorizontalScaling(float scale) throws IOException
    {
<span class="nc" id="L1578">        writeOperand(scale);</span>
<span class="nc" id="L1579">        writeOperator(OperatorName.SET_TEXT_HORIZONTAL_SCALING);</span>
<span class="nc" id="L1580">    }</span>

    /**
     * Set the text rendering mode. This determines whether showing text shall cause glyph outlines
     * to be stroked, filled, used as a clipping boundary, or some combination of the three.
     *
     * @param rm The text rendering mode.
     * @throws IOException If the content stream could not be written.
     */
    public void setRenderingMode(RenderingMode rm) throws IOException
    {
<span class="nc" id="L1591">        writeOperand(rm.intValue());</span>
<span class="nc" id="L1592">        writeOperator(OperatorName.SET_TEXT_RENDERINGMODE);</span>
<span class="nc" id="L1593">    }</span>

    /**
     * Set the text rise value, i.e. move the baseline up or down. This is useful for drawing
     * superscripts or subscripts.
     *
     * @param rise Specifies the distance, in unscaled text space units, to move the baseline up or
     * down from its default location. 0 restores the default location.
     * @throws IOException
     */
    public void setTextRise(float rise) throws IOException
    {
<span class="nc" id="L1605">        writeOperand(rise);</span>
<span class="nc" id="L1606">        writeOperator(OperatorName.SET_TEXT_RISE);</span>
<span class="nc" id="L1607">    }</span>

    private byte[] encodeForGsub(GsubWorker gsubWorker,
                                 Set&lt;Integer&gt; glyphIds, PDType0Font font, String text) throws IOException
    {
<span class="fc" id="L1612">        Pattern spaceRegex = Pattern.compile(&quot;\\s&quot;);</span>

        // break the entire chunk of text into words by splitting it with space
<span class="fc" id="L1615">        List&lt;String&gt; words = new CompoundCharacterTokenizer(&quot;\\s&quot;).tokenize(text);</span>

<span class="fc" id="L1617">        ByteArrayOutputStream out = new ByteArrayOutputStream();</span>

<span class="fc bfc" id="L1619" title="All 2 branches covered.">        for (String word : words)</span>
        {
<span class="fc bfc" id="L1621" title="All 2 branches covered.">            if (spaceRegex.matcher(word).matches())</span>
            {
<span class="fc" id="L1623">                out.write(font.encode(word));</span>
            }
            else
            {
<span class="fc" id="L1627">                glyphIds.addAll(applyGSUBRules(gsubWorker, out, font, word));</span>
            }
<span class="fc" id="L1629">        }</span>

<span class="fc" id="L1631">        return out.toByteArray();</span>
    }

    private List&lt;Integer&gt; applyGSUBRules(GsubWorker gsubWorker, ByteArrayOutputStream out, PDType0Font font, String word) throws IOException
    {
<span class="fc" id="L1636">        List&lt;Integer&gt; originalGlyphIds = new ArrayList&lt;&gt;();</span>
<span class="fc" id="L1637">        CmapLookup cmapLookup = font.getCmapLookup();</span>

        // convert characters into glyphIds
<span class="fc bfc" id="L1640" title="All 2 branches covered.">        for (char unicodeChar : word.toCharArray())</span>
        {
<span class="fc" id="L1642">            int glyphId = cmapLookup.getGlyphId(unicodeChar);</span>
<span class="pc bpc" id="L1643" title="1 of 2 branches missed.">            if (glyphId &lt;= 0)</span>
            {
<span class="nc" id="L1645">                throw new IllegalStateException(</span>
                        &quot;could not find the glyphId for the character: &quot; + unicodeChar);
            }
<span class="fc" id="L1648">            originalGlyphIds.add(glyphId);</span>
        }

<span class="fc" id="L1651">        List&lt;Integer&gt; glyphIdsAfterGsub = gsubWorker.applyTransforms(originalGlyphIds);</span>

<span class="fc bfc" id="L1653" title="All 2 branches covered.">        for (Integer glyphId : glyphIdsAfterGsub)</span>
        {
<span class="fc" id="L1655">            out.write(font.encodeGlyphId(glyphId));</span>
<span class="fc" id="L1656">        }</span>

<span class="fc" id="L1658">        return glyphIdsAfterGsub;</span>

    }
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.8.202204050719</span></div></body></html>