import { IQuestion , Question , QuestionRatingModel , QuestionFileModel , LocalizableString , QuestionSelectBase , QuestionDropdownModel } from 'survey-core' ; import * as SurveyPDFModule from './entries/pdf' ; import { SurveyPDF } from './survey' ; import { IPoint , IRect , ISize , DocController } from './doc_controller' ; import { FlatRepository } from './flat_layout/flat_repository' ; import { IFlatQuestion } from './flat_layout/flat_question' ; import { IHTMLRenderType } from './flat_layout/flat_html' ; import { IPdfBrick , PdfBrick } from './pdf_render/pdf_brick' ; import { TextBrick } from './pdf_render/pdf_text' ; import { TextBoldBrick } from './pdf_render/pdf_textbold' ; import { TitlePanelBrick } from './pdf_render/pdf_titlepanel' ; import { DescriptionBrick } from './pdf_render/pdf_description' ; import { CommentBrick } from './pdf_render/pdf_comment' ; import { LinkBrick } from './pdf_render/pdf_link' ; import { HTMLBrick } from './pdf_render/pdf_html' ; import { ImageBrick } from './pdf_render/pdf_image' ; import { EmptyBrick } from './pdf_render/pdf_empty' ; import { RowlineBrick } from './pdf_render/pdf_rowline' ; import { CompositeBrick } from './pdf_render/pdf_composite' ; import { AdornersOptions } from './event_handler/adorners' ; export class SurveyHelper { public static EPSILON : number = 2.2204460492503130808472633361816e-15 ; public static TITLE_SURVEY_FONT_SIZE_SCALE : number = 1.7 ; public static TITLE_PAGE_FONT_SIZE_SCALE : number = 1.3 ; public static TITLE_PANEL_FONT_SIZE_SCALE : number = 1.3 ; public static DESCRIPTION_FONT_SIZE_SCALE : number = 2.0 / 3.0 ; public static OTHER_ROWS_COUNT : number = 2 ; public static RATING_MIN_WIDTH : number = 3 ; public static RATING_MIN_HEIGHT : number = 2 ; public static RATING_COLUMN_WIDTH : number = 5 ; public static MATRIX_COLUMN_WIDTH : number = 5 ; public static IMAGEPICKER_COUNT : number = 4 ; public static IMAGEPICKER_RATIO : number = 4.0 / 3.0 ; public static MULTIPLETEXT_TEXT_PERS : number = Math . E / 10.0 ; public static HTML_TAIL_TEXT_SCALE : number = 0.24 ; public static SELECT_ITEM_FLAT_SCALE : number = 0.95 ; public static GAP_BETWEEN_ROWS : number = 0.25 ; public static GAP_BETWEEN_COLUMNS : number = 1.5 ; public static GAP_BETWEEN_ITEM_TEXT : number = 0.25 ; public static FORM_BORDER_VISIBLE : boolean = true ; public static BORDER_SCALE : number = 0.1 ; public static VISIBLE_BORDER_SCALE : number = 0.8 ; public static UNVISIBLE_BORDER_SCALE : number = 0.2 ; public static RADIUS_SCALE : number = 3.0 ; public static TITLE_FONT_SCALE : number = 1.1 ; public static VALUE_READONLY_PADDING_SCALE : number = 0.3 ; public static HTML_TO_IMAGE_QUALITY : number = 1.0 ; public static FORM_BORDER_COLOR : string = '#9f9f9f' ; public static TEXT_COLOR : string = '#404040' ; public static BACKGROUND_COLOR : string = '#FFFFFF' ; public static TITLE_LOCATION_MATRIX : string = 'matrix' ; public static STANDARD_FONT : string = 'helvetica' ; public static CUSTOM_FONT_ENCODING : string = 'Identity-H' ; public static parseWidth ( width , maxWidth , columnsCount = 1 ) { if ( width . indexOf ( 'calc' ) === 0 ) { return maxWidth / columnsCount ; } const value = parseFloat ( width ) ; const unit = width . replace ( /[^A-Za-z]/g , '' ) ; let k ; switch ( unit ) { case 'pt' : k = 1.0 ; break ; case 'mm' : k = 72.0 / 25.4 ; break ; case 'cm' : k = 72.0 / 2.54 ; break ; case 'in' : k = 72.0 ; break ; case 'px' : k = 72.0 / 96.0 ; break ; case 'pc' : k = 12.0 ; break ; case 'em' : k = 12.0 ; break ; case 'ex' : k = 6.0 ; break ; default : case '%' : k = maxWidth / 100.0 ; break ; } return Math . min ( value * k , maxWidth ) ; } public static pxToPt ( value ) { if ( typeof value === 'string' ) { return SurveyHelper . parseWidth ( value , Number . MAX_VALUE ) ; } return value * 72.0 / 96.0 ; } public static mergeRects ( ... rects ) { const resultRect = { xLeft : rects [ 0 ] . xLeft , xRight : rects [ 0 ] . xRight , yTop : rects [ 0 ] . yTop , yBot : rects [ 0 ] . yBot } ; rects . forEach ( ( rect ) => { resultRect . xLeft = Math . min ( resultRect . xLeft , rect . xLeft ) , resultRect . xRight = Math . max ( resultRect . xRight , rect . xRight ) , resultRect . yTop = Math . min ( resultRect . yTop , rect . yTop ) , resultRect . yBot = Math . max ( resultRect . yBot , rect . yBot ) ; } ) ; return resultRect ; } public static createPoint ( rect , isLeft = true , isTop = false ) { return { xLeft : isLeft ? rect . xLeft : rect . xRight , yTop : isTop ? rect . yTop : rect . yBot } ; } public static createRect ( point , width , height ) { return { xLeft : point . xLeft , xRight : point . xLeft + width , yTop : point . yTop , yBot : point . yTop + height } ; } public static createHeaderRect ( controller ) { return { xLeft : 0.0 , xRight : controller . paperWidth , yTop : 0.0 , yBot : controller . margins . top } ; } public static createFooterRect ( controller ) { return { xLeft : 0.0 , xRight : controller . paperWidth , yTop : controller . paperHeight - controller . margins . bot , yBot : controller . paperHeight } ; } public static chooseHtmlFont ( controller , renderAs ) { if ( renderAs === 'standard' ) { return this . isCustomFont ( controller , controller . fontName ) ? this . STANDARD_FONT : controller . fontName ; } return controller . useCustomFontInHtml ? controller . fontName : this . STANDARD_FONT ; } public static generateCssTextRule ( fontSize , fontStyle , fontName ) { return `"font-size: ${ fontSize }pt; font-weight: ${ fontStyle }; font-family: ${ fontName }; color: ${ this . TEXT_COLOR };"` ; } public static createHtmlContainerBlock ( html , controller , renderAs ) { const font = this . chooseHtmlFont ( controller , renderAs ) ; return `<div class="__surveypdf_html" style=${ this . generateCssTextRule ( controller . fontSize , controller . fontStyle , font ) }>` + `<style>.__surveypdf_html p { margin: 0; line-height: ${ controller . fontSize }pt } body { margin: 0; }</style>${ html }</div>` ; } public static splitHtmlRect ( controller , htmlBrick ) { const bricks = [ ] ; const htmlHeight = htmlBrick . height ; const minHeight = controller . doc . getFontSize ( ) ; htmlBrick . yBot = htmlBrick . yTop + minHeight ; const emptyBrickCount = Math . floor ( htmlHeight / minHeight ) - 1 ; bricks . push ( htmlBrick ) ; const currPoint = this . createPoint ( htmlBrick ) ; for ( let i = 0 ; i < emptyBrickCount ; i ++ ) { bricks . push ( new EmptyBrick ( this . createRect ( currPoint , htmlBrick . width , minHeight ) ) ) ; currPoint . yTop += minHeight ; } const remainingHeight = htmlHeight - ( emptyBrickCount + 1 ) * minHeight ; if ( remainingHeight > 0 ) { bricks . push ( new EmptyBrick ( this . createRect ( currPoint , htmlBrick . width , remainingHeight ) ) ) ; } return new CompositeBrick ( ... bricks ) ; } public static createPlainTextFlat < T extends IPdfBrick > ( point , question , controller , text , fabric ) { const lines = controller . doc . splitTextToSize ( text , controller . paperWidth - controller . margins . right - point . xLeft ) ; const currPoint = this . clone ( point ) ; const composite = new CompositeBrick ( ) ; lines . forEach ( ( line ) => { const size = controller . measureText ( line ) ; composite . addBrick ( new fabric ( question , controller , this . createRect ( currPoint , size . width , size . height ) , line ) ) ; currPoint . yTop += size . height ; } ) ; return composite ; } public static async createTextFlat < T extends IPdfBrick > ( point , question , controller , text , fabric ) { if ( typeof text === 'string' || ! this . hasHtml ( text ) ) { return this . createPlainTextFlat ( point , question , controller , typeof text === 'string' ? text : this . getLocString ( < LocalizableString > text ) , fabric ) ; } else { return this . splitHtmlRect ( controller , await this . createHTMLFlat ( point , < Question > question , controller , this . createHtmlContainerBlock ( this . getLocString ( text ) , controller , 'standard' ) ) ) ; } } private static hasHtml ( text ) { const pattern = /<\/?[a-z][\s\S]*>/i ; return text . hasHtml && ( pattern . test ( ( < any > text ) . renderedText ) || pattern . test ( text . renderedHtml ) ) ; } private static getHtmlMargins ( controller , point ) { const width = controller . paperWidth - point . xLeft - controller . margins . right ; return { top : controller . margins . top , bottom : controller . margins . bot , width : width > controller . unitWidth ? width : controller . unitWidth } ; } public static createHTMLRect ( point , controller , margins , resultY ) { const availablePageHeight = controller . paperHeight - controller . margins . bot - controller . margins . top ; const height = ( controller . helperDoc . getNumberOfPages ( ) - 1 ) * ( controller . fontSize * Math . floor ( availablePageHeight / controller . fontSize ) ) + resultY - margins . top + SurveyHelper . HTML_TAIL_TEXT_SCALE * controller . fontSize ; const numberOfPages = controller . helperDoc . getNumberOfPages ( ) ; controller . helperDoc . addPage ( ) ; for ( let i = 0 ; i < numberOfPages ; i ++ ) { controller . helperDoc . deletePage ( 1 ) ; } return SurveyHelper . createRect ( point , margins . width , height ) ; } public static async createHTMLFlat ( point , question , controller , html ) { const margins = this . getHtmlMargins ( controller , point ) ; return await new Promise < IPdfBrick > ( ( resolve ) => { controller . helperDoc . fromHTML ( html , point . xLeft , margins . top , { pagesplit : true , width : margins . width } , function ( result ) { const rect = SurveyHelper . createHTMLRect ( point , controller , margins , result . y ) ; resolve ( new HTMLBrick ( question , controller , rect , html ) ) ; } , margins ) ; } ) ; } public static generateFontFace ( fontName , fontBase64 , fontWeight ) { return `@font-face { font-family: ${ fontName }; ` + `src: url(data:application/font-woff;charset=utf-8;base64,${ fontBase64 }) format('woff'); ` + `font-weight: ${ fontWeight }; }` ; } public static generateFontFaceWithItalicStyle ( fontName , fontBase64 , fontWeight ) { return `@font-face { font-family: ${ fontName }; ` + `src: url(data:application/font-woff;charset=utf-8;base64,${ fontBase64 }) format('woff'); ` + `font-weight: ${ fontWeight }; font-style: italic}` ; } public static htmlToXml ( html ) { const htmlDoc = document . implementation . createHTMLDocument ( '' ) ; htmlDoc . write ( html . replace ( /\#/g , '%23' ) ) ; htmlDoc . documentElement . setAttribute ( 'xmlns' , htmlDoc . documentElement . namespaceURI ) ; htmlDoc . body . style . margin = 'unset' ; return ( new XMLSerializer ( ) ) . serializeToString ( htmlDoc . body ) . replace ( /%23/g , '#' ) ; } public static createSvgContent ( html , width , controller ) { const style = document . createElement ( 'style' ) ; style . innerHTML = '.__surveypdf_html p { margin: unset; line-height: 22px; } body { margin: unset; }' ; document . body . appendChild ( style ) ; const div = document . createElement ( 'div' ) ; div . className = '__surveypdf_html' ; div . style . display = 'block' ; div . style . position = 'fixed' ; div . style . top = '-10000px' ; div . style . left = '-10000px' ; div . style . width = ( width / 72.0 * 96.0 ) + 'px' ; div . style . boxSizing = 'initial' ; div . style . color = 'initial' ; div . style . fontFamily = 'initial' ; div . style . font = 'initial' ; div . style . lineHeight = 'initial' ; div . insertAdjacentHTML ( 'beforeend' , html ) ; document . body . appendChild ( div ) ; const divWidth = div . offsetWidth ; const divHeight = div . offsetHeight ; div . remove ( ) ; style . remove ( ) ; let defs = '' ; if ( controller . useCustomFontInHtml ) { defs = `<defs><style>${ this . generateFontFace ( controller . fontName , controller . base64Normal , 'normal' ) }` + ` ${ this . generateFontFace ( controller . fontName , controller . base64Bold , 'bold' ) }</style></defs>` ; } else { Object . keys ( DocController . customFonts ) . forEach ( fontName => { const font = DocController . customFonts [ fontName ] ; Object . keys ( font ) . forEach ( ( fontStyle ) => { if ( fontStyle === 'normal' || fontStyle === 'bold' ) { defs += `${ this . generateFontFace ( fontName , font [ fontStyle ] , fontStyle ) }` ; } else { defs += `${ this . generateFontFaceWithItalicStyle ( fontName , font [ fontStyle ] , fontStyle === 'italic' ? 'normal' : 'bold' ) }` ; } } ) ; defs = '<defs><style>' + defs + '</style></defs>' ; } ) ; } const svg = '<svg xmlns="http://www.w3.org/2000/svg">' + defs + '<style>.__surveypdf_html p { margin: unset; line-height: 22px; }</style>' + `<foreignObject width="${ divWidth }px" height="${ divHeight }px">` + this . htmlToXml ( html ) + '</foreignObject></svg>' ; return { svg , divWidth , divHeight } ; } public static async htmlToImage ( html , width , controller ) { const { svg , divWidth , divHeight } = SurveyHelper . createSvgContent ( html , width , controller ) ; const data = 'data:image/svg+xml;base64,' + btoa ( unescape ( encodeURIComponent ( svg ) ) ) ; const img = new Image ( ) ; img . src = data ; return await new Promise ( ( resolve ) => { img . onload = function ( ) { const canvas = document . createElement ( 'canvas' ) ; canvas . width = divWidth ; canvas . height = divHeight ; const context = canvas . getContext ( '2d' ) ; context . fillStyle = SurveyHelper . BACKGROUND_COLOR ; context . fillRect ( 0 , 0 , divWidth , divHeight ) ; context . drawImage ( img , 0 , 0 ) ; const url = canvas . toDataURL ( 'image/jpeg' , SurveyHelper . HTML_TO_IMAGE_QUALITY ) ; canvas . remove ( ) ; resolve ( { url : url , aspect : divWidth / divHeight } ) ; } ; img . onerror = function ( ) { resolve ( { url : 'data:,' , aspect : width / this . EPSILON } ) ; } ; } ) ; } public static async createBoldTextFlat ( point , question , controller , text ) { controller . fontStyle = 'bold' ; const composite = await this . createTextFlat ( point , question , controller , text , TextBoldBrick ) ; controller . fontStyle = 'normal' ; return composite ; } public static async createTitleFlat ( point , question , controller ) { const composite = new CompositeBrick ( ) ; let currPoint = this . clone ( point ) ; const oldFontSize = controller . fontSize ; controller . fontSize *= this . TITLE_FONT_SCALE ; if ( question . no ) { const noText = question . no + ' ' ; let noFlat ; if ( this . hasHtml ( question . locTitle ) ) { controller . fontStyle = 'bold' ; controller . pushMargins ( ) ; controller . margins . right = controller . paperWidth - controller . margins . left - controller . measureText ( noText , 'bold' ) . width ; noFlat = await this . createHTMLFlat ( currPoint , question , controller , this . createHtmlContainerBlock ( noText , controller , 'standard' ) ) ; controller . popMargins ( ) ; controller . fontStyle = 'normal' ; } else { noFlat = await this . createBoldTextFlat ( currPoint , question , controller , noText ) ; } composite . addBrick ( noFlat ) ; currPoint . xLeft = noFlat . xRight ; } controller . pushMargins ( ) ; controller . margins . left = currPoint . xLeft ; const textFlat = < CompositeBrick > await this . createBoldTextFlat ( currPoint , question , controller , question . locTitle ) ; composite . addBrick ( textFlat ) ; controller . popMargins ( ) ; if ( question . isRequired ) { const requiredText = question . requiredText ; if ( this . hasHtml ( question . locTitle ) ) { currPoint = this . createPoint ( textFlat . unfold ( ) [ 0 ] , false , false ) ; controller . fontStyle = 'bold' ; controller . pushMargins ( ) ; controller . margins . right = controller . paperWidth - controller . margins . left - controller . measureText ( requiredText , 'bold' ) . width ; composite . addBrick ( await this . createHTMLFlat ( currPoint , question , controller , this . createHtmlContainerBlock ( requiredText , controller , 'standard' ) ) ) ; controller . popMargins ( ) ; controller . fontStyle = 'normal' ; } else { currPoint = this . createPoint ( textFlat . unfold ( ) . pop ( ) , false , true ) ; composite . addBrick ( await this . createBoldTextFlat ( currPoint , question , controller , requiredText ) ) ; } } controller . fontSize = oldFontSize ; return composite ; } private static async createTitleSurveyPanelFlat ( point , controller , text , fontSizeScale ) { const oldFontSize = controller . fontSize ; controller . fontSize = oldFontSize * fontSizeScale ; controller . fontStyle = 'bold' ; const titleFlat = await this . createTextFlat ( point , null , controller , text , TitlePanelBrick ) ; controller . fontStyle = 'normal' ; controller . fontSize = oldFontSize ; return titleFlat ; } public static async createTitleSurveyFlat ( point , controller , text ) { return await this . createTitleSurveyPanelFlat ( point , controller , text , this . TITLE_SURVEY_FONT_SIZE_SCALE ) ; } public static async createTitlePanelFlat ( point , controller , text , isPage = false ) { return await this . createTitleSurveyPanelFlat ( point , controller , text , isPage ? this . TITLE_PAGE_FONT_SIZE_SCALE : this . TITLE_PANEL_FONT_SIZE_SCALE ) ; } public static async createDescFlat ( point , question , controller , text ) { const oldFontSize = controller . fontSize ; controller . fontSize = oldFontSize * this . DESCRIPTION_FONT_SIZE_SCALE ; const composite = await this . createTextFlat ( point , question , controller , text , DescriptionBrick ) ; controller . fontSize = oldFontSize ; return composite ; } public static getReadonlyRenderAs ( question , controller ) { return ( < any > question ) . readonlyRenderAs === 'auto' ? controller . readonlyRenderAs : ( < any > question ) . readonlyRenderAs ; } public static async createCommentFlat ( point , question , controller , rows , isQuestion , index = 0 ) { const rect = this . createTextFieldRect ( point , controller , rows ) ; if ( question . isReadOnly && this . getReadonlyRenderAs ( question , controller ) !== 'acroform' ) { const textFlat = await this . createReadOnlyTextFieldTextFlat ( point , controller , question , this . getQuestionOrCommentValue ( question , isQuestion ) , false ) ; const padding = controller . unitHeight * this . VALUE_READONLY_PADDING_SCALE ; if ( textFlat . yBot + padding > rect . yBot ) rect . yBot = textFlat . yBot + padding ; } return new CommentBrick ( question , controller , rect , isQuestion , index ) ; } public static getQuestionOrCommentValue ( question , isQuestion = true ) { return isQuestion ? ( question . value !== undefined && question . value !== null ? question . value : '' ) : ( question . comment !== undefined && question . comment !== null ? question . comment : '' ) ; } public static getQuestionOrCommentDisplayValue ( question , isQuestion = true ) { return isQuestion ? question . displayValue : SurveyHelper . getQuestionOrCommentValue ( question , isQuestion ) ; } public static inBrowser = typeof Image === 'function' ; public static createImageFlat ( point , question , controller , imagelink , width , height ) { if ( SurveyHelper . inBrowser ) { const html = `<img src='${ imagelink }' width='${ width }' height='${ height }'/>` ; return new HTMLBrick ( question , controller , this . createRect ( point , width , height ) , html , true ) ; } return new ImageBrick ( question , controller , imagelink , point , width , height ) ; } public static canPreviewImage ( question , item , url ) { return question . canPreviewImage ( item ) ; } public static async getImageSize ( url ) { if ( ! SurveyHelper . inBrowser ) { return await new Promise ( ( resolve ) => { return resolve ( { width : undefined , height : undefined } ) ; } ) ; } return await new Promise ( ( resolve ) => { const image = new Image ( ) ; image . src = url ; image . onload = function ( ) { return resolve ( { width : image . width , height : image . height } ) ; } ; image . onerror = function ( ) { return resolve ( null ) ; } ; } ) ; } public static createRowlineFlat ( point , controller , width ? , color ? ) { let xRight = typeof width === 'undefined' ? controller . paperWidth - controller . margins . right : point . xLeft + width ; xRight = xRight > point . xLeft ? xRight : point . xLeft + this . EPSILON ; return new RowlineBrick ( controller , { xLeft : point . xLeft , xRight : xRight , yTop : point . yTop + this . EPSILON , yBot : point . yTop + this . EPSILON } , typeof color === 'undefined' ? null : color ) ; } public static async createLinkFlat ( point , question , controller , text , link ) { const compositeText = < CompositeBrick > await this . createTextFlat ( point , question , controller , text , TextBrick ) ; const compositeLink = new CompositeBrick ( ) ; compositeText . unfold ( ) . forEach ( ( text ) => { compositeLink . addBrick ( new LinkBrick ( text , link ) ) ; const linePoint = this . createPoint ( compositeLink ) ; compositeLink . addBrick ( this . createRowlineFlat ( linePoint , controller , compositeLink . width , LinkBrick . COLOR ) ) ; } ) ; return compositeLink ; } public static createAcroformRect ( rect ) { return [ rect . xLeft , rect . yTop , rect . xRight - rect . xLeft , rect . yBot - rect . yTop ] ; } public static createTextFieldRect ( point , controller , lines = 1 ) { let width = controller . paperWidth - point . xLeft - controller . margins . right ; width = Math . max ( width , controller . unitWidth ) ; const height = controller . unitHeight * lines ; return this . createRect ( point , width , height ) ; } public static async createReadOnlyTextFieldTextFlat ( point , controller , question , value , onlyFirstLine ) { const padding = controller . unitWidth * this . VALUE_READONLY_PADDING_SCALE ; if ( ! onlyFirstLine ) point . yTop += padding ; point . xLeft += padding ; controller . pushMargins ( point . xLeft , controller . margins . right + padding ) ; const textFlat = await this . createTextFlat ( point , question , controller , value . toString ( ) , TextBrick ) ; controller . popMargins ( ) ; return textFlat ; } public static renderFlatBorders ( controller , flat ) { if ( ! this . FORM_BORDER_VISIBLE ) return ; const minSide = Math . min ( flat . width , flat . height ) ; const visibleWidth = controller . unitHeight * this . VISIBLE_BORDER_SCALE * this . BORDER_SCALE ; const visibleScale = this . formScale ( controller , flat ) + visibleWidth / minSide ; const unvisibleWidth = controller . unitHeight * this . UNVISIBLE_BORDER_SCALE * this . BORDER_SCALE ; const unvisibleScale = 1.0 - unvisibleWidth / minSide ; const unvisibleRadius = this . RADIUS_SCALE * unvisibleWidth ; const oldDrawColor = controller . doc . getDrawColor ( ) ; controller . doc . setDrawColor ( flat . formBorderColor ) ; controller . doc . setLineWidth ( visibleWidth ) ; controller . doc . rect ( ... this . createAcroformRect ( this . scaleRect ( flat , visibleScale ) ) ) ; controller . doc . setDrawColor ( this . BACKGROUND_COLOR ) ; controller . doc . setLineWidth ( unvisibleWidth ) ; controller . doc . roundedRect ( ... this . createAcroformRect ( this . scaleRect ( flat , unvisibleScale ) ) , unvisibleRadius , unvisibleRadius ) ; controller . doc . setDrawColor ( oldDrawColor ) ; } public static async renderReadOnlyTextField ( controller , question , flat , value , onlyFirstLine = true ) { const point = this . createPoint ( flat , true , true ) ; const oldFontSize = controller . fontSize ; controller . fontSize = flat . fontSize ; const textFlat = await this . createReadOnlyTextFieldTextFlat ( point , controller , question , value , onlyFirstLine ) ; controller . fontSize = oldFontSize ; if ( onlyFirstLine ) await textFlat . unfold ( ) [ 0 ] . render ( ) ; else await textFlat . render ( ) ; this . renderFlatBorders ( controller , flat ) ; } public static getLocString ( text ) { if ( this . hasHtml ( text ) ) return text . renderedHtml ; return ( < any > text ) . renderedText || text . renderedHtml ; } public static getDropdownQuestionValue ( question ) { const qDropDown = < QuestionDropdownModel > question ; if ( qDropDown . isOtherSelected ) { return qDropDown . otherText ; } else if ( ! ! question . displayValue ) { return question . displayValue ; } else if ( qDropDown . showOptionsCaption ) { return qDropDown . optionsCaption ; } return '' ; } public static getContentQuestion ( question ) { return ! ! ( < any > question ) . contentQuestion ? ( < any > question ) . contentQuestion : question ; } public static getRatingMinWidth ( controller ) { return controller . measureText ( this . RATING_MIN_WIDTH ) . width ; } public static getRatingItemText ( question , index , locText ) { const ratingItemLocText = new LocalizableString ( locText . owner , locText . useMarkdown ) ; ratingItemLocText . text = this . getLocString ( locText ) ; if ( index === 0 && question . minRateDescription ) { ratingItemLocText . text = question . locMinRateDescription . text + ' ' + this . getLocString ( locText ) ; } else if ( index === question . visibleRateValues . length - 1 && question . maxRateDescription ) { ratingItemLocText . text = this . getLocString ( locText ) + ' ' + question . locMaxRateDescription . text ; } return ratingItemLocText ; } public static getPageAvailableWidth ( controller ) { return controller . paperWidth - controller . margins . left - controller . margins . right ; } public static getImagePickerAvailableWidth ( controller ) { const width = ( this . getPageAvailableWidth ( controller ) - ( this . IMAGEPICKER_COUNT - 1 ) * controller . unitHeight ) ; return width > 0 ? width : controller . unitHeight ; } public static getColumnWidth ( controller , colCount ) { return ( this . getPageAvailableWidth ( controller ) - ( colCount - 1 ) * controller . unitWidth * this . GAP_BETWEEN_COLUMNS ) / colCount ; } public static setColumnMargins ( controller , colCount , column ) { const cellWidth = this . getColumnWidth ( controller , colCount ) ; controller . margins . left = controller . margins . left + column * ( cellWidth + controller . unitWidth * this . GAP_BETWEEN_COLUMNS ) ; controller . margins . right = controller . margins . right + ( colCount - column - 1 ) * ( cellWidth + controller . unitWidth * this . GAP_BETWEEN_COLUMNS ) ; } public static moveRect ( rect , left = rect . xLeft , top = rect . yTop ) { return { xLeft : left , yTop : top , xRight : left + rect . xRight - rect . xLeft , yBot : top + rect . yBot - rect . yTop } ; } public static scaleRect ( rect , scale ) { const width = rect . xRight - rect . xLeft ; const height = rect . yBot - rect . yTop ; const scaleWidth = ( ( width < height ) ? width : height ) * ( 1.0 - scale ) / 2.0 ; return { xLeft : rect . xLeft + scaleWidth , yTop : rect . yTop + scaleWidth , xRight : rect . xRight - scaleWidth , yBot : rect . yBot - scaleWidth } ; } public static formScale ( controller , flat ) { const minSide = Math . min ( flat . width , flat . height ) ; const borderWidth = 2.0 * controller . unitWidth * this . BORDER_SCALE ; return ( minSide - borderWidth ) / minSide ; } public static async generateQuestionFlats ( survey , controller , question , point ) { const questionComposite = this . getContentQuestion ( question ) ; const questionType = question . customWidget ? question . customWidget . pdfQuestionType : questionComposite . getType ( ) ; const flatQuestion = FlatRepository . getInstance ( ) . create ( survey , question , controller , questionType ) ; const questionFlats = await flatQuestion . generateFlats ( point ) ; const adornersOptions = new AdornersOptions ( point , questionFlats , question , controller , FlatRepository . getInstance ( ) , SurveyPDFModule ) ; if ( question . customWidget && question . customWidget . isFit ( question ) && question . customWidget . pdfRender ) { survey . onRenderQuestion . unshift ( question . customWidget . pdfRender ) ; } await survey . onRenderQuestion . fire ( survey , adornersOptions ) ; return [ ... adornersOptions . bricks ] ; } public static isFontExist ( controller , fontName ) { return controller . doc . internal . getFont ( fontName ) . fontName === fontName ; } public static isCustomFont ( controller , fontName ) { return controller . doc . internal . getFont ( fontName ) . encoding === this . CUSTOM_FONT_ENCODING ; } public static fixFont ( controller ) { if ( this . isCustomFont ( controller , controller . fontName ) ) { controller . doc . text ( 'load font' , 0 , 0 ) ; controller . doc . deletePage ( 1 ) ; controller . addPage ( ) ; } } public static clone ( src ) { const target = { } ; for ( const prop in src ) { if ( src . hasOwnProperty ( prop ) ) { target [ prop ] = src [ prop ] ; } } return target ; } }