* @copyright (c) 2010 Australian Consortium for Social and Political Research Incorporated (ACSPRI) * @since 2010-09-02 * @link http://www.acspri.org.au/software * @link http://quexml.sourceforge.net * @link http://quexf.sourceforge.net */ class quexmlpdf extends pdf { /** * Define an inch in MM * * @const float Defaults to 25.4 */ const INCH_IN_MM = 25.4; /** * Language for translation */ protected $language = "en"; /** * Pixels per inch of exported document * * @var int Defaults to 300. */ protected $ppi = 300; /** * Whether a page break has occured * Should be a private var but crash occurs on PHP 5.1.6, see Limesurvey Bug 5824 * @var bool */ protected $pageBreakOccured; /** * Corner border (the number of mm between the edge of the page and the start of the document) * * @var int Defaults to 15. * @since 2010-09-02 */ protected $cornerBorder = 14; /** * The length in MM of a corner line * * @var mixed Defaults to 20. * @since 2010-09-20 */ protected $cornerLength = 20; /** * The width in MM of a corner line * * @var mixed Defaults to 0.5. * @since 2010-09-20 */ protected $cornerWidth = 0.5; /** * The TCPDF barcode type * * @var bool Defaults to 'I25'. * @since 2010-09-20 * @see write1DBarcode */ protected $barcodeType = 'I25'; /** * The x position in MM of the barcode * * @var bool Defaults to 138. * @since 2010-09-20 * @deprecated * @see $barcodeMarginX */ //protected $barcodeX = 138; /** * The distance between the right hand page border and * the end of the barcode in MM * * @var bool Defaults to 23. * @since 2011-10-25 */ protected $barcodeMarginX = 23; /** * Y position of barcode in mm * * @var bool Defaults to 6. * @since 2010-09-20 */ protected $barcodeY = 6; /** * Width of the barcode in mm * * @var bool Defaults to 49. * @since 2010-09-20 */ protected $barcodeW = 49; /** * Height of the barcode in mm * * @var bool Defaults to 6. * @since 2010-09-20 */ protected $barcodeH = 6; /** * The questionnaire ID of this form * * @var mixed Defaults to 1. * @since 2010-09-20 */ protected $questionnaireId = 1; /** * The length of a the id portion barcode * * @var int Defaults to 6. * @since 2010-09-20 * @see $pageLength */ protected $idLength = 6; /** * The length of the page portion of the barcode * * @var mixed Defaults to 2. * @since 2010-09-20 * @see $idLength */ protected $pageLength = 2; /** * width of the question title column in MM * * @var mixed Defaults to 14. * @since 2010-09-20 */ protected $questionTitleWidth = 14; /** * The suffix of the question title. i.e. A15. (the . is the suffix) * * @var mixed Defaults to ".". * @since 2012-01-31 */ protected $questionTitleSuffix = "."; /** * Width of question text in MM * * @var mixed Defaults to 120. * @since 2010-09-20 * @deprecated */ //protected $questionTextWidth = 120; /** * Right margin of question text in MM * * @var mixed Defaults to 40. * @since 2012-01-11 * @see $questionTextWidth */ protected $questionTextRightMargin = 40; /** * Height of the border between questions in MM * * @var mixed Defaults to 1. * @since 2010-09-20 */ protected $questionBorderBottom = 1; /** * Border after a help before directive * * @var mixed Defaults to 3. * @since 2012-01-31 */ protected $helpBeforeBorderBottom = 3; /** * Border before a help before directive * * @var mixed Defaults to 3. * @since 2012-01-31 */ protected $helpBeforeBorderTop = 3; /** * Width of the skip column area (where skip text is written) * * @var string Defaults to 20. * @since 2010-09-20 */ protected $skipColumnWidth = 20; /** * The default style for the text of the questionnaire * * @var string Defaults to "". * @since 2010-09-16 */ protected $style = ""; /** * Width of the area of each single response * * @var string Defaults to 10. * @since 2010-09-20 * Height of the area of a single response where displayed horizontally * * @var string Defaults to 10.5. * @since 2011-12-20 */ protected $singleResponseHorizontalHeight = 10.5; /** * The maximum number of lines of text to display * in a horizontal single response before adding additional space * * @var string Defaults to 2. * @since 2013-05-02 * @see $singleResponseHorizontalHeight */ protected $singleResponseHorizontalMaxLines = 2; /** * Height of the are of each single response (includes guiding lines) * * @var string Defaults to 9. * @since 2010-09-20 */ protected $singleResponseAreaHeight = 9; /** * Width of a single response box * * @var string Defaults to 5. * @since 2010-09-20 */ protected $singleResponseBoxWidth = 5; /** * Height of a single response box * * @var string Defaults to 5. * @since 2010-09-20 */ protected $singleResponseBoxHeight = 5; /** * Width of a response boxes border * * @var string Defaults to 0.1. * @since 2010-09-20 */ protected $singleResponseBoxBorder = 0.15; /** * Length of the "eye guide" for a vertical response box * * @var string Defaults to 1. * @since 2010-09-20 */ protected $singleResponseBoxLineLength = 1; /** * Vertical area taken up by a response box * * @var string Defaults to 15. * @since 2010-09-20 */ protected $singleResponseVerticalAreaWidth = 13; /** * Vertical area taken up by a "small" vertical response area * * @var string Defaults to 9. * @since 2010-09-20 */ protected $singleResponseVerticalAreaWidthSmall = 9; /** * Maximum number of horizontal boxes to display normally before shrinking horizontal area width * * @var int Defaults to 10. * @since 2010-09-08 */ protected $singleResponseHorizontalMax = 10; /** * Allows all single choice horizontal arrays to be split over multiple pages/columns * Can override with "split" attribute on "response" in queXML * * @var bool Defaults to false. * @since 2012-08-10 */ protected $allowSplittingSingleChoiceHorizontal = true; /** * Allows all single choice vertical arrays to be split over multiple pages/columns * Can override with "split" attribute on "response" in queXML * * @var bool Defaults to false. * @since 2013-10-24 */ protected $allowSplittingSingleChoiceVertical = true; /** * If splitting is allowed for single choice vertical, only split if there is at * least these many categories * * @var int Defaults to 5. * @since 2013-12-13 */ protected $minSplittingSingleChoiceVertical = 25; /** * Allows multiple responses to the same question to be split over multiple pages/columns * Can override with "split" attribute on "question" in queXML * * @var bool Defaults to false. * @since 2013-10-25 */ protected $allowSplittingResponses = true; /** * Allows vertical matrix texts to be split over multiple pages/columns * Can override with "split" attribute on "response" in queXML * * @var bool Defaults to false. * @since 2013-10-25 */ protected $allowSplittingMatrixText = true; /** * Allows matrix VAS items to be split over multiple pages/columns * Can override with "split" attribute on "response" in queXML * * @var bool Defaults to false. * @since 2013-10-25 */ protected $allowSplittingVas = false; /** * The height of an arrow * * @var array Defaults to 3. * @since 2010-09-20 */ protected $arrowHeight = 3; /** * The width of a text response box * * @var mixed Defaults to 6. * @since 2010-09-20 */ protected $textResponseWidth = 6; /** * The border width of a text response box * * @var mixed Defaults to 0.15. Any less than this may produce printing problems * @since 2010-09-20 */ protected $textResponseBorder = 0.15; /** * The height of a text response box * * @var mixed Defaults to 8. * @since 2010-09-20 */ protected $textResponseHeight = 8; /** * The height of a pre-filled response barcode * * @var bool Defaults to 6. * @since 2012-06-22 */ protected $barcodeResponseHeight = 6; /** * The maximum number of text responses per line * * @var mixed Defaults to 24. * @since 2010-09-20 * @deprecated * @see $textResponseMarginX */ //protected $textResponsesPerLine = 24; /** * The left hand margin of text responses to auto calculate responses * per line (mm) * * @var mixed Defaults to 13. * @since 2011-10-25 */ protected $textResponseMarginX = 13; /** * Maximum number of text responses boxes where the label should appear on the same line * * @var mixed Defaults to 16. * @since 2010-09-20 * @deprecated * @see $labelTextResponsesSameLineMarginX */ //protected $labelTextResponsesSameLine = 16; /** * The left hand margin of text responses to auto calculated responses * per line where the label should appear on the same line (mm) * * @var mixed Defaults to 62. * @since 2011-10-25 */ protected $labelTextResponsesSameLineMarginX = 62; /** * The gap between multi line text responses * * @var mixed Defaults to 1. * @since 2010-09-20 */ protected $textResponseLineSpacing = 1; /** * The vertical gap between subquestions in mm * * @var string Defaults to 2. * @since 2010-09-02 */ protected $subQuestionLineSpacing = 2; /** * The multiplier from long text response width specified to the height in mm * * @var mixed Defaults to 1. * @since 2010-09-20 */ protected $longTextResponseHeightMultiplier = 1; /** * Width of a long text response box * * @var mixed Defaults to 145. * @since 2010-09-20 * @deprecated * @see drawLongText() for the new calculation of long text box width */ //protected $longTextResponseWidth = 145; /** * Default number of characters to store in a long text field * * @var int Default is 1024; * @since 2010-09-02 */ protected $longTextStorageWidth = 1024; /** * The number of columns to display the sections/questions in on each page * * @var int Defaults to 1. * @since 2012-05-30 */ protected $columns = 1; /** * The width of the border between columns * * @var int Defaults to 1. * @since 2012-05-31 */ protected $columnBorder = 1; /** * The layout of the form for importing in to queXF * * @var array Defaults to empty array * @link http://quexf.sourceforge.net/ */ protected $layout = array(); /** * Array to store section information for layout * * @var array Defaults to empty array * @since 2010-09-02 */ protected $section = array(); /** * An array of key: skip target, value: last originating question * that skips to the target * * @var string Defaults to array(). * @since 2012-01-31 */ protected $skipToRegistry = array(); /** * Page counter pointer (links to barcode id of page) * * @var mixed Defaults to "". * @since 2010-09-02 */ protected $layoutCP = ""; /** * Section counter pointer * * @var string Defaults to 0. * @since 2010-09-02 */ protected $sectionCP = 0; /** * Box group counter pointer * * @var bool Defaults to 0. * @since 2010-09-02 */ protected $boxGroupCP = 0; /** * Box counter pointer * * @var int Defaults to 0. */ protected $boxCP = 0; /** * Column counter pointer (current column) * * @var mixed Defaults to 0. * @since 2012-05-30 */ protected $columnCP = 0; /** * Background colour of a question * * @var bool Defaults to array(220,220,220). * @since 2010-09-15 */ protected $backgroundColourQuestion = array(241); /** * The bacground colour of a section * * @var bool Defaults to array(200,200,200). * @since 2010-09-20 */ protected $backgroundColourSection = array(221); /** * Empty background colour * * @var bool Defaults to array(255,255,255). * @since 2010-09-20 */ protected $backgroundColourEmpty = array(255); /** * The colour of a line/fill * * @var mixed Defaults to array(0,0,0). * @since 2010-09-20 */ protected $lineColour = array(0); /** * Text colour in grayscale * * @var mixed Defaults to 0. * @since 2012-04-16 */ protected $textColour = 0; /** * The text to display before a skip * * @var string Defaults to "Skip to ". * @since 2010-09-16 */ protected $skipToText = "Skip to "; /** * Should fonts be embedded in the document? * * @var mixed Defaults to true. * @since 2010-09-20 */ protected $embedFonts = true; /** * Height in MM of a VAS response * * @var mixed Defaults to 8. * @since 2010-09-20 */ protected $vasAreaHeight = 8; /** * Width of a VAS line * * @var mixed Defaults to 0.5. * @since 2010-09-20 */ protected $vasLineWidth = 0.5; /** * The width of a line for the default value * * @var double Defaults to 0.5. * @since 2012-06-14 */ protected $defaultValueLineWidth = 0.5; /** * Height of the VAS ending lines in mm * * @var mixed Defaults to 4. * @since 2010-09-20 */ protected $vasHeight = 4; /** * Length of the vas line itself * * @var mixed Defaults to 100. * @since 2010-09-20 */ protected $vasLength = 100; /** * The number of increments stored on a vas line * * @var mixed Defaults to 100. * @since 2010-09-20 */ protected $vasIncrements = 100; /** * The text to separate parent text and subquestion text * * @var string Defaults to " : ". * @since 2010-09-22 */ protected $subQuestionTextSeparator = " : "; /** * The top margin for questionnaireInfo section * * @var mixed Defaults to 5. * @since 2010-10-29 */ protected $questionnaireInfoMargin = 5; /** * Height of a response label * * @var resource Defaults to 10. * @since 2010-11-05 */ protected $responseLabelHeight = 10; /** * Font size for response label * * @var resource Defaults to 8. * @since 2010-11-05 */ protected $responseLabelFontSize = 7.5; /** * A smaller font size for response labels where otherwise will break the line * * @var resource Defaults to 6. * @since 2012-03-30 */ protected $responseLabelFontSizeSmall = 6.5; /** * Reduce the font size of a response label if any words are longer than this * * @var resource Defaults to 7. * @since 2012-03-30 */ protected $responseLabelSmallWordLength = 7; /** * Font size for response text * * @var resource Defaults to 10. * @since 2010-11-05 */ protected $responseTextFontSize = 10; /** * Font size of the skip to text * * @var string Defaults to 8. * @since 2010-11-05 */ protected $skipToTextFontSize = 8; /** * Default font * * @var string Defaults to 'freeserif'. * @since 2010-11-05 */ protected $defaultFont = 'freeserif'; /** * Height of a section break in mm * * @var string Defaults to 18. * @since 2010-11-05 */ protected $sectionHeight = 18; public function setLanguage($language) { if (!empty($language)) $this->language = $language; } /** * Return the length of the longest word * * @param mixed $txt * * @return int Length of longest word * @author Adam Zammit * @since 2012-03-30 */ protected function wordLength($txt) { $words = explode(' ', $txt); $length = 0; foreach($words as $v) { if(strlen($v) > $length) $length = strlen($v); } return $length; } /** * Add a box group to the page layout system * * VALUES(0, 'Temporary'); * VALUES(1, 'Single choice'); * VALUES(2, 'Multiple choice'); * VALUES(3, 'Text'); * VALUES(4, 'Number'); * VALUES(5, 'Barcode'); * VALUES(6, 'Long text'); * * @param int $type The type of box group for verification purposes * @param string $varname The variable name * @param string $label The label for the box group Optional, defaults to "". * @param int $width The width of this group Optional, defaults to 1. * * @author Adam Zammit * @since 2010-09-02 */ protected function addBoxGroup($type,$varname,$label = "", $width = 1) { $this->boxGroupCP++; $this->layout[$this->layoutCP]['boxgroup'][$this->boxGroupCP] = array( 'type' => $type, 'width' => $width, 'varname' => $varname, 'sortorder' => $this->boxGroupCP, 'label' => $label, 'groupsection' => $this->sectionCP, 'box' => array()); } /** * Add a new box group which is a copy of the previous one (if exists) * * @author Adam Zammit * @since 2012-03-26 */ protected function addBoxGroupCopyPrevious() { if (isset($this->layout[$this->layoutCP]['boxgroup'][$this->boxGroupCP])) { $a = $this->layout[$this->layoutCP]['boxgroup'][$this->boxGroupCP]; $this->addBoxGroup($a['type'],$a['varname'],$a['label']); } } /** * Add a box to the page layout system * * @param int $tlx Top left X * @param int $tly Top left Y * @param int $brx Bottom right X * @param int $bry Bottom right Y * @param string $value Optional, defaults to "". * @param string $label Optional, defaults to "". * */ protected function addBox($tlx,$tly,$brx,$bry,$value = "",$label = "") { $this->boxCP++; $this->layout[$this->layoutCP]['boxgroup'][$this->boxGroupCP]['box'][] = array( 'tlx' => $this->mm2px($tlx), 'tly' => $this->mm2px($tly), 'brx' => $this->mm2px($brx), 'bry' => $this->mm2px($bry), 'value' => $value, 'label'=> $label, ); //Update the width of the parent boxgroup given its type and this additional box $type = $this->layout[$this->layoutCP]['boxgroup'][$this->boxGroupCP]['type']; $count = count($this->layout[$this->layoutCP]['boxgroup'][$this->boxGroupCP]['box']); $width = $this->layout[$this->layoutCP]['boxgroup'][$this->boxGroupCP]['width']; switch($type){ case 1: //Single choice case 2: //Multiple choice if (strlen($value) > $width) $width = strlen($value); if (strlen($count) > $width) $width = strlen($count); break; case 3: //Text case 4: //Numbers $width = $count; break; case 6: //Longtext $width = $this->longTextStorageWidth; } $this->layout[$this->layoutCP]['boxgroup'][$this->boxGroupCP]['width'] = $width; } /** * Set allow splitting * * @param bool $allow Whether to allow or not (default true) * * @author Adam Zammit * @since 2013-10-25 */ public function setAllowSplittingSingleChoiceVertical($allow = true) { if ($allow) $this->allowSplittingSingleChoiceVertical = true; else $this->allowSplittingSingleChoiceVertical = false; } /** * Get allow splitting * * @return bool Whether to allow or not * * @author Adam Zammit * @since 2013-10-25 */ public function getAllowSplittingSingleChoiceVertical() { return $this->allowSplittingSingleChoiceVertical; } /** * Set allow splitting * * @param bool $allow Whether to allow or not (default true) * * @author Adam Zammit * @since 2013-10-25 */ public function setAllowSplittingSingleChoiceHorizontal($allow = true) { if ($allow) $this->allowSplittingSingleChoiceHorizontal = true; else $this->allowSplittingSingleChoiceHorizontal = false; } /** * Get allow splitting * * @return bool Whether to allow or not * * @author Adam Zammit * @since 2013-10-25 */ public function getAllowSplittingSingleChoiceHorizontal() { return $this->allowSplittingSingleChoiceHorizontal; } /** * Set allow splitting * * @param bool $allow Whether to allow or not (default true) * * @author Adam Zammit * @since 2013-10-25 */ public function setAllowSplittingVas($allow = true) { if ($allow) $this->allowSplittingVas = true; else $this->allowSplittingVas = false; } /** * Get allow splitting * * @return bool Whether to allow or not * * @author Adam Zammit * @since 2013-10-25 */ public function getAllowSplittingVas() { return $this->allowSplittingVas; } /** * Set allow splitting * * @param bool $allow Whether to allow or not (default true) * * @author Adam Zammit * @since 2013-10-25 */ public function setAllowSplittingMatrixText($allow = true) { if ($allow) $this->allowSplittingMatrixText = true; else $this->allowSplittingMatrixText = false; } /** * Get allow splitting * * @return bool Whether to allow or not * * @author Adam Zammit * @since 2013-10-25 */ public function getAllowSplittingMatrixText() { return $this->allowSplittingMatrixText; } /** * Set allow splitting * * @param bool $allow Whether to allow or not (default true) * * @author Adam Zammit * @since 2013-10-25 */ public function setAllowSplittingResponses($allow = true) { if ($allow) $this->allowSplittingResponses = true; else $this->allowSplittingResponses = false; } /** * Get allow splitting * * @return bool Whether to allow or not * * @author Adam Zammit * @since 2013-10-25 */ public function getAllowSplittingResponses() { return $this->allowSplittingResponses; } /** * Set the minimum section height * * @param int $height The minimum height of a section * * @author Adam Zammit * @since 2013-07-30 */ public function setSectionHeight($height) { $height = intval($height); if ($height < 0) $height = 1; $this->sectionHeight = $height; } /** * Get the section height * * @return The section height * @author Adam Zammit * @since 2013-07-30 */ public function getSectionHeight() { return $this->sectionHeight; } /** * Get the response label font sizes (normal and small) * * @return array containing the normal font size as the first element and small as second * @author Adam Zammit * @since 2013-04-10 */ public function getResponseLabelFontSize() { return array($this->responseLabelFontSize,$this->responseLabelFontSizeSmall); } /** * Set the response label font sizes * * @param array $sizes normal font size first then small * * @author Adam Zammit * @since 2013-04-10 */ public function setResponseLabelFontSize($sizes) { $this->responseLabelFontSize = intval($sizes[0]); $this->responseLabelFontSizeSmall = intval($sizes[1]); } /** * Get the response text font size * * @return int The response text font size * @author Adam Zammit * @since 2013-04-10 */ public function getResponseTextFontSize() { return $this->responseTextFontSize; } /** * Set the response text font size * * @param int $size * * @author Adam Zammit * @since 2013-04-10 */ public function setResponseTextFontSize($size) { $this->responseTextFontSize = intval($size); } /** * Get the style without any HTML/etc formatting * * @return string The style without HTML or tabs * @author Adam Zammit * @since 2013-04-10 */ public function getStyle() { return strip_tags(str_replace("\t","",$this->style)); } /** * Set the CSS styling of some questionnaire elements * * @param string $style The CSS styling of some questionnire elements * * @return none * @author Adam Zammit * @since 2013-04-10 */ public function setStyle($style) { $this->style = ""; } /** * Export the layout as an XML file * * @return string The XML layout in queXF Banding XML format * @author Adam Zammit * @since 2010-09-20 */ public function getLayout() { $doc = new DomDocument('1.0'); $root = $doc->createElement('queXF'); $q = $doc->createElement('questionnaire'); $id = $doc->createElement('id'); $value = $doc->createTextNode($this->questionnaireId); $id->appendChild($value); $q->appendChild($id); foreach($this->section as $key => $val) { $s = $doc->createElement('section'); $s->setAttribute('id',$key); foreach ($val as $sk => $sv) { $tmpe = $doc->createElement($sk); $tmpv = $doc->createTextNode($sv); $tmpe->appendChild($tmpv); $s->appendChild($tmpe); } $q->appendChild($s); } foreach($this->layout as $key => $val) { $p = $doc->createElement('page'); foreach ($val as $pk => $pv) { if ($pk != 'boxgroup') { $tmpe = $doc->createElement($pk); $tmpv = $doc->createTextNode($pv); $tmpe->appendChild($tmpv); $p->appendChild($tmpe); } } foreach ($val['boxgroup'] as $bg) { $bgE = $doc->createElement('boxgroup'); foreach ($bg as $pk => $pv) { if ($pk == 'groupsection') { $gs = $doc->createElement('groupsection'); $gs->setAttribute('idref',$pv); $bgE->appendChild($gs); } else if ($pk != 'box') { $tmpe = $doc->createElement($pk); $tmpv = $doc->createTextNode($pv); $tmpe->appendChild($tmpv); $bgE->appendChild($tmpe); } } foreach($bg['box'] as $b) { $bE = $doc->createElement('box'); foreach($b as $bk => $bv) { $tmpe = $doc->createElement($bk); $tmpv = $doc->createTextNode($bv); $tmpe->appendChild($tmpv); $bE->appendChild($tmpe); } $bgE->appendChild($bE); } $p->appendChild($bgE); } $q->appendChild($p); } $root->appendChild($q); $doc->appendChild($root); $doc->formatOutput = true; //make it look nice return $doc->saveXML(); } /** * Set font size and style * * @param string $size Optional, defaults to 12 * @param string $style Optional, defaults to ''. * * @return TODO * @author Adam Zammit * @since 2010-11-05 */ protected function setDefaultFont($size = 12,$style = '') { $alternatepdffontfile=Yii::app()->getConfig('alternatepdffontfile'); if(array_key_exists($this->language,$alternatepdffontfile)) { $this->SetFont($alternatepdffontfile[$this->language],$style); } else { $this->SetFont($this->defaultFont,$style); } $this->SetFontSize($size); } /** * Initialise TCPDF width some default values and embedded fonts * * @return TODO * @author Adam Zammit * @since 2010-09-20 */ protected function init() { if ($this->embedFonts) { $this->setFontSubsetting(false); //we want full subsetting $this->AddFont('freesans',''); $this->AddFont('freesans','B'); $this->AddFont('freesans','I'); $this->AddFont('freesans','BI'); $this->AddFont('freeserif',''); $this->AddFont('freeserif','B'); $this->AddFont('freeserif','I'); $this->AddFont('freeserif','BI'); $this->setDefaultFont(); } // set document information $this->SetCreator('queXMLPDF (http://quexml.sourceforge.net)'); $this->SetAuthor('Adam Zammit '); $this->SetTitle('queXML Document'); $this->SetSubject('queXML'); $this->SetKeywords('queXML queXF'); //set text colour $this->SetTextColor($this->textColour); //set column pointer $this->columnCP = -1; } /** * Override of TCPDF Header function to blank * * @author Adam Zammit * @since 2010-09-20 */ public function Header(){ } /** * Override of TCPDF Footer function to blank * * @author Adam Zammit */ public function Footer(){ } /** * Set the background wash of the page * * @param mixed $type Optional, defaults to 'empty'. * * @author Adam Zammit * @since 2010-09-02 */ protected function setBackground($type = 'empty') { switch ($type) { case 'question': $this->SetFillColor($this->backgroundColourQuestion[0]); break; case 'section': $this->SetFillColor($this->backgroundColourSection[0]); break; case 'empty': $this->SetFillColor($this->backgroundColourEmpty[0]); break; } } /* * The X coordinate of the start of the page proper * * @return int The X coordinate of the start of the page * @author Adam Zammit * @since 2010-09-02 */ public function getMainPageX() { return ($this->cornerBorder + $this->cornerWidth); } /** * The X coordinate of the start of the column * * @return int The X coordinate of the start of the current column * @author Adam Zammit * @since 2012-05-30 */ public function getColumnX() { $border = 0; if ($this->columnCP > 0) $border = $this->columnBorder; return $this->getMainPageX() + ($this->columnCP * ($this->getColumnWidth() + $border)) + $border; } /** * The width of the writeable page * * @return int The width of the writeable page * @author Adam Zammit * @since 2010-09-02 */ public function getMainPageWidth() { return ($this->getPageWidth() - (($this->cornerBorder * 2.0) + ($this->cornerWidth * 2.0))); } /** * The width of the writable column * * @return int The width of the current column * @author Adam Zammit * @since 2012-05-30 */ public function getColumnWidth() { $border = 0; if ($this->columnCP > 0) $border = $this->columnBorder; return ((1 / $this->columns) * $this->getMainPageWidth()) - $border; } /** * Draw a horizontal response box with possible eye guides and arrows * * @param int $x The x position of the box area (top left) * @param int $y The y position of the box area (top left) * @param string $position What position the box is in for the eye guides * @param bool $downarrow Draw a down arrow? * @param bool $rightarrow Draw an arrow to the right? * @param bool $smallwidth Whether or not to use the small width * @param bool $filled Whether or not to have the box pre-filled * */ protected function drawHorizontalResponseBox($x,$y,$position = 'only',$downarrow = false, $rightarrow = false, $smallwidth = false, $filled =false) { $this->SetDrawColor($this->lineColour[0]); $this->SetLineWidth($this->singleResponseBoxBorder); //centre for the line $boxmid = ($y + ($this->singleResponseHorizontalHeight / 2.0)); //centre on y $y = $y + (($this->singleResponseHorizontalHeight - $this->singleResponseBoxHeight) / 2.0); if ($smallwidth) $areawidth = $this->singleResponseVerticalAreaWidthSmall; else $areawidth = $this->singleResponseVerticalAreaWidth; $linelength = (($areawidth - $this->singleResponseBoxWidth) / 2.0); $this->SetLineStyle(array('dash' => '1')); if ($position == 'last' || $position == 'middle') { $this->Line($x, $boxmid, $x + $linelength,$boxmid); } if ($position == 'first' || $position == 'middle') { $this->Line($x + $linelength + $this->singleResponseBoxWidth, $boxmid, $x + ($linelength * 2) + $this->singleResponseBoxWidth,$boxmid); } $this->SetLineStyle(array('dash' => '0')); $this->Rect($x + $linelength,$y,$this->singleResponseBoxWidth,$this->singleResponseBoxHeight,'DF',array(),$this->backgroundColourEmpty); if ($downarrow) { $boxmiddle = ($x + ($this->singleResponseBoxWidth / 2.0)) + $linelength; $this->SetFillColor($this->lineColour[0]); $this->Polygon(array($x + $linelength, $y + $this->singleResponseBoxHeight, $boxmiddle, $y + $this->singleResponseBoxHeight + $this->arrowHeight, $x + $linelength + $this->singleResponseBoxWidth, $y + $this->singleResponseBoxHeight),'DF',array(),$this->lineColour); $this->setBackground('empty'); } if ($filled) { //draw a cross $this->SetLineWidth($this->defaultValueLineWidth); $this->Line($x + $linelength, $y, $x + $linelength + $this->singleResponseBoxWidth, $y + $this->singleResponseBoxHeight); $this->Line($x + $linelength + $this->singleResponseBoxWidth, $y, $x + $linelength, $y + $this->singleResponseBoxHeight); } $this->setBackground('question'); return array($x + $linelength,$y,$x + $linelength + $this->singleResponseBoxWidth, $y + $this->singleResponseBoxHeight); //return the posistion for banding } /** * Draw a vertical response box with possible eye guides and arrows * * @param int $x The x position of the box area (top left) * @param int $y The y position of the box area (top left) * @param string $position What position the box is in for the eye guides * @param bool $downarrow Draw a down arrow? * @param bool $rightarrow Draw an arrow to the right? * @param bool $filled Whether or not to have the box pre-filled * */ protected function drawVerticalResponseBox($x,$y,$position = 'only',$downarrow = false, $rightarrow = false, $filled = false) { $this->SetDrawColor($this->lineColour[0]); $this->SetLineWidth($this->singleResponseBoxBorder); if (!$downarrow) { $y = $y + (($this->singleResponseAreaHeight - $this->singleResponseBoxHeight) / 2.0); } $boxmid = ($x + ($this->singleResponseBoxWidth / 2.0)); if ($position == 'first' || $position == 'middle') { $this->Line($boxmid, ($y + $this->singleResponseBoxHeight), $boxmid, ($y + $this->singleResponseBoxHeight + $this->singleResponseBoxLineLength)); } if ($position == 'last' || $position == 'middle') { $this->Line($boxmid, $y, $boxmid, ($y - $this->singleResponseBoxLineLength)); } if ($downarrow) { $this->SetFillColor($this->lineColour[0]); $this->Polygon(array($x, $y + $this->singleResponseBoxHeight, $boxmid, $y + $this->singleResponseBoxHeight + $this->arrowHeight, $x + $this->singleResponseBoxWidth, $y + $this->singleResponseBoxHeight),'DF',array(),$this->lineColour); $this->setBackground('empty'); } if ($rightarrow !== false) { //Draw skipto $boxymid = ($y + ($this->singleResponseBoxHeight / 2.0)); $this->SetFillColor($this->lineColour[0]); $this->Polygon(array($x + $this->singleResponseBoxWidth, $y, $x + $this->singleResponseBoxWidth + $this->arrowHeight, $boxymid, $x + $this->singleResponseBoxWidth, $y + $this->singleResponseBoxHeight),'DF',array(),$this->lineColour); $this->setBackground('empty'); //Now draw the text //Start at $x + singleResponseboxWidth + arrowHeight, $y - siongleresponseboxlinelength and go to $skipcolumnwidth wide and singleresponseareHeight high $this->setBackground('question'); $text = $this->skipToText . $rightarrow; $ypos = $this->GetY(); $this->setDefaultFont($this->skipToTextFontSize,'B'); $this->MultiCell($this->skipColumnWidth,$this->singleResponseBoxHeight,$text,0,'L',false,0,(($this->getColumnWidth() + $this->getColumnX()) - $this->skipColumnWidth),$y,true,0,false,true,$this->singleResponseBoxHeight,'M',true); //Reset to non bold as causing problems with TCPDF HTML CSS conversion $this->setDefaultFont($this->skipToTextFontSize,''); //$this->writeHTMLCell($this->skipColumnWidth, 0, $this->getPageWidth() - $this->getMainPageX() - $this->skipColumnWidth ,$y, $this->style . $html,0,0,true,true,'C',true); $this->SetY($ypos,false); } $this->Rect($x,$y,$this->singleResponseBoxWidth,$this->singleResponseBoxHeight,'DF',array(),$this->backgroundColourEmpty); if ($filled) { //draw a cross $this->SetLineWidth($this->defaultValueLineWidth); $this->Line($x, $y, $x + $this->singleResponseBoxWidth, $y + $this->singleResponseBoxHeight); $this->Line($x + $this->singleResponseBoxWidth, $y, $x, $y + $this->singleResponseBoxHeight); } $this->setBackground('question'); return array($x,$y,$x + $this->singleResponseBoxWidth, $y + $this->singleResponseBoxHeight); //return the posistion for banding } /** * Return capital letter(s) corresponding to the given number * * @param integer $number * * @return string Letter(s) corresponding to the number * @author Adam Zammit * @since 2010-09-08 */ public function numberToLetter($number) { if ($number < 1) $number = 1; if ($number > 26) return chr((($number - 1) / 26) + 64) . chr((($number - 1) % 26) + 65); else return chr($number + 64); } /** * Get the questionnaire id * * @return int The questionnaire Id * @author Adam Zammit * @since 2010-09-23 */ public function getQuestionnaireId() { return $this->questionnaireId; } /** * Converts a queXML file to the array format required for the create function * * @param string $quexml The queXML file * * @return array An array readable by create * @author Adam Zammit * @since 2010-09-08 * @see create */ public function createqueXML($quexml) { $clang = new limesurvey_lang($this->language); $xml = new SimpleXMLElement($quexml); $q = array(); $scount = 1; $sl = ""; $q['id'] = $xml['id']; foreach ($xml->questionnaireInfo as $qitmp) { if ($qitmp->position == 'after') { if (!isset($q['infoafter'])) $q['infoafter'] = ""; $q['infoafter'] .= $qitmp->text . "

"; } else if ($qitmp->position == 'before') { if (!isset($q['infobefore'])) $q['infobefore'] = ""; $q['infobefore'] .= $qitmp->text . "

"; } } foreach($xml->section as $s) { $stmp = array(); $sl = $this->numberToLetter($scount); $stmp['title'] = $clang->gT("Section") . " " . $sl; $stmp['info'] = ""; $stmp['text'] = ""; $bfc = 0; foreach ($s->sectionInfo as $sitmp) { if ($sitmp->position == 'title') { $stmp['text'] .= $sitmp->text; } if ($sitmp->position == 'before' || $sitmp->position == 'during') { $stmp['info'] .= $sitmp->text; if ($bfc > 0) $stmp['info'] .= "
"; $bfc++; } } $qcount = 1; foreach ($s->question as $qu) { $qtmp = array(); $rstmp = array(); $qtmp['split'] = 'notset'; if (isset($qu['split'])) { if (current($qu['split']) == "true") $qtmp['split'] = true; else $qtmp['split'] = false; } $qtmp['title'] = $sl . $qcount . $this->questionTitleSuffix; if (isset($qu['hidetitle']) && $qu['hidetitle'] == "true") $qtmp['hidetitle'] = "true"; $qtmp['text'] = ""; foreach ($qu->text as $ttmp) { //Add a new line if we aren't at the end if ($ttmp != end($qu->text)){ $qtmp['text'] .= "
"; } $qtmp['text'] .= $ttmp; } foreach ($qu->specifier as $ttmp) { if (!isset($qtmp['specifier'])) $qtmp['specifier'] = ""; $qtmp['specifier'] .= $ttmp; } foreach ($qu->directive as $ttmp) { if ($ttmp->administration == 'self' && $ttmp->position == 'during') { if (!isset($qtmp['helptext'])) $qtmp['helptext'] = ""; $qtmp['helptext'] .= $ttmp->text; } if ($ttmp->administration == 'self' && $ttmp->position == 'after') { if (!isset($qtmp['helptextafter'])) $qtmp['helptextafter'] = ""; $qtmp['helptextafter'] .= $ttmp->text; } if ($ttmp->administration == 'self' && $ttmp->position == 'before') { if (!isset($qtmp['helptextbefore'])) $qtmp['helptextbefore'] = ""; $qtmp['helptextbefore'] .= $ttmp->text; } } foreach ($qu->subQuestion as $sq) { $sqtmp = array(); $sqtmp['text'] = ""; foreach ($sq->text as $ttmp) { $sqtmp['text'] .= $ttmp; } $sqtmp['varname'] = $sq['varName']; if (isset($sq['defaultValue'])) $sqtmp['defaultvalue'] = $sq['defaultValue']; if (isset($sq->contingentQuestion)) { //Need to handle contingent questions $oarr = array(); $oarr['width'] = current($sq->contingentQuestion->length); $oarr['text'] = current($sq->contingentQuestion->text); $oarr['format'] = 'text'; if (isset($sq->contingentQuestion->format)) $oarr['format'] = current($sq->contingentQuestion->format); if (isset($sq->contingentQuestion['defaultValue'])) $oarr['defaultvalue'] = $sq->contingentQuestion['defaultValue']; $oarr['varname'] = $sq->contingentQuestion['varName']; $sqtmp['other'] = $oarr; } $rstmp['subquestions'][] = $sqtmp; } foreach ($qu->response as $r) { $rtmp = array(); $rstmp['varname'] = $r['varName']; $rtmp['split'] = 'notset'; if (isset($r['split'])) { if (current($r['split']) == "true") $rtmp['split'] = true; else $rtmp['split'] = false; } if (isset($r['defaultValue'])) $rstmp['defaultvalue'] = $r['defaultValue']; if (isset($r->fixed)) { $rtmp['type'] = 'fixed'; $rtmp['width'] = count($r->fixed->category); if ($r->fixed['rotate'] == "true") $rtmp['rotate'] = "true"; if ($r->fixed['separate'] == "true") $rtmp['separate'] = "true"; $ctmp = array(); foreach ($r->fixed->category as $c) { $cat = array(); $cat['text'] = current($c->label); $cat['value'] = current($c->value); if (isset($c->skipTo)) { $cat['skipto'] = current($c->skipTo); //save a skip $this->skipToRegistry[current($c->skipTo) . $this->questionTitleSuffix] = $qtmp['title']; } if (isset($c->contingentQuestion)) { //Need to handle contingent questions $oarr = array(); $oarr['width'] = current($c->contingentQuestion->length); $oarr['text'] = current($c->contingentQuestion->text); $oarr['format'] = 'text'; if (isset($c->contingentQuestion->format)) $oarr['format'] = current($c->contingentQuestion->format); if (isset($c->contingentQuestion['defaultValue'])) $oarr['defaultvalue'] = $c->contingentQuestion['defaultValue']; $oarr['varname'] = $c->contingentQuestion['varName']; $cat['other'] = $oarr; } $ctmp[] = $cat; } $rtmp['categories'] = $ctmp; } else if (isset($r->free)) { $format = strtolower(trim(current($r->free->format))); if ($format == 'longtext') $rtmp['type'] = 'longtext'; else if ($format == 'number' || $format == 'numeric' || $format == 'integer') $rtmp['type'] = 'number'; else if ($format == 'i25') $rtmp['type'] = 'i25'; else if ($format == 'codabar') $rtmp['type'] = 'codabar'; else $rtmp['type'] = 'text'; $rtmp['width'] = current($r->free->length); $rtmp['text'] = current($r->free->label); } else if (isset($r->vas)) { $rtmp['type'] = 'vas'; $rtmp['width'] = 100; $rtmp['labelleft'] = current($r->vas->labelleft); $rtmp['labelright'] = current($r->vas->labelright); } $rstmp['response'] = $rtmp; $qtmp['responses'][] = $rstmp; } $stmp['questions'][] = $qtmp; $qcount++; } $q['sections'][] = $stmp; $scount++; } return $q; } /** * Create a queXML PDF document based on an array * that is structured like a queXML document * * sections (title, text, info) * questions (title, text, varname, helptext, helptextafter) * responses (varname) * subquestion (text, varname) * response (type, width, text, rotate) * categories (text, value) * * @param array $questionnaire The questionnaire in the array format above */ public function create($questionnaire) { $this->init(); $this->questionnaireId = intval($questionnaire['id']); $this->newPage(true); //first page //Draw questionnaireInfo before if exists if (isset($questionnaire['infobefore'])) { $this->drawInfo($questionnaire['infobefore']); } foreach($questionnaire['sections'] as $sk => $sv) { //link the section title with the first question for pagination purposes if (isset($sv['questions'])) { $questions = count($sv['questions']); } else $questions=0; $this->startTransaction(); $this->addSection($sv['text'],$sv['title'],$sv['info']); if ($questions != 0) $this->createQuestion($sv['questions'][0]); if ($this->pageBreakOccured) { $this->pageBreakOccured = false; $this->rollBackTransaction(true); $this->SetAutoPageBreak(false); //Temporarily set so we don't trigger a page break $this->fillPageBackground(); $this->newPage(); $this->addSection($sv['text'],$sv['title'],$sv['info']); if ($questions != 0) $this->createQuestion($sv['questions'][0]); } else $this->commitTransaction(); //start from the second question as first is linked to the section (if there is a question in this section) if ($questions != 0) { foreach(array_slice($sv['questions'], 1) as $qk => $qv) { $this->startTransaction(); //add question here $this->createQuestion($qv); if ($this->pageBreakOccured) { $this->pageBreakOccured = false; $this->rollBackTransaction(true); $this->SetAutoPageBreak(false); //Temporarily set so we don't trigger a page break //now draw a background to the bottom of the page $this->fillPageBackground(); $this->newPage(); //retry question here $this->createQuestion($qv); } else $this->commitTransaction(); } } } //Draw questionnaireInfo after if exists if (isset($questionnaire['infoafter'])) { $this->startTransaction(); $this->drawInfo($questionnaire['infoafter']); if ($this->pageBreakOccured) { $this->pageBreakOccured = false; $this->rollBackTransaction(true); $this->SetAutoPageBreak(false); //Temporarily set so we don't trigger a page break //now draw a background to the bottom of the page $this->fillPageBackground(); $this->newPage(); //retry question here $this->drawInfo($questionnaire['infoafter']); } else $this->commitTransaction(); } //fill to the end of the last page $this->fillLastPageBackground(); } /** * Draw the questionnaire info specified * * @param string $text The text to draw in info style * @author Adam Zammit * @since 2011-12-21 */ protected function drawInfo($info) { $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->questionnaireInfoMargin, $this->getColumnX(), $this->GetY() - $this->questionBorderBottom, "
",0,1,true,true); $html = "
getColumnWidth() . "mm\" class=\"questionnaireInfo\">{$info}
"; $this->writeHTMLCell($this->getColumnWidth(), 1, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); } /** * Create a question that may have multiple response groups * * questions (title, text, specifier, helptext, helptextafter) * responses (varname) * subquestions * subquestion(text, varname) * response (type, width, text, rotate) * categories * category(text, value, skipto, other) * * @param array $question The questions portion of the array * @see create */ protected function createQuestion($question) { $help = false; $specifier = false; if (isset($question['helptext'])) $help = $question['helptext']; if (isset($question['specifier'])) $specifier = $question['specifier']; //If there is some help text for before the question if (isset($question['helptextbefore'])) { //Leave a border at the top of the Help Before text if ($this->helpBeforeBorderTop > 0) //question border $this->SetY($this->GetY() + $this->helpBeforeBorderTop,false); //new line $this->setBackground('question'); $html = "
getColumnWidth() . "mm\" class=\"questionHelpBefore\">{$question['helptextbefore']}
"; $this->writeHTMLCell($this->getColumnWidth(), 1, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); //Leave a border at the bottom of the Help Before text if ($this->helpBeforeBorderBottom > 0) //question border $this->SetY($this->GetY() + $this->helpBeforeBorderBottom,false); //new line } //Question header $helph = $help; //don't display help if separate questions are involved if (isset($question['responses'][0]['response']['separate'])) $helph = false; //hide if requested $qtitle = $question['title']; if (isset($question['hidetitle'])) $qtitle = ""; $this->drawQuestionHead($qtitle, $question['text'],$helph,$specifier); $text = ""; if (isset($question['text'])) $text = $question['text']; $split = $question['split']; if ($split === 'notset') $split = $this->allowSplittingResponses; //Loop over response groups and produce questions of various types if (isset($question['responses'])) { //the number of response scales is needed later on to decide on labelling scales in fixed response questions $iCountResponseScales=count($question['responses']); if ($this->pageBreakOccured) //don't continue if page break already return; for ($rcount = 0; $rcount < count($question['responses']); $rcount++) { $r = $question['responses'][$rcount]; //only split after one response if ($split && $rcount == 1) { if ($this->pageBreakOccured) return; $this->startTransaction(); } $varname = $r['varname']; if (isset($r['subquestions'])) { $response = $r['response']; $subquestions = $r['subquestions']; $type = $response['type']; $bgtype = 3; //box group type temp set to 3 (text) // question with > 1 responses and >1 subquestions --> matrix question --> need to come up with unique variable names if (count($question['responses'])>1) { foreach ($subquestions as $index=>$sv) { $subquestions[$index]['varname']=$subquestions[$index]['varname'].'_'.$varname; } } switch ($type) { case 'fixed': $categories = $response['categories']; if (isset($response['rotate'])) $this->drawSingleChoiceVertical($categories,$subquestions,$text,$response['split']); else { if (isset($response['separate'])) { $this->drawSingleChoiceVerticalSeparate($categories,$subquestions,$text,$help,$response['split']); } else { // if there is more than one response scale, add a label to scales in the pdf $vn = false; if ($iCountResponseScales > 1) $vn = $varname; $this->drawSingleChoiceHorizontal($categories,$subquestions,$text,$vn,$response['split']); } } break; case 'number': $bgtype = 4; case 'currency': case 'text': if (isset($response['rotate'])) $this->drawMatrixTextHorizontal($subquestions,$response['width'],$text,$bgtype,$response['text']); else $this->drawMatrixTextVertical($subquestions,$response['width'],$text,$bgtype,$response['text'],$response['split']); break; case 'vas': $this->drawMatrixVas($subquestions,$text,$response['labelleft'],$response['labelright'],$response['split']); break; case 'i25': $this->drawMatrixBarcode($subquestions, 'I25'); break; case 'codabar': $this->drawMatrixBarcode($subquestions, 'CODABAR'); break; } } else { $response = $r['response']; $type = $response['type']; $defaultvalue = false; if (isset($r['defaultvalue'])) $defaultvalue = $r['defaultvalue']; if (isset($response['text']) && !empty($response['text'])) $rtext = $text . $this->subQuestionTextSeparator . $response['text']; else $rtext = $text; $bgtype = 3; //box group type temp set to 3 (text) switch ($type) { case 'fixed': if (isset($response['rotate'])) $this->drawSingleChoiceHorizontal($response['categories'],array(array('text' => '', 'varname' => $varname, 'defaultvalue' => $defaultvalue)),$rtext,false,$response['split']); else $this->drawSingleChoiceVertical($response['categories'],array(array('text' => '', 'varname' => $varname, 'defaultvalue' => $defaultvalue)),$rtext,$response['split']); break; case 'longtext': $this->addBoxGroup(6,$varname,$rtext); $this->drawLongText($response['width'],$defaultvalue); break; case 'number': $bgtype = 4; case 'currency': case 'text': $this->addBoxGroup($bgtype,$varname,$rtext,$response['width']); $this->drawText($response['text'],$response['width'],$defaultvalue); //Insert a gap here $this->Rect($this->getColumnX(),$this->GetY(),$this->getColumnWidth(),$this->subQuestionLineSpacing,'F',array(),$this->backgroundColourQuestion); $this->SetY($this->GetY() + $this->subQuestionLineSpacing,false); break; case 'vas': $this->addBoxGroup(1,$varname,$rtext,strlen($this->vasIncrements)); $this->drawVas("",$response['labelleft'],$response['labelright']); break; case 'i25': $this->drawMatrixBarcode(array(array('text' => $rtext, 'varname' => $varname, 'defaultvalue' => $defaultvalue)),'I25'); break; case 'codabar': $this->drawMatrixBarcode(array(array('text' => $rtext, 'varname' => $varname, 'defaultvalue' => $defaultvalue)),'CODABAR'); break; } } //only allow a page break if defined and we have more than one item already on this page if ($split && $this->pageBreakOccured && $rcount > 0) { $this->pageBreakOccured = false; $this->rollBackTransaction(true); $this->SetAutoPageBreak(false); //Temporarily set so we don't trigger a page break $this->fillPageBackground(); $this->newPage(); //go back to last response $rcount = $rcount - 1; } else { if ($split && $rcount > 0) { $this->commitTransaction(); $this->startTransaction(); //start a transaction to allow for splitting over pages if necessary } } }} //If there is some help text for after the question if (isset($question['helptextafter'])) { $this->setBackground('question'); $html = "
getColumnWidth() . "mm\" class=\"questionHelpAfter\">{$question['helptextafter']}
"; $this->writeHTMLCell($this->getColumnWidth(), 1, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); } //Leave a border at the bottom of the question if ($this->questionBorderBottom > 0) //question border $this->SetY($this->GetY() + $this->questionBorderBottom,false); //new line } /** * Draw text responses line by line * * @param array $subquestions The subquestions containing text and varname * @param int $width The width of the text element * @param string|bool $parenttext The question text of the parent or false if not specified * @param int $bgtype The box group type (default is 3 - text) * @param string|bool $responsegrouplabel The label for this response group or false if not specified * @param string|bool $split Allow splitting this over multiple pages. 'notset' means leave default. Otherwise force setting * * @author Adam Zammit * @since 2010-09-02 */ protected function drawMatrixTextVertical($subquestions,$width,$parenttext = false,$bgtype = 3, $responsegrouplabel = false, $split = 'notset') { if ($split === 'notset') $split = $this->allowSplittingMatrixText; $c = count($subquestions); //draw second axis label if ($responsegrouplabel) { $this->setBackground('question'); $html = "
questionTitleWidth}mm\">getColumnWidth() - $this->skipColumnWidth - $this->questionTitleWidth) . "mm\" class=\"matrixResponseGroupLabel\">$responsegrouplabel:
"; $this->writeHTMLCell($this->getColumnWidth(), 1, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); } //don't proceed if breaking the page already if ($this->pageBreakOccured) return; for($i = 0; $i < $c; $i++) { if ($split && $i == 1) { //don't proceed if breaking the page already if ($this->pageBreakOccured) return; $this->startTransaction(); //start a transaction when one line drawn } $s = $subquestions[$i]; if ($parenttext == false) $this->addBoxGroup($bgtype,$s['varname'],$s['text'],$width); else $this->addBoxGroup($bgtype,$s['varname'],$parenttext . $this->subQuestionTextSeparator . $s['text'],$width); $defaultvalue = false; if (isset($s['defaultvalue'])) $defaultvalue = $s['defaultvalue']; $this->drawText($s['text'],$width,$defaultvalue); $currentY = $this->GetY(); //Insert a gap here $this->Rect($this->getColumnX(),$this->GetY(),$this->getColumnWidth(),$this->subQuestionLineSpacing,'F',array(),$this->backgroundColourQuestion); $this->SetY($currentY + $this->subQuestionLineSpacing,false); //only allow a page break if defined and we have more than one item already on this page if ($split && $this->pageBreakOccured && $i > 0) { $this->pageBreakOccured = false; $this->rollBackTransaction(true); $this->SetAutoPageBreak(false); //Temporarily set so we don't trigger a page break $this->fillPageBackground(); $this->newPage(); //fill page background at top $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->subQuestionLineSpacing, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); //move from top of page $currentY = $this->GetY(); $i = $i - 1; //go back and draw subquestions on the new page } else { if ($split && $i > 0) { $this->commitTransaction(); $this->startTransaction(); //start a transaction to allow for splitting over pages if necessary } } } } /** * Draw a barcode as a "question" * * @param string $subquestions * @param mixed $type * * @author Adam Zammit * @since 2012-06-22 */ protected function drawMatrixBarcode($subquestions, $type) { $c = count($subquestions); for ($i = 0; $i < $c; $i++) { $s = $subquestions[$i]; $this->addBoxGroup(5,$s['varname'],$s['text'],strlen($s['defaultvalue'])); $x = $this->getColumnX(); $y = $this->GetY(); $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->barcodeResponseHeight, $this->getColumnX(), $this->GetY() , $this->style . $html,0,1,true,false); //draw the barcode $barcodeStyle = array('align' => 'R', 'border' => false, 'padding' => '0', 'bgcolor' => $this->backgroundColourQuestion, 'text' => false, 'stretch' => false); $this->write1DBarcode($s['defaultvalue'], $type, $x, $y, $this->getColumnWidth() - $this->skipColumnWidth,$this->barcodeResponseHeight,'', $barcodeStyle, 'B'); //pointer should now be at the bottom right - but make the box the width of the whole column for better reading $this->addBox($x,$y,$this->GetX(),$this->getColumnWidth() + $this->getColumnX()); $currentY = $this->GetY(); //Insert a gap here $this->Rect($this->getColumnX(),$this->GetY(),$this->getColumnWidth(),$this->subQuestionLineSpacing,'F',array(),$this->backgroundColourQuestion); $this->SetY($currentY + $this->subQuestionLineSpacing,false); } } /** * Draw multiple VAS items * * @param array $subquestions The subquestions containing text and varname * @param string|bool $parenttext The question text of the parent or false if not specified * @param string $labelleft The left hand side label * @param string $labelright The right hand side label * @param string|bool $split Allow splitting this over multiple pages. 'notset' means leave default. Otherwise force setting * * @author Adam Zammit * @since 2010-09-20 */ protected function drawMatrixVas($subquestions,$parenttext = false,$labelleft,$labelright,$split = 'notset') { if ($split === 'notset') $split = $this->allowSplittingVas; $c = count($subquestions); $width = strlen($this->vasIncrements); $heading = true; for ($i = 0; $i < $c; $i++) { if ($split && $i == 1) { //don't proceed if breaking the page already if ($this->pageBreakOccured) return; $this->startTransaction(); //start a transaction when one line drawn } $s = $subquestions[$i]; if ($parenttext == false) $this->addBoxGroup(1,$s['varname'],$s['text'],$width); else $this->addBoxGroup(1,$s['varname'],$parenttext . $this->subQuestionTextSeparator . $s['text'],$width); $this->drawVas($s['text'],$labelleft,$labelright,$heading); $currentY = $this->GetY(); //Insert a gap here $this->Rect($this->getColumnX(),$this->GetY(),$this->getColumnWidth(),$this->subQuestionLineSpacing,'F',array(),$this->backgroundColourQuestion); $this->SetY($currentY + $this->subQuestionLineSpacing,false); $heading = false; //only allow a page break if defined and we have more than one item already on this page if ($split && $this->pageBreakOccured && $i > 0) { $this->pageBreakOccured = false; $this->rollBackTransaction(true); $this->SetAutoPageBreak(false); //Temporarily set so we don't trigger a page break $this->fillPageBackground(); $this->newPage(); //fill page background at top $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->subQuestionLineSpacing, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); //move from top of page $currentY = $this->GetY(); $i = $i - 1; //go back and draw subquestions on the new page //draw heading again $heading = true; } else { if ($split && $i > 0) { $this->commitTransaction(); $this->startTransaction(); //start a transaction to allow for splitting over pages if necessary } } } } /** * Draw a large empty box for writing in text * * @param mixed $width The "width" of the box. This relates to the number of "lines" high * @param bool|string $defaultvalue The default text to print in the box (if any) * @param bool|string $text The text to display above the box (if any) * * @author Adam Zammit * @since 2010-09-02 */ protected function drawLongText($width,$defaultvalue = false,$text =false) { //Calculate long text box width as the width of the available column minus the skip column and question title area $rwidth = $this->getColumnWidth() - $this->skipColumnWidth - $this->questionTitleWidth; if ($text !== false && !empty($text)) { $this->setBackground('question'); $html = "
questionTitleWidth}mm\">getColumnWidth() - $this->skipColumnWidth - $this->questionTitleWidth) . "mm\" class=\"responseAboveText\">$text
"; $this->writeHTMLCell($this->getColumnWidth(), 1, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); } $currentY = $this->GetY(); $height = $width * $this->longTextResponseHeightMultiplier; $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $height, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); $this->SetY($currentY,false); $this->setBackground('empty'); $border = array('LTRB' => array('width' => $this->textResponseBorder, 'dash' => 0)); //Align to skip column on right $this->SetX((($this->getColumnWidth() + $this->getColumnX()) - $this->skipColumnWidth - $rwidth),false); //Add to pay layout $this->addBox($this->GetX(),$this->GetY(),$this->GetX() + $rwidth, $this->GetY() + $height); $this->SetDrawColor($this->lineColour[0]); $text = ""; if ($defaultvalue !== false) $text = $defaultvalue; $this->Cell($rwidth,$height,$text,$border,0,'',true,'',0,false,'T','T'); $currentY = $currentY + $height; $this->SetY($currentY,false); } /** * Draw a VAS * * @param string $text The text of this item * @param string $labelleft The left hand side label * @param string $labelright The right hand side label * @param bool $heading Whether to draw a heading or not * * @author Adam Zammit * @since 2010-09-20 */ protected function drawVas($text, $labelleft,$labelright,$heading = true) { $textwidth = $this->getColumnWidth() - $this->skipColumnWidth - ($this->vasLength + ($this->vasLineWidth * 2.0)) - 2; $this->setBackground('question'); if ($heading) { //draw heading $lwidth = 20; $slwidth = $textwidth - ($lwidth / 2); $gapwidth = ($this->vasLength + ($this->vasLineWidth * 2.0)) - $lwidth; $html = "
$labelleft$labelright
"; $this->writeHTMLCell($this->getColumnWidth(), 0, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,false); } $currentY = $this->GetY(); $html = "
$text
"; $textwidth += 2; $this->writeHTMLCell($this->getColumnWidth(), $this->vasAreaHeight, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,false); $ncurrentY = $this->GetY(); $this->SetY($currentY,false); $this->SetX($textwidth + $this->getColumnX(),false); $this->SetLineWidth($this->vasLineWidth); $this->SetDrawColor($this->lineColour[0]); //Draw the VAS left vert line $ly = (($this->vasAreaHeight - $this->vasHeight) / 2.0) + $currentY; $lx = $textwidth + $this->getColumnX(); $this->Line($lx,$ly,$lx,$ly + $this->vasHeight); //Right vert line $lx = $textwidth + $this->getColumnX() + $this->vasLength + $this->vasLineWidth; $this->Line($lx,$ly,$lx,$ly + $this->vasHeight); //Line itself $ly = ($this->vasAreaHeight / 2.0) + $currentY; $lx = $textwidth + $this->getColumnX() + ($this->vasLineWidth / 2.0); $this->Line($lx,$ly,$lx + $this->vasLength,$ly); //Add to layout system $bw = ($this->vasLength / $this->vasIncrements); $ly = (($this->vasAreaHeight - $this->vasHeight) / 2.0) + $currentY; for ($i = 0; $i < $this->vasIncrements; $i++) { $this->addBox($lx,$ly,$lx + $bw,$ly + $this->vasHeight, $i + 1, $i + 1); $lx += $bw; } //Go back to the right Y position $this->SetY($ncurrentY,false); } /** * Draw a text response * * @param string $text The text label if any (can be HTML) * @param int $width The number of boxes to draw * @param bool|string $defaultvalue The default text to include or false if none */ protected function drawText($text,$width,$defaultvalue = false) { $this->SetDrawColor($this->lineColour[0]); //calculate text responses per line $textResponsesPerLine = round(($this->getColumnWidth() - $this->skipColumnWidth - $this->textResponseMarginX) / ($this->textResponseWidth + $this->textResponseBorder)); $labelTextResponsesSameLine = round(($this->getColumnWidth() - $this->skipColumnWidth - $this->labelTextResponsesSameLineMarginX) / ($this->textResponseWidth + $this->textResponseBorder)); //draw boxes - can draw up to $textResponsesPerLine for each line $lines = ceil($width / $textResponsesPerLine); //draw the text label on the top of this box if ($width > $labelTextResponsesSameLine && !empty($text)) { $this->setBackground('question'); $html = "
questionTitleWidth}mm\">getColumnWidth() - $this->skipColumnWidth - $this->questionTitleWidth) . "mm\" class=\"responseAboveText\">$text
"; $this->writeHTMLCell($this->getColumnWidth(), 1, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); } $currentY = $this->GetY(); $startstring = 0; for ($i = 0; $i < $lines; $i++) { if ($lines == 1) $cells = $width; //one line only else if (($i + 1 == $lines)) $cells = ($width - ($textResponsesPerLine * $i)); //last line else $cells = $textResponsesPerLine; //middle line //add another box group if moving on to another line if ($i >= 1) $this->addBoxGroupCopyPrevious(); $textwidth = ($this->getColumnWidth() - $this->skipColumnWidth) - (($this->textResponseWidth + $this->textResponseBorder ) * $cells); //print "textwidth: $textwidth cells: $cells mainpagex: " . $this->getMainPageX() . "
"; //First draw a background of height $this->responseLabelHeight $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->textResponseHeight, $this->getColumnX(), $this->GetY() , $this->style . $html,0,1,true,false); if ($lines == 1 && $cells <= $labelTextResponsesSameLine && !empty($text)) { $this->setDefaultFont($this->responseTextFontSize); $this->MultiCell($textwidth,$this->textResponseHeight,$text,0,'R',false,1,$this->getColumnX(),$currentY,true,0,false,true,$this->textResponseHeight,'M',true); //$html = "
$text
"; } $ncurrentY = $this->GetY(); $this->SetY($currentY,false); $this->SetX($textwidth + $this->getColumnX() + 2,false); //set the X position to the first cell $string = false; if ($defaultvalue !== false) $string = substr($defaultvalue,$startstring,$cells); $this->drawCells($cells,$string); $startstring += $cells; $currentY = $ncurrentY; //New line $this->SetY($currentY,false); //new line if (!(($i + 1) == $lines) && $this->textResponseLineSpacing > 0) //if there should be a gap between text responses and not the last { $this->SetX($this->getColumnX(),false); $this->setBackground('question'); $this->Cell($this->getColumnWidth(),$this->textResponseLineSpacing,'','',0,'',true,'',0,false,'T','C'); $currentY += $this->textResponseLineSpacing; $this->SetY($currentY,false); //new line } } } /** * Draw X number of cells at the current X Y position * * @param int $cells The number of text cells to draw * @param string $string A string to draw if set * * @author Adam Zammit * @since 2010-09-08 */ protected function drawCells($cells, $string) { $this->setBackground('empty'); $this->SetDrawColor($this->lineColour[0]); for ($j = 0; $j < $cells; $j++) { //draw text cells if ($cells == 1) //only $border = array('LTR' => array('width' => $this->textResponseBorder, 'dash' => 0), 'B' => array('width' => ($this->textResponseBorder * 2), 'dash' => 0)); else if ($j == 0) //first $border = array('LT' => array('width' => $this->textResponseBorder, 'dash' => 0), 'R' => array('width' => $this->textResponseBorder, 'dash' => 1), 'B' => array('width' => ($this->textResponseBorder * 2), 'dash' => 0)); else if (($j + 1) == $cells) //last { $border = array('TR' => array('width' => $this->textResponseBorder, 'dash' => 0), 'B' => array('width' => ($this->textResponseBorder * 2), 'dash' => 0)); //add a border gap $this->SetX($this->GetX() + ($this->textResponseBorder),false); } else //middle { $border = array('T' => array('width' => $this->textResponseBorder, 'dash' => 0), 'R' => array('width' => $this->textResponseBorder, 'dash' => 1), 'B' => array('width' => ($this->textResponseBorder * 2), 'dash' => 0)); //add a border gap $this->SetX($this->GetX() + ($this->textResponseBorder),false); } //Add the box to the layout scheme $this->addBox($this->GetX(),$this->GetY(),$this->GetX() + $this->textResponseWidth,$this->GetY() + $this->textResponseHeight); $text = ''; if (isset($string[$j])) $text = $string[$j]; //Draw the box $this->Cell($this->textResponseWidth,$this->textResponseHeight,$text,$border,0,'',true,'',0,false,'T','C'); } //add some spacing for the bottom border //$this->SetY(($this->GetY() + ($this->textResponseBorder * 2)),false); } /** * Draw a horizontal table of text boxes * * @param array $subquestions The subquestions * @param int $width The width * @param string|bool $parenttext The question text of the parent or false if not specified * @param int $bgtype The type of the box group (defaults to 3 - text) * @param string|bool $responsegrouplabel The label for this response group or false if not specified * * @author Adam Zammit * @since 2010-09-08 */ protected function drawMatrixTextHorizontal($subquestions,$width,$parenttext = false,$bgtype = 3, $responsegrouplabel = false) { $total = count($subquestions); $currentY = $this->GetY(); $rwidth = ($width * ($this->textResponseWidth + $this->textResponseBorder + $this->textResponseLineSpacing)); $textwidth = ($this->getColumnWidth() - $this->skipColumnWidth) - ($rwidth * $total); $html = ""; foreach ($subquestions as $r) { $html .= ""; } $html .= "
{$r['text']}
"; $this->writeHTMLCell($this->getColumnWidth(), $this->singleResponseAreaHeight, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); $currentY = $this->GetY(); //label "vertical axis" $html = "
$responsegrouplabel
"; $this->writeHTMLCell($this->getColumnWidth(), $this->singleResponseAreaHeight, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); $ncurrentY = $this->GetY(); $this->SetY($currentY,false); //Set X position $this->SetX($this->getColumnX() + $textwidth,false); foreach ($subquestions as $s) { //Add box group to current layout if ($parenttext == false) $this->addBoxGroup($bgtype,$s['varname'],$s['text']); else $this->addBoxGroup($bgtype,$s['varname'],$parenttext . $this->subQuestionTextSeparator . $s['text']); $string = false; if (isset($s['defaultvalue'])) $string = substr($defaultvalue,0,$width); //Draw the cells $this->drawCells($width,$string); //Move X for a gap $this->SetX($this->GetX() + $this->textResponseLineSpacing,false); $this->SetY($currentY,false); } //Move cursor back to the right spot $this->SetY($ncurrentY,false); } /** * Draw the head of a single choice horizontal table of responses * * @param array $categories The response categories * @param string|bool $responsegrouplabel The label for this response group or false if not specified * * @author Adam Zammit * @since 2012-06-05 */ protected function drawSingleChoiceHorizontalHead($categories, $responsegrouplabel=false) { $total = count($categories); $currentY = $this->GetY(); if ($total > $this->singleResponseHorizontalMax) //change if too many cats $rwidth = $this->singleResponseVerticalAreaWidthSmall; else $rwidth = $this->singleResponseVerticalAreaWidth; $textwidth = ($this->getColumnWidth() - $this->skipColumnWidth) - ($rwidth * $total); //Draw a label for a group of Questions/Responses (e.g. useful for dual scale matrix questions) if ($responsegrouplabel!=false) { $this->setBackground('question'); $this->setDefaultFont(); $this->MultiCell($textwidth,$this->responseLabelHeight,$responsegrouplabel.':',0,'L',false,0,$this->getColumnX()+$this->questionTitleWidth,$this->GetY(),true,0,false,true,$this->responseLabelHeight,'B',true); } //First draw a background of height $this->responseLabelHeight $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->responseLabelHeight, $this->getColumnX(), $currentY , $this->style . $html,0,1,true,true); $this->setDefaultFont($this->responseLabelFontSize); $count = 0; //Draw a Cell for each rwidth from $textwidth + $this->getColumnX(),currentY foreach ($categories as $r) { $y = $currentY; $x = ($textwidth + $this->getColumnX() + ($rwidth * $count)); // Going to break the line because of long word if ($this->wordLength($r['text']) > $this->responseLabelSmallWordLength) $this->setDefaultFont($this->responseLabelFontSizeSmall); $this->MultiCell($rwidth,$this->responseLabelHeight,$r['text'],0,'C',false,0,$x,$y,true,0,false,true,$this->responseLabelHeight,'B',true); //reset font if ($this->wordLength($r['text']) > $this->responseLabelSmallWordLength) $this->setDefaultFont($this->responseLabelFontSize); $count++; } } /** * Draw a horizontal table of respones including "eye guides" * * @param array $categories The response categories * @param array $subquestions The subquestions if any * @param string|bool $parenttext The question text of the parent or false if not specified * @param string|bool $responsegrouplabel The label for this response group or false if not specified * @param string|bool $split Allow splitting this over multiple pages. 'notset' means leave default. Otherwise force setting * * @author Adam Zammit * @since 2010-09-08 */ protected function drawSingleChoiceHorizontal($categories, $subquestions = array(array('text' => '')),$parenttext = false, $responsegrouplabel=false, $split = "notset") { if ($split === "notset") $split = $this->allowSplittingSingleChoiceHorizontal; $total = count($categories); $currentY = $this->GetY(); if ($total > $this->singleResponseHorizontalMax) //change if too many cats $rwidth = $this->singleResponseVerticalAreaWidthSmall; else $rwidth = $this->singleResponseVerticalAreaWidth; $textwidth = ($this->getColumnWidth() - $this->skipColumnWidth) - ($rwidth * $total); //draw the header $this->drawSingleChoiceHorizontalHead($categories, $responsegrouplabel); $currentY += $this->responseLabelHeight; //don't continue if page break already if ($this->pageBreakOccured) return; for ($i = 0; $i < count($subquestions); $i++) { if ($split && $i == 1) { //don't proceed if breaking the page already if ($this->pageBreakOccured) return; $this->startTransaction(); //start a transaction when one line drawn } $s = $subquestions[$i]; //Add box group to current layout if ($parenttext == false) $this->addBoxGroup(1,$s['varname'],$s['text']); else $this->addBoxGroup(1,$s['varname'],$parenttext . $this->subQuestionTextSeparator . $s['text']); //Draw background $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->singleResponseHorizontalHeight, $this->getColumnX(), $currentY, $this->style . $html,0,1,true,true); $this->setDefaultFont($this->responseTextFontSize); $newlineheight = $this->singleResponseHorizontalHeight; $heightadjust = 0; $testcells = $this->getNumLines($s['text'],$textwidth); if ($testcells > $this->singleResponseHorizontalMaxLines) { //more than two lines so need to increase the space between these questions $heightadjust = (($this->singleResponseHorizontalHeight / $this->singleResponseHorizontalMaxLines) * ($testcells - $this->singleResponseHorizontalMaxLines)); $newlineheight = $newlineheight + $heightadjust; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $newlineheight, $this->getColumnX(), $currentY, $this->style . $html,0,1,true,false); $this->setDefaultFont($this->responseTextFontSize); $this->MultiCell($textwidth,$newlineheight,$s['text'],0,'R',false,0,$this->getColumnX(),$currentY,true,0,false,true,$newlineheight,'M',false); } else { $this->MultiCell($textwidth,$this->singleResponseHorizontalHeight,$s['text'],0,'R',false,0,$this->getColumnX(),$currentY,true,0,false,true,$this->singleResponseHorizontalHeight,'M',false); } $other = false; if (isset($s['other'])) $other = true; //Draw the categories horizontally $rnum = 1; foreach ($categories as $r) { if ($total == 1) $num = 'only'; else if ($rnum == 1) $num = 'first'; else if ($rnum < $total) $num = 'middle'; else if ($rnum == $total) $num = 'last'; $bfilled = false; if (isset($s['defaultvalue']) && $s['defaultvalue'] !== false && $s['defaultvalue'] == $r['value']) $bfilled = true; $position = $this->drawHorizontalResponseBox(($this->getColumnX() + $textwidth + (($rnum - 1) * $rwidth)),$currentY + ($heightadjust/2), $num,$other,false,($total > $this->singleResponseHorizontalMax),$bfilled); //Add box to the current layout $this->addBox($position[0],$position[1],$position[2],$position[3],$r['value'],$r['text']); $rnum++; } if (($this->GetY() - $currentY) > $newlineheight) $currentY = $this->GetY(); else $currentY = $currentY + $newlineheight; $this->SetY($currentY,false); if ($other) $this->drawOther($s['other']); //only allow a page break if defined and we have more than one item already on this page if ($split && $this->pageBreakOccured && $i > 0) { $this->pageBreakOccured = false; $this->rollBackTransaction(true); $this->SetAutoPageBreak(false); //Temporarily set so we don't trigger a page break $this->fillPageBackground(); $this->newPage(); $this->drawSingleChoiceHorizontalHead($categories, $responsegrouplabel); //reset currentY $currentY = $this->GetY() + $this->responseLabelHeight; $i = $i - 1; //go back and draw subquestions on the new page } else { if ($split && $i > 0) { $this->commitTransaction(); $this->startTransaction(); //start a transaction to allow for splitting over pages if necessary } } } } /** * Draw vertical questions separately instead of in a matrix * * @param array $categories An array containing the category text, value, skipto and other * @param array $subquestions An array containing the subquestions if any * @param string|bool $parenttext The question text of the parent or false if not specified * @param string|bool $help Help text if any for the responses * @param string|bool $split Allow splitting this over multiple pages. 'notset' means leave default. Otherwise force setting * * @author Adam Zammit * @since 2013-07-30 */ protected function drawSingleChoiceVerticalSeparate($categories,$subquestions,$parenttext,$help,$split='notset') { for ($sc = 0; $sc < count($subquestions); $sc++) { $s = $subquestions[$sc]; $this->drawQuestionHead("",$this->numberToLetter($sc + 1) . ". " . $s['text'],$help); //Don't send it twice unset($s['text']); $this->drawSingleChoiceVertical($categories,array(array($s)),$this->subQuestionTextSeparator . $s['text']); } } /** * Draw the head of a single choice vertical table of responses * * @param array $subquestions The subquestions * * @author Adam Zammit * @since 2013-10-24 */ protected function drawSingleChoiceVerticalHead($subquestions) { $currentY = $this->GetY(); $total = count($subquestions); $rwidth = $this->singleResponseVerticalAreaWidth; $textwidth = ($this->getColumnWidth() - $this->skipColumnWidth) - ($rwidth * $total); $isempty = true; $count = 0; //First draw a background of height $this->responseLabelHeight $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->responseLabelHeight, $this->getColumnX(), $currentY , $this->style . $html,0,1,true,true); $this->setDefaultFont($this->responseLabelFontSize); //Draw a Cell for each rwidth from $textwidth + $this->getColumnX(),currentY foreach ($subquestions as $r) { $y = $currentY; $x = ($textwidth + $this->getColumnX() + ($rwidth * $count)); // Going to break the line because of long word if ($this->wordLength($r['text']) > $this->responseLabelSmallWordLength) $this->setDefaultFont($this->responseLabelFontSizeSmall); $this->MultiCell($rwidth,$this->responseLabelHeight,$r['text'],0,'C',false,0,$x,$y,true,0,false,true,$this->responseLabelHeight,'B',true); if ($this->wordLength($r['text']) > $this->responseLabelSmallWordLength) $this->setDefaultFont($this->responseLabelFontSize); if (!empty($r['text'])) $isempty = false; $count++; } if ($isempty) $this->SetY($currentY,false); else $this->SetY($currentY+$this->responseLabelHeight); } /** * Draw a vertical table of single choice responses including "eye guides" * * @param array $categories An array containing the category text, value, skipto and other * @param array $subquestions An array containing the subquestions if any * @param string|bool $parenttext The question text of the parent or false if not specified * @param string|bool $split Allow splitting this over multiple pages. 'notset' means leave default. Otherwise force setting * * @author Adam Zammit * @since 2010-09-02 */ protected function drawSingleChoiceVertical($categories, $subquestions = array(array('text' => '')),$parenttext = false,$split = 'notset') { //draw subquestions if more than one category (otherwise probably a multiple choice question) if (count($categories) > 1) $this->drawSingleChoiceVerticalHead($subquestions); $total = count($subquestions); $rwidth = $this->singleResponseVerticalAreaWidth; $textwidth = ($this->getColumnWidth() - $this->skipColumnWidth) - ($rwidth * $total); $currentY = $this->GetY(); $total = count($categories); $boxcp = array(); //restrict splitting to minSplittingSingleChoiceVertical where not explicitly set in queXML if ($split === 'notset') $split = $this->allowSplittingSingleChoiceVertical && ($total >= $this->minSplittingSingleChoiceVertical); for ($i = 0; $i < count($categories); $i++) { //don't continue if page break already (start on new page) if ($i == 1 && $split) { if ($this->pageBreakOccured) return; $this->startTransaction(); //allow for splitting } $rnum = $i + 1; $r = $categories[$i]; if ($total == 1) $num = 'only'; else if ($rnum == 1) $num = 'first'; else if ($rnum < $total) $num = 'middle'; else if ($rnum == $total) $num = 'last'; //Draw background $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $this->singleResponseAreaHeight, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); $this->setDefaultFont($this->responseTextFontSize); //draw text $this->MultiCell($textwidth,$this->singleResponseAreaHeight,$r['text'],0,'R',false,0,$this->getColumnX(),$currentY,true,0,false,true,$this->singleResponseAreaHeight,'M',true); $skipto = false; $other = false; if (isset($r['skipto'])) $skipto = $r['skipto']; if (isset($r['other']) && $rnum == $total) $other = $r['other']; //only set for last in set //draw the response boxes for ($j = 0; $j < count($subquestions); $j++) { $s = $subquestions[$j]; if ($i == 0) // only need to do this once { if ($parenttext == false) $this->addBoxGroup(1,$s['varname'],$s['text']); else $this->addBoxGroup(1,$s['varname'],$parenttext . $this->subQuestionTextSeparator . $s['text']); //save the box group for this subquestion $boxcp[$j] = $this->boxGroupCP; } else { //reset box group pointer to be for the correct subquestion $this->boxGroupCP = $boxcp[$j]; } $bfilled = false; if (isset($s['defaultvalue']) && $s['defaultvalue'] !== false && $s['defaultvalue'] == $r['value']) $bfilled = true; $x = $this->getColumnX() + $textwidth + ($rwidth * $j) + ((($rwidth - $this->singleResponseBoxWidth) / 2.0 )); //Draw the box over the top $position = $this->drawVerticalResponseBox($x,$currentY, $num, $other, $skipto, $bfilled); //Add box to the current layout $this->addBox($position[0],$position[1],$position[2],$position[3],$r['value'],$r['text']); } if (($this->GetY() - $currentY) > $this->singleResponseAreaHeight) $currentY = $this->GetY(); else $currentY = $currentY + $this->singleResponseAreaHeight; $this->SetY($currentY,false); if ($other !== false) { //Display the "other" variable $this->drawOther($other); } //only allow a page break if defined and we have more than one item already on this page if ($split && $this->pageBreakOccured && $i > 0) { $this->pageBreakOccured = false; $this->rollBackTransaction(true); $this->SetAutoPageBreak(false); //Temporarily set so we don't trigger a page break $this->fillPageBackground(); //draw an arrow indicating that this was split and will be on the next page $boxmiddle = ($x + ($this->singleResponseBoxWidth / 2.0)); $this->SetFillColor($this->lineColour[0]); //set the bottom of the box to be the bottom of the page $y = $this->getPageHeight() - $this->cornerBorder - $this->arrowHeight; $this->Polygon(array($x, $y, $boxmiddle, $y + $this->arrowHeight, $x + $this->singleResponseBoxWidth, $y),'DF',array(),$this->lineColour); $this->setBackground('empty'); $this->newPage(); if (count($categories) > 1) $this->drawSingleChoiceVerticalHead($subquestions); //reset currentY $currentY = $this->GetY(); $i = $i - 1; //go back and draw categories on the new page //create a new box group as we are on a new page $sbgc = 0; foreach ($subquestions as $sbg) { if ($parenttext == false) $this->addBoxGroup(1,$sbg['varname'],$sbg['text']); else $this->addBoxGroup(1,$sbg['varname'],$parenttext . $this->subQuestionTextSeparator . $sbg['text']); //save the box group for this subquestion $boxcp[$sbgc] = $this->boxGroupCP; $sbgc++; } } else { if ($split && $i > 0) { $this->commitTransaction(); $this->startTransaction(); //start a transaction to allow for splitting over pages if necessary } } } } /** * Draw an "other" box * * @param array $other An array continaing varname,text,width,defaultvalue * * @return TODO * @author Adam Zammit * @since 2013-05-01 */ protected function drawOther($other) { $btid = 3; if ($other['format'] == 'longtext') $btid = 6; $this->addBoxGroup($btid,$other['varname'],$other['text'],$other['width']); $defaultvalue = false; if (isset($other['defaultvalue']) && $other['defaultvalue'] !== false) $defaultvalue = $other['defaultvalue']; if ($btid == 3) $this->drawText($other['text'],$other['width'],$defaultvalue); else $this->drawLongText($other['width'],$defaultValue,$other['text']); //Insert a gap here $this->Rect($this->getColumnX(),$this->GetY(),$this->getColumnWidth(),$this->subQuestionLineSpacing,'F',array(),$this->backgroundColourQuestion); $this->SetY($this->GetY() + $this->subQuestionLineSpacing,false); } /** * Draw the header of a question (question title, text and help text if any) * * @param string $title The question title (number) * @param string $text The question text (can be HTML) * @param string|bool $help The question help text or false if none (can be HTML) * @param string|bool $specifier The question specifier text or false if none (can be HTML) */ protected function drawQuestionHead($title,$text,$help = false,$specifier = false) { $this->setBackground('question'); //Cell for question number (title) and text including a white border at the bottom $class = "questionTitle"; //If there is a skip to this question, make the question title bigger if (isset($this->skipToRegistry[$title])) $class = "questionTitleSkipTo"; $html = ""; if ($specifier !== false) { $html .= ""; } $html .= "
questionTitleWidth . "mm\">$titlegetColumnWidth() - $this->questionTextRightMargin - $this->questionTitleWidth) . "mm\">$text
questionTitleWidth . "mm\"> getColumnWidth() - $this->questionTextRightMargin - $this->questionTitleWidth) . "mm\">$specifier
"; $this->writeHTMLCell($this->getColumnWidth(), 1, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); if ($help != false) { $html = "
getColumnWidth() - $this->skipColumnWidth) . "mm\" class=\"questionHelp\">$help
"; $this->writeHTMLCell($this->getColumnWidth(), 1, $this->getColumnX(), $this->GetY(), $this->style . $html,0,1,true,true); } } /** * Add a new section to the page * * @param string $text The text of the section * @param string $desc The description of this section * @param string $info Information for this section */ protected function addSection($desc = 'queXMLPDF Section',$title = false,$info = false) { $this->sectionCP++; if ($title === false) $title = $this->sectionCP; $this->section[$this->sectionCP] = array('label' => $desc, 'title' => $title); $html = "$title: $desc"; if ($info && !empty($info)) $html .= "
$info
"; $this->setBackground('section'); $this->writeHTMLCell($this->getColumnWidth(),$this->sectionHeight,$this->getColumnX(),$this->getY(),$this->style . $html,array('B' => array('width' => 1, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->backgroundColourEmpty)),1,true,true,''); $this->setBackground('empty'); } /** * Convert mm to pixels based on the set ppi (dpi) * * @param float $mm Measurement in millimetres * @return int Pixel value as an integer */ public function mm2px($mm) { return round($mm * ($this->ppi / self::INCH_IN_MM)); } /** * Make sure to fill the remaining columns on the last page * * @author Adam Zammit * @since 2012-05-31 */ protected function fillLastPageBackground() { while ($this->columnCP < $this->columns) { $this->fillPageBackground(); $this->SetXY($this->getColumnX(),($this->cornerBorder + $this->cornerWidth)); $this->columnCP++; } } /** * Draw the background from the current Y position to the bottom of the page * * @author Adam Zammit * @since 2010-09-15 */ protected function fillPageBackground() { $height = $this->getPageHeight() - $this->cornerBorder - $this->GetY() + $this->questionBorderBottom; $html = "
"; $this->setBackground('question'); $this->writeHTMLCell($this->getColumnWidth(), $height, $this->getColumnX(), $this->GetY() - $this->questionBorderBottom, $this->style . $html,0,1,true,true); } /** * Create a new queXML PDF page * * Draw the barcode and page corners * */ protected function newPage($init = false) { $this->columnCP++; //increment the column pointer if ($init || ($this->columnCP >= $this->columns)) // if it is time for a new page { $this->AddPage(); //Set Auto page break to false $this->SetAutoPageBreak(false); $this->SetMargins(0,0,0); $this->SetHeaderMargin(0); $this->SetFooterMargin(0); //Shortcuts to make the code (a bit) nicer $width = $this->getPageWidth(); $height = $this->getPageHeight(); $cb = $this->cornerBorder; $cl = $this->cornerLength; $this->SetDrawColor($this->lineColour[0]); $barcodeStyle = array('border' => false, 'padding' => '0', 'bgcolor' => false, 'text' => false, 'stretch' => true); $lineStyle = array('width' => $this->cornerWidth, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)); //Top left $this->Line($cb,$cb,$cb + $cl,$cb,$lineStyle); $this->Line($cb,$cb,$cb,$cb + $cl,$lineStyle); //Top right $this->Line($width - $cb,$cb,$width - $cb - $cl,$cb,$lineStyle); $this->Line($width - $cb,$cb,$width - $cb,$cb + $cl,$lineStyle); //Bottom left $this->Line($cb,$height - $cb,$cb + $cl,$height - $cb,$lineStyle); $this->Line($cb,$height - $cb,$cb,$height - ($cb + $cl),$lineStyle); //Bottom right $this->Line($width - $cb,$height - $cb,$width - $cb - $cl,$height - $cb,$lineStyle); $this->Line($width - $cb,$height - $cb,$width - $cb,$height - ($cb + $cl),$lineStyle); $barcodeValue = substr(str_pad($this->questionnaireId,$this->idLength,"0",STR_PAD_LEFT),0,$this->idLength) . substr(str_pad($this->getPage(),$this->pageLength,"0",STR_PAD_LEFT),0,$this->pageLength); //Calc X position of barcode from page width $barcodeX = $width - ($this->barcodeMarginX + $this->barcodeW); $this->write1DBarcode($barcodeValue, $this->barcodeType, $barcodeX, $this->barcodeY, $this->barcodeW, $this->barcodeH,'', $barcodeStyle, 'N'); //Add this page to the layout system $b = $this->cornerBorder + ($this->cornerWidth / 2.0); //temp calc for middle of line $this->layout[$barcodeValue] = array( 'id' => $barcodeValue, 'tlx' => $this->mm2px($b), 'tly' => $this->mm2px($b), 'trx' => $this->mm2px($width - $b), 'try' => $this->mm2px($b), 'brx' => $this->mm2px($width - $b), 'bry' => $this->mm2px($height - $b), 'blx' => $this->mm2px($b), 'bly' => $this->mm2px($height - $b), 'rotation' => 0, 'boxgroup' => array() ); $this->layoutCP = $barcodeValue; $this->SetXY($cb + $this->cornerWidth, $cb + $this->cornerWidth); $this->setBackground('empty'); $this->columnCP = 0; //reset column pointer } else // move to a new column { $this->SetXY($this->getColumnX(),($this->cornerBorder + $this->cornerWidth)); } $this->SetAutoPageBreak(true,$this->getMainPageX()); //after a new page was begun....page should not have already ended $this->pageBreakOccured = false; } /** * Override of the acceptPageBreak function * * Allow our page handling function to know that a page break has occured * * $return bool Returns false so no page break is automatically issued */ public function AcceptPageBreak() { $this->pageBreakOccured = true; return FALSE; } }