<?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> > <a href="index.source.html" class="el_package">org.apache.pdfbox.pdmodel</a> > <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 "License"); 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 "AS IS" 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<PDFont> fontStack = new ArrayDeque<>();</span> <span class="fc" id="L89"> protected final Deque<PDColorSpace> nonStrokingColorSpaceStack = new ArrayDeque<>();</span> <span class="fc" id="L90"> protected final Deque<PDColorSpace> strokingColorSpaceStack = new ArrayDeque<>();</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<PDType0Font, GsubWorker> gsubWorkers = new HashMap<>();</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("Error: Nested beginText() calls are not allowed.");</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("Error: You must call beginText() before calling endText.");</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("Using the subsetted font '" + font.getName() +</span> "' without a PDDocument context; call subset() before saving"); } } <span class="fc bfc" id="L193" title="All 4 branches covered."> else if (!font.isEmbedded() && !font.isStandard14())</span> { <span class="fc" id="L195"> LOG.warn("attempting to use font '" + font.getName() + "' that isn't embedded");</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("[");</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("Argument must consist of array of Float and String types");</span> } } <span class="nc" id="L247"> write("] ");</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(" ");</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("Must call beginText() before showText()");</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("Must call setFont() before showText()");</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<Integer> glyphIds = new HashSet<>();</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 < 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("Must call beginText() before newLine()");</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("Error: must call beginText() before newLineAtOffset()");</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("Error: must call beginText() before setTextMatrix");</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("Error: drawImage is not allowed within a text block.");</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("Error: drawImage is not allowed within a text block.");</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("Error: drawImage is not allowed within a text block.");</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("\n /W ");</span> <span class="fc" id="L501"> sb.append(inlineImage.getWidth());</span> <span class="fc" id="L503"> sb.append("\n /H ");</span> <span class="fc" id="L504"> sb.append(inlineImage.getHeight());</span> <span class="fc" id="L506"> sb.append("\n /CS ");</span> <span class="fc" id="L507"> sb.append("/");</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 && decodeArray.size() > 0)</span> { <span class="fc" id="L513"> sb.append("\n /D ");</span> <span class="fc" id="L514"> sb.append("[");</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(" ");</span> <span class="fc" id="L519"> }</span> <span class="fc" id="L520"> sb.append("]");</span> } <span class="pc bpc" id="L523" title="1 of 2 branches missed."> if (inlineImage.isStencil())</span> { <span class="fc" id="L525"> sb.append("\n /IM true");</span> } <span class="fc" id="L528"> sb.append("\n /BPC ");</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("Error: drawForm is not allowed within a text block.");</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("Error: Modifying the current transformation matrix is not allowed within text objects.");</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("Error: Saving the graphics state is not allowed within text objects.");</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("Error: Restoring the graphics state is not allowed within text objects.");</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("Parameters must be within 0..1, but are "</span> <span class="nc" id="L715"> + String.format("(%.2f,%.2f,%.2f)", 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("Parameters must be within 0..1, but are "</span> <span class="nc" id="L739"> + String.format("(%.2f,%.2f,%.2f,%.2f)", 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("Parameter must be within 0..1, but is " + 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("Parameters must be within 0..1, but are "</span> <span class="nc" id="L834"> + String.format("(%.2f,%.2f,%.2f)", 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("Parameters must be within 0..1, but are "</span> <span class="nc" id="L857"> + String.format("(%.2f,%.2f,%.2f,%.2f)", 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("Parameter must be within 0..1, but is " + 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("Error: addRect is not allowed within a text block.");</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("Error: curveTo is not allowed within a text block.");</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("Error: curveTo2 is not allowed within a text block.");</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("Error: curveTo1 is not allowed within a text block.");</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("Error: moveTo is not allowed within a text block.");</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("Error: lineTo is not allowed within a text block.");</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("Error: stroke is not allowed within a text block.");</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("Error: closeAndStroke is not allowed within a text block.");</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("Error: fill is not allowed within a text block.");</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("Error: fillEvenOdd is not allowed within a text block.");</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("Error: fillAndStroke is not allowed within a text block.");</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("Error: fillAndStrokeEvenOdd is not allowed within a text block.");</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("Error: closeAndFillAndStroke is not allowed within a text block.");</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("Error: closeAndFillAndStrokeEvenOdd is not allowed within a text block.");</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("Error: shadingFill is not allowed within a text block.");</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("Error: closePath is not allowed within a text block.");</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("Error: clip is not allowed within a text block.");</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("Error: clipEvenOdd is not allowed within a text block.");</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 >= 0 && lineJoinStyle <= 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("Error: unknown value for line join style");</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 >= 0 && lineCapStyle <= 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("Error: unknown value for line cap style");</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("[");</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("] ");</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 <= 0.0)</span> { <span class="nc" id="L1301"> throw new IllegalArgumentException("A miter limit <= 0 is invalid and will not render in Acrobat Reader");</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') >= 0 || comment.indexOf('\r') >= 0)</span> { <span class="nc" id="L1368"> throw new IllegalArgumentException("comment should not include a newline");</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 + " is not a finite number");</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("You did not call endText(), some viewers won't display your text");</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 < 0 || val > 255;</span> } private boolean isOutsideOneInterval(double val) { <span class="pc bpc" id="L1508" title="2 of 4 branches missed."> return val < 0 || val > 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. * <p> * This will have an effect only with Type1 and TrueType fonts, not with Type0 fonts. The PDF * specification tells why: "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." * * @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<Integer> glyphIds, PDType0Font font, String text) throws IOException { <span class="fc" id="L1612"> Pattern spaceRegex = Pattern.compile("\\s");</span> // break the entire chunk of text into words by splitting it with space <span class="fc" id="L1615"> List<String> words = new CompoundCharacterTokenizer("\\s").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<Integer> applyGSUBRules(GsubWorker gsubWorker, ByteArrayOutputStream out, PDType0Font font, String word) throws IOException { <span class="fc" id="L1636"> List<Integer> originalGlyphIds = new ArrayList<>();</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 <= 0)</span> { <span class="nc" id="L1645"> throw new IllegalStateException(</span> "could not find the glyphId for the character: " + unicodeChar); } <span class="fc" id="L1648"> originalGlyphIds.add(glyphId);</span> } <span class="fc" id="L1651"> List<Integer> 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>