From ba012c7a683bef9117ca3e5206c8949432d82113 Mon Sep 17 00:00:00 2001 From: benweet Date: Fri, 4 Nov 2016 12:01:42 +0000 Subject: [PATCH 1/4] Button widget annotations: implement checkboxes and radio buttons --- src/core/annotation.js | 55 +++++++++++++++++ src/display/annotation_layer.js | 97 ++++++++++++++++++++++++++++++ test/unit/annotation_layer_spec.js | 91 ++++++++++++++++++++++++++++ web/annotation_layer_builder.css | 62 +++++++++++++++++++ 4 files changed, 305 insertions(+) diff --git a/src/core/annotation.js b/src/core/annotation.js index 97f728621..615bc3f96 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -106,6 +106,8 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ { switch (fieldType) { case 'Tx': return new TextWidgetAnnotation(parameters); + case 'Btn': + return new ButtonWidgetAnnotation(parameters); case 'Ch': return new ChoiceWidgetAnnotation(parameters); } @@ -767,6 +769,59 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { return TextWidgetAnnotation; })(); +var ButtonWidgetAnnotation = (function ButtonWidgetAnnotationClosure() { + function ButtonWidgetAnnotation(params) { + WidgetAnnotation.call(this, params); + + this.data.pushbutton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); + this.data.radio = !this.data.pushbutton && + this.hasFieldFlag(AnnotationFieldFlag.RADIO); + if (isName(this.data.fieldValue)) { + this.data.fieldValue = this.data.fieldValue.name; + } + // Get the value of the radio button + if (this.data.radio) { + // Generate a unique ID in case no value is found + this.data.buttonValue = Math.random().toString(16).slice(2); + var appearanceState = params.dict.get('AP'); + if (isDict(appearanceState)) { + var appearances = appearanceState.get('N'); + if (isDict(appearances) && appearances.has('Off')) { + var keys = appearances.getKeys(); + for (var i = 0, ii = keys.length; i < ii; i++) { + if (keys[i] !== 'Off') { + this.data.buttonValue = keys[i]; + break; + } + } + } + } + } + } + + Util.inherit(ButtonWidgetAnnotation, WidgetAnnotation, { + getOperatorList: + function ButtonWidgetAnnotation_getOperatorList(evaluator, task, + renderForms) { + var operatorList = new OperatorList(); + + // Do not render form elements on the canvas when interactive forms are + // enabled. The display layer is responsible for rendering them instead. + if (renderForms) { + return Promise.resolve(operatorList); + } + + if (this.appearance) { + return Annotation.prototype.getOperatorList.call(this, evaluator, task, + renderForms); + } + return Promise.resolve(operatorList); + } + }); + + return ButtonWidgetAnnotation; +})(); + var ChoiceWidgetAnnotation = (function ChoiceWidgetAnnotationClosure() { function ChoiceWidgetAnnotation(params) { WidgetAnnotation.call(this, params); diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 0a57128c2..b72c4d944 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -76,6 +76,17 @@ AnnotationElementFactory.prototype = switch (fieldType) { case 'Tx': return new TextWidgetAnnotationElement(parameters); + case 'Btn': + if (!parameters.data.pushbutton) { + if (parameters.data.radio) { + return new RadioButtonWidgetAnnotationElement(parameters); + } else { + return new CheckboxWidgetAnnotationElement(parameters); + } + } else { + warn('Unimplemented push button'); + } + break; case 'Ch': return new ChoiceWidgetAnnotationElement(parameters); } @@ -141,6 +152,7 @@ var AnnotationElement = (function AnnotationElementClosure() { var height = data.rect[3] - data.rect[1]; container.setAttribute('data-annotation-id', data.id); + container.setAttribute('data-annotation-name', data.fieldName); // Do *not* modify `data.rect`, since that will corrupt the annotation // position on subsequent calls to `_createContainer` (see issue 6804). @@ -531,6 +543,91 @@ var TextWidgetAnnotationElement = ( })(); /** + * @class + * @alias CheckboxWidgetAnnotationElement + */ +var CheckboxWidgetAnnotationElement = + (function CheckboxWidgetAnnotationElementClosure() { + function CheckboxWidgetAnnotationElement(parameters) { + WidgetAnnotationElement.call(this, parameters, + parameters.renderInteractiveForms); + } + + Util.inherit(CheckboxWidgetAnnotationElement, WidgetAnnotationElement, { + /** + * Render the checkbox widget annotation's HTML element + * in the empty container. + * + * @public + * @memberof CheckboxWidgetAnnotationElement + * @returns {HTMLSectionElement} + */ + render: function CheckboxWidgetAnnotationElement_render() { + this.container.className = 'checkboxWidgetAnnotation'; + + var element = document.createElement('input'); + element.type = 'checkbox'; + element.id = this.data.fieldName; + if (this.data.fieldValue && this.data.fieldValue !== 'Off') { + element.checked = true; + } + this.container.appendChild(element); + element = document.createElement('label'); + element.htmlFor = this.data.fieldName; + this.container.appendChild(element); + + return this.container; + } + }); + + return CheckboxWidgetAnnotationElement; +})(); + +/** + * @class + * @alias RadioButtonWidgetAnnotationElement + */ +var RadioButtonWidgetAnnotationElement = + (function RadioButtonWidgetAnnotationElementClosure() { + function RadioButtonWidgetAnnotationElement(parameters) { + WidgetAnnotationElement.call(this, parameters, + parameters.renderInteractiveForms); + } + + Util.inherit(RadioButtonWidgetAnnotationElement, WidgetAnnotationElement, { + /** + * Render the radio button widget annotation's HTML element + * in the empty container. + * + * @public + * @memberof RadioButtonWidgetAnnotationElement + * @returns {HTMLSectionElement} + */ + render: function RadioButtonWidgetAnnotationElement_render() { + this.container.className = 'radioButtonWidgetAnnotation'; + + var element = document.createElement('input'); + var id = this.data.fieldName + '.' + this.data.buttonValue; + element.type = 'radio'; + element.id = id; + element.name = this.data.fieldName; + element.value = this.data.buttonValue; + if (this.data.fieldValue === this.data.buttonValue) { + element.checked = true; + } + this.container.appendChild(element); + element = document.createElement('label'); + element.htmlFor = id; + this.container.appendChild(element); + + return this.container; + }, + }); + + return RadioButtonWidgetAnnotationElement; +})(); + + /** * @class * @alias ChoiceWidgetAnnotationElement */ diff --git a/test/unit/annotation_layer_spec.js b/test/unit/annotation_layer_spec.js index f5f3a1bb4..5bdec7f9f 100644 --- a/test/unit/annotation_layer_spec.js +++ b/test/unit/annotation_layer_spec.js @@ -869,6 +869,97 @@ describe('Annotation layer', function() { }); }); + describe('CheckboxWidgetAnnotation', function() { + var checkboxWidgetDict; + + beforeEach(function (done) { + checkboxWidgetDict = new Dict(); + checkboxWidgetDict.set('Type', Name.get('Annot')); + checkboxWidgetDict.set('Subtype', Name.get('Widget')); + checkboxWidgetDict.set('FT', Name.get('Btn')); + + done(); + }); + + afterEach(function () { + checkboxWidgetDict = null; + }); + + it('should have proper flags', + function() { + var checkboxWidgetRef = new Ref(124, 0); + var xref = new XRefMock([ + { ref: checkboxWidgetRef, data: checkboxWidgetDict, } + ]); + + var checkboxWidgetAnnotation = + annotationFactory.create(xref, checkboxWidgetRef); + expect(checkboxWidgetAnnotation.data.radio).toEqual(false); + expect(checkboxWidgetAnnotation.data.pushbutton).toEqual(false); + expect(checkboxWidgetAnnotation.data.fieldValue).toEqual(null); + }); + + it('should have a proper value', + function() { + checkboxWidgetDict.set('V', Name.get('1')); + + var checkboxWidgetRef = new Ref(124, 0); + var xref = new XRefMock([ + { ref: checkboxWidgetRef, data: checkboxWidgetDict, } + ]); + + var checkboxWidgetAnnotation = + annotationFactory.create(xref, checkboxWidgetRef); + expect(checkboxWidgetAnnotation.data.fieldValue).toEqual('1'); + }); + }); + + describe('RadioButtonWidgetAnnotation', function() { + var radioButtonWidgetDict; + + beforeEach(function (done) { + radioButtonWidgetDict = new Dict(); + radioButtonWidgetDict.set('Type', Name.get('Annot')); + radioButtonWidgetDict.set('Subtype', Name.get('Widget')); + radioButtonWidgetDict.set('FT', Name.get('Btn')); + radioButtonWidgetDict.set('Ff', AnnotationFieldFlag.RADIO); + + done(); + }); + + afterEach(function () { + radioButtonWidgetDict = null; + }); + + it('should have proper flags', + function() { + var radioButtonWidgetRef = new Ref(124, 0); + var xref = new XRefMock([ + { ref: radioButtonWidgetRef, data: radioButtonWidgetDict, } + ]); + + var radioButtonWidgetAnnotation = + annotationFactory.create(xref, radioButtonWidgetRef); + expect(radioButtonWidgetAnnotation.data.radio).toEqual(true); + expect(radioButtonWidgetAnnotation.data.pushbutton).toEqual(false); + expect(radioButtonWidgetAnnotation.data.fieldValue).toEqual(null); + }); + + it('should have a proper value', + function() { + radioButtonWidgetDict.set('V', Name.get('1')); + + var radioButtonWidgetRef = new Ref(124, 0); + var xref = new XRefMock([ + { ref: radioButtonWidgetRef, data: radioButtonWidgetDict, } + ]); + + var radioButtonWidgetAnnotation = + annotationFactory.create(xref, radioButtonWidgetRef); + expect(radioButtonWidgetAnnotation.data.fieldValue).toEqual('1'); + }); + }); + describe('ChoiceWidgetAnnotation', function() { var choiceWidgetDict; diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 8cff630f9..58afd7e3b 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -97,6 +97,68 @@ width: 115%; } +.annotationLayer .checkboxWidgetAnnotation label, +.annotationLayer .radioButtonWidgetAnnotation label { + background-color: rgba(0, 54, 255, 0.13); + border: 1px solid transparent; + box-sizing: border-box; + cursor: pointer; + height: 100%; + position: absolute; + width: 100%; +} + +.annotationLayer .checkboxWidgetAnnotation input, +.annotationLayer .radioButtonWidgetAnnotation input { + position: absolute; + left: -9999px; +} + +.annotationLayer .radioButtonWidgetAnnotation label { + border-radius: 50%; +} + +.annotationLayer .checkboxWidgetAnnotation label:hover, +.annotationLayer .radioButtonWidgetAnnotation label:hover, +.annotationLayer .checkboxWidgetAnnotation label:focus, +.annotationLayer .radioButtonWidgetAnnotation label:focus, +.annotationLayer .checkboxWidgetAnnotation input:focus + label, +.annotationLayer .radioButtonWidgetAnnotation input:focus + label { + border: 1px solid #000; +} + +.annotationLayer .checkboxWidgetAnnotation input:checked + label:before, +.annotationLayer .radioButtonWidgetAnnotation input:checked + label:before { + content: ''; + left: 50%; + top: 50%; + position: absolute; +} + +.annotationLayer .checkboxWidgetAnnotation input:checked + label:before { + border-bottom: 1px solid #000; + border-left: 1px solid #000; + height: 25%; + -webkit-transform: translate(-50%, -65%) rotateZ(-45deg); + -moz-transform: translate(-50%, -65%) rotateZ(-45deg); + -o-transform: translate(-50%, -65%) rotateZ(-45deg); + -ms-transform: translate(-50%, -65%) rotateZ(-45deg); + transform: translate(-50%, -65%) rotateZ(-45deg); + width: 60%; +} + +.annotationLayer .radioButtonWidgetAnnotation input:checked + label:before { + background-color: #000; + border-radius: 50%; + height: 50%; + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -o-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + width: 50%; +} + .annotationLayer .popupWrapper { position: absolute; width: 20em; From 0c9a06c020fce7965083b7eff0bd1cb2a452c7cc Mon Sep 17 00:00:00 2001 From: Tim van der Meij Date: Thu, 15 Dec 2016 22:15:38 +0100 Subject: [PATCH 2/4] Button widget annotations: implement reference testing Moreover, ensure that the read-only state is respected and improve CSS names. --- src/display/annotation_layer.js | 6 +++-- test/annotation_layer_test.css | 23 +++++++++++++++-- test/pdfs/.gitignore | 1 + test/pdfs/annotation-button-widget.pdf | Bin 0 -> 23785 bytes test/test_manifest.json | 14 ++++++++++ web/annotation_layer_builder.css | 34 +++++++++++++------------ 6 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 test/pdfs/annotation-button-widget.pdf diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index b72c4d944..e5382cdd4 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -563,9 +563,10 @@ var CheckboxWidgetAnnotationElement = * @returns {HTMLSectionElement} */ render: function CheckboxWidgetAnnotationElement_render() { - this.container.className = 'checkboxWidgetAnnotation'; + this.container.className = 'buttonWidgetAnnotation checkBox'; var element = document.createElement('input'); + element.disabled = this.data.readOnly; element.type = 'checkbox'; element.id = this.data.fieldName; if (this.data.fieldValue && this.data.fieldValue !== 'Off') { @@ -604,10 +605,11 @@ var RadioButtonWidgetAnnotationElement = * @returns {HTMLSectionElement} */ render: function RadioButtonWidgetAnnotationElement_render() { - this.container.className = 'radioButtonWidgetAnnotation'; + this.container.className = 'buttonWidgetAnnotation radioButton'; var element = document.createElement('input'); var id = this.data.fieldName + '.' + this.data.buttonValue; + element.disabled = this.data.readOnly; element.type = 'radio'; element.id = id; element.name = this.data.fieldName; diff --git a/test/annotation_layer_test.css b/test/annotation_layer_test.css index 5c6a87be8..be7935b64 100644 --- a/test/annotation_layer_test.css +++ b/test/annotation_layer_test.css @@ -45,7 +45,9 @@ .annotationLayer .textWidgetAnnotation input, .annotationLayer .textWidgetAnnotation textarea, -.annotationLayer .choiceWidgetAnnotation select { +.annotationLayer .choiceWidgetAnnotation select, +.annotationLayer .buttonWidgetAnnotation.checkBox label, +.annotationLayer .buttonWidgetAnnotation.radioButton label { background-color: rgba(0, 54, 255, 0.13); border: 1px solid transparent; box-sizing: border-box; @@ -64,7 +66,9 @@ .annotationLayer .textWidgetAnnotation input[disabled], .annotationLayer .textWidgetAnnotation textarea[disabled], -.annotationLayer .choiceWidgetAnnotation select[disabled] { +.annotationLayer .choiceWidgetAnnotation select[disabled], +.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled] + label, +.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] + label { background: none; border: 1px solid transparent; } @@ -75,6 +79,21 @@ padding-right: 0; } +.annotationLayer .buttonWidgetAnnotation.checkBox label, +.annotationLayer .buttonWidgetAnnotation.radioButton label { + position: absolute; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input, +.annotationLayer .buttonWidgetAnnotation.radioButton input { + position: absolute; + left: -9999px; +} + +.annotationLayer .buttonWidgetAnnotation.radioButton label { + border-radius: 50%; +} + .annotationLayer .popupAnnotation { display: block !important; } diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 90f561d3a..ee6015144 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -266,5 +266,6 @@ !annotation-fileattachment.pdf !annotation-text-widget.pdf !annotation-choice-widget.pdf +!annotation-button-widget.pdf !zero_descent.pdf !operator-in-TJ-array.pdf diff --git a/test/pdfs/annotation-button-widget.pdf b/test/pdfs/annotation-button-widget.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f1fa2394c752fd87cd2c5d3b889172f98781f82d GIT binary patch literal 23785 zcmeIa1zeTO+BZy>AdQNk3lODOQ_|htA-M>pSx9$CD%~g@Dj=PrgdiOf(xre%3exc{ z-0rRWY|lP^@3YVE-S2bG0v317ea&1o*MH`oxfqqiB-p_mTttj>^Sy6~2spt25Wv>> zHW42mP|nf@VdP-xhA;(yfpP!{l#3euGfC^wu3sDQemhPnZRaRH?Ox&Sx~ z1b}dI=?e%DA#6;~$N>KEAtK=6`c{V+!o=1Tp=9J>?TD&T)Yi($+Qtz811dQnOf5~2 zwhjO=L_h#2VQGa#H~=NAjF5<59sx>=>GFwlaSOvm#NiU65@4_}j7u2G34wt*IfXfS zM8(8l5CJ~8umqTg6Ur&U1s8*fz`)#MP&gP2=N1!(bMuG_a|!4JrEN?RF1jej;hbkD zX0`xM5UPPHKskhsInn~a4L)nl-7jo%166EokpND}mjbFN*#N*`E})vL9RjGOf-nPW z>Vg3f02n0!N2CM7$eM`2V#?Uql*G``*pLdBj8M`C>y17MDK`C_57xXB$tJd#onLSk zlU`7kmg%T)05(yzm79=X4u+Iow5g%7sSCCPIwr$V$f?}n6eiY1w8e6YNFoB1Y`;?f zDrHBckpt4j0bxc&03sq_WE5AFAR_pK-iwJ?p_eZ5uwA~WV|xCXS2Xf2~y1gf%syVkYDWb zi#dv-2w2&ge{)4n=r?Wt;!nEYJCy#fI)s7X-xMbmGN`Jr*aV{R3^K5vBo8wsHBgJi zrI41Rh%H`vGtgJpuhmg{Hy!g~FM;{Q{&BY1u%|YlW zXK4zEDyX%h(wD%#gMP0tRH~VSqUFV|us9KM*{$h~y>cZ*OY9Nzdzl1DHG2C-?|QX= zfU(6WxMhaS;<>&1`csZc^jXqcr~UgB3b)gn@K&*E&+kVWK4YHk;3=!hR}*3hvLjT# zo~K4f-Y_nb?Uq!7|LE}AF+^?Kwp??CFzsY8Sh`JeXq|7tLJDe$mFm}soUSDUWq6ga0cWlFxaQ7m04XCG8qcAJ0e zwRsau^E2_*gQKyhZMfaHDz*Vyoe>b#$J%o;j?Wck_16b)#q@V&15_+6 zD9>9frl_(P_+AOA?g*ka+kDnW(KZpVaNR~FV`H+J;ogC>k^4Pfv-xXe&HZLiPqRm> zcLZ-Z9iut6f)CMRqHa;WK=lAM`H=-bGEmshDHp;8`(C*d(aN&TAd0~0Lskygi>xAZ zPloAZnDJDM-uNlLFRCeQe2Q;fOV@L1qdQ@$f6jdeyz9X;;Yb$OIOwSZ8AWows9QWI zB8?!+Dq>Djzk1CRPt?$fj#(93dchqH)Fgs;X*m=_@w11C__(8|;;8-fG%Y=SQj3~K z2Zg7}+f~Exz{}Nh2@ShL#7=cBH_buucqeot;HkUdtu2hg%7IDjz(LYSC$Y;wT zsCZcpV?}0GlF^n3j=UfL1a@<=A+oW4J%iDwV*{`4iv76o`g%&U%NzCb>EfYn;Wt|E z;0uEhZ>HiBa_bbBZW^&U%y>TFpVd|;ewL?BEd2cOgN7!-d^doD?&k++m}m*tXhVJ^ zS@_RM77qQLWZ$WE+Khup>K9q51V)ACt}=Nj&BByz9{-4dede*z(E09d6~MbK@(zEN$ic7RbOH2CTE7W_cy{uVGprurjR z4s+AUeqJ4hsM=Fs3{*dF#1L*OSP)fZycDwA_fkO>9w;daujXgF+Nj-!CsA@Vp=e)J~tsqRuaQ^ae# zblMwoqVGA;U;C-#0tK?Q=r(MVb3PiUSlv+YkF)ESiml!S`;escaU|$Ah~Jt;5Lpz zX44y=yvBkJ+)jRDxnw^ilOXm*v^wxd)gM{ccOh2T#>N)u2mpi5d|3qrhHOxQ1$BYv z%mPv84z_k8wl2CL4ir2={c(aJsBbPF)VDq=WSCkS{nNWBe1)R)YtT?ZINCZnm>|yL zl?191)IT+_0SFaszEp$?KuQj_CaMUeE)azdfocdBBnr=dhc{JtAxc?VGgsXaq$%2X?|E8^q8;z$VeixN4XfSqWKWp=?4g~m`_Fu@Z7svoKH3=Vw72>(N#Q7G!)&NHMB%2fgb zK{TWmAxa=65LOn7d03GMhbXov_BjBSLvziiYQk+f%8r_FAB-A3Dgp@t9z80cw{4lWoE2m%F|6Py8j@Rwf)1c4dB518OO zEAp?H_%bT_-($gC-wpo$qb->03`hSf<^Sz1_&dD+Kh);`Gkq}EcQE*W$n*c(eK6N| zsP&%;;%{%k-+`ll%EZt0!Cc^fcXW{o`GO+QL`1Pw0dhU>As7xq*%=Qrd_yjUV}wvM zUvvD>v|nq-SPPI&r%)oi?#GsOZr~n?P`;mq>^#9c3_K8;TFFJMABCd74orc;JpX<< zSl#a?@&VyVtjjfGz~JAur*Q zRz>+j89fDgOmW58*E!7YM4#aA+{xv}N6x?XIrUq*ET$Z7S2XYN_(XS`f+3qfxJ0-i z$ivo;Cx`SMsUcb{BlJg;He5dg$o}>|@H1fQf4>iK|8;QeKgtKVe+JV1-F<-jXW-a> zfDin2fbKub2Yv<={_TC>X8_%QfDin2knlgt2Yv=a{_TC>X8_%QfDin2knlgs2Y7x4 zL;l@;fahlb-T!_c;Q8wy;eVD7{0xTt+xx(GfbIt?gCr~+9FYJpjOW)^NMIN@>};(P z_b)Gi6pe4IBCWr@|FM{A(XzH3=Oj5{rP+Da8n5ILr$&KJlV?*^sU2x*31(oTNGX}@ zh^A{7FEx0v$h0pWl5&Bq86)+$Kn9z2C(%U) zMUH6~wu(M?+p93Wrfq#o#kqz2%xOU5MY@}VB#9o};qVIz3+fCSsWf^pg)dHfE%9^5(Swh7 z%;u>fUV5@kPLb~Bu*SuZ2j1mZpQ_|^;>v9`@(4bKoNV&L4xcLw1`gntO(}UrQ*ek% zEl(9(Ru{drBH8h*59g4#QVu@Z=M3bTE2UA7xb?C(UUO)*X;ZOmvqk;r=<`gsAqkcQ zwl6&0tGS;T?xMF_;$}!HR#HqW|AS)eY{aCKm5pzcLJO0LU=K9}{Nn;zR3<06W0X_wkBh${*NTxmGiy!9}r zN>NDBwhwB(J;>=cC-|BtM+7#ekLQ z?YL3&f(G0VW7Bn`q}X>182Wqe?-h)0(w~G$9$kpCLiZWpMaSw=CB@Uq=EPglC}-Gj z>Zc->yjgqInnAa}(&1k5&ZYj!%LdJ;TU*wPYU;KY`);T+?VJ?bSFc&;y}-+Fiyd*dkd z!CvA`f%njph&;Y|9-vD*Yr{J(8qKw+Cl{t;uFv?xl44z^4=)_e8hJq{$ggih+=z7` zvjgXF+dbl^?g~s%m6>)agx2d4_p0P+>CKB^ZU-+cKCfDzYB9T2xkzjIV2tj{ z3G98__=|Rl{fj`?mk&ReI#jL^n~7_aR=+KK9#$Oix`tR9Dw^DlE^ihRVNcO>Tj#ue zQrT(DwIN3qLz%z^CYZ~>y}eX68BE_8k-1X9fFEQy)9l>lP!L=)N_y*&NNIltTq9fg zZou1-CY&tc>Kt|fnuufgd7CHfX<^TRJArK7{Vvp!Os0h)8?Rcsm_|47+&T^o;XA&3 zehrOJ#w;S0r-%bN?QJoS+RhQU8`;LP=t#~)X51AOKS%rY4&e?i&W5_leKn@!{yh6E zlVWoveP)XGPz9K{JN{E?V#Pg-q#z<4POYYdp_AwxMyr?noEEGm1p^aU!GvN6>*vl= zOf)OC!>|amtW^f%@*BY7Q%0p?Lwk%US1)v3)@W(0ZWipdK*C(=0fytqCL)GDu9KPb zo++jTyGd6?o$@0&VG^yE=u(iv$(E2Vm$W{sU=^RH7hxjVk3!_q&g;ITo1j1EqWQrt z;Wfwfuu+(Yj8Z;{1HmZ=fiky6av1XM^+)G*)$%lB&O3(;gjbWYXU1+YUm#H1IYmvM zb3_D={9yY0%kqh@FY|s7Eq+_&$^9)NNQF#j=-Z~CR=q81?%BuOZo6uDxnNoJ&F!&l zf$~oGII`N(sAr`}-P7Z6pn)Yh?sY~#rYP$fhjpHYdkoN)F5u~c{MsC{LBXr-4O{mo z(@Vu$*F;?B>}AUFBBNvXGQ`5EPWg7O@P~#za@`%xW{M|NZzS-`$|2Hmdzz>MyoTO1 zNLU~_%_gcv-xd1uM$AcNoTimu{?;6%#FMskG`kQHR~2Mi!mu5 z7FlE7&CPwf9h!Vg-fX2IpU*uY_iY4eDu|Jg(8j1Me(K%_RhnZV;?Zg7c4=s6n9HX9 z&SYH!c(lm;%98-qiqwuP2ZogDH?y~U~UR@v~wa}Mm?H!OW`vv71eUAsU8i2qp)0pAXqu6Ey~1rr^Zx)!r*hu{iY)b^CJI zUMBZ*J=Lm-4}hH0B~=}pDrF5WQC#gK1``vsbT3f_V;@ifUt$iT;S9qKt?Wg#YB)kh zN$SzvQ`mz$sPt?AHIFA}P>~t3y;0huVZNl9+S>1yKnE&R=pm&^Z8gGoPl13G?QHfy z@{Rbe6h$*P^?d_RY;>1q&RM@99F%_BWFs?g7wE};DUrFl(mnCvj0hirEFLc{1%~%M zhWe*&sgw#W6evow1ZR|V&nS7GQECO$rBR-YUmEL?ekFae8AWB|3l;V=Dn(~hRF%>c z&c&{61bo%qO&ThN7V@)3eIccJM#}n(R6}|lE!DxLWh!VVvt|)r58I$%MCBMe{}^6i!~L zeA-c5Sj6k}!hP9e@lfGWE%(Al|5|^!E`vKi<{=BNaz+M$gr%@gKP~kM{7GTHg8A+z?dWAyGk8)F; zr_6J3w_Ana>;fl`(8P&!(vMEdYE$X5((2~9OPrr{Y!{sEj!!LL-6?g63RCPFa8dRX zt{>cD)-HgGQ>{u}h#O8s^jQubq$kOeY1s5=Y&q_$eRd;kenouQ-y{aahSf~(S@NEn z%HO1fJ6_1}>g?M`Ew08_jqSCrYXpCuE8D;nC|7*it1Tk)ls+`1v&DMa*+aeIp69)G zsnP(NY^pJuw5A7X)faC#k)1ba48$lRJewMC$lcP(ghz=Rz3~p*f3`QGN#e0t{>mdZ2EO`2q2qvs zFr>m!J6nRhA1gKp2||wu+H;>A8j7It%)wdwBY-I_F1;gu78auAll|7nYd_Jbsadq|wAOfFm(ees~Q@&Rx`$X!8M|IGZoBCUCT`oo{-BKwh6TAy1Wg=T#K$ z{YOPBh6cR)Q3anw@I##x!X-{Vy-Jh4wVifz%0o(7ru~z15nx?u{Qjt}&inN=Gt)Kp zSAmrxfu%wPp=KkGiEkZT*+^czdw;ED=ZUXZxmf+M;FiGx&`O{itr=-;kuIJ3*o47-vR35XE zVf#b3cA4Yk4s|vHuM4vl)X4|UYKaq`m90)bS;acVc@3oFM|jjYuQuf_$rvj=(I2pq z<0WmRKW%^0M|0y|E?*MCi8Duq9yd4Sekte^CqmH7*_-*onzYEh&O|@supM6MT4UJ{)-ekS={y}WI z5ZzK5Pq0W#|2s)lqp`wsaCd6T1&&fflgIENqWgBuOg9%-jEmN+PIkbDr2d^s+WRy*{!pa6FPFcL*24$sI(492rYBtwoLGYLyd`wDSbrGK)^6 zvP!%dB=1PDq-(9*loHD4F7NZcK_o&kG=;yVEP#J85Y|V?I%DF1!B}*9i0x_?!)RNt zZ&g*?1B-p9z7lsc_8QB^SfY29(!{0WbEE9V5uA@7XFu-BvD2)osxPtMj)*|4-?(<; zBjsJ0;f7U?OA2w1Qy&jG)HaVX?Cs8UKggH|qeqC4`nPVq>8F9sFA816^#bj1vIR0j zZ_(W^1DbnTCc%hj!RMM5TBeRyr4P5f?O_LDOo)YJQsz>D;`0|O+O*YGGL{M~KJFzK zzJ32}eQr%QRkxBi>}|CeA~vc_xiojRw>?nJvJ}dEOJOTBw5oPh;`4Z?cNoIgc}si}UTXHHw&U7e6(n-cK=))}^tJvQ zZyUKNRw&eXxTv78N28Cai8mdBpYeRaoES0M8}PAfB^WWCP95@;*=x766AM@Gf0jXm&Jz@wS*L!?flmRj|A59S)nDta<8lTkl`{ zPV(%Ti?AuPe|o8>7EB-Me-yQh>tQSUxp6sYlTl4x!)fQ%Dp3G#eYO&JY!lyJ0AY5# ze8;%ya8kvbOd|37k;N-IpJdUVlS<2rW;tx=H?_5J0N+Q?x;WgcFnwZ@Vv=Hgr+>*= zzFnKzbM3l`hOhm{lh;1Sh(dL1dCea0ef@+z4BVGtJ^&UHxb^_S$OS~mpe{9AcYGH4 zOc>hOGPPSV24kIJZs#gB=Sp@!z5|7-z*DD+1g=UW$x6K{2-2hH{tka+eW~sT|Zwoo`_o;BTP*Zrl`WG6~Vu5gOUoN2rW79 z51>GEe&JV7i!PichWto8$T3OL7fk`8ye#+vS^X}$&a3|Dvghie9hk|G5!=-Neruq7 zqe<;7Rcpo)u&k4?!U)36 zR?(r4j-C(J+e$=hG?KcSE7{aMYpbg&52Rk(RE3R&i;um_Uf5NF3^+(niLJNO2QzmH zk1CarDO|v~ox}<{?_Hr6H2WZnNky>Pt|pwBw@|H1rqHjN>=Gk1fF8-j_NK#2Bk8=9 zT*$O&EmS|)cTBgp>6URAb1ZW(Kp;1}iYdvZ%5a&Zcz!7$+DJU9k6EvhDi(A2Ss;Dz zw6V-RyAC4I6M2$HJkHhey47#DFq+h-3pOD5f?|@fgf~l=*s0H-lEZ9r;9RAp2IY2( zQ@7HTA)@(eRD|Fw;?t)*?De^pZ7?w%?{ENauq zXksZ={wcZh(Uq(G?JxYu)^Lm&RGG1uBai)($v!L>ghIF-gB z6SUPb-9#h->g1i|hgIm;VEb9?Z|79^@5l8XacV2_sfKUMovu}&*Uu1kUT5fR+_6=* z$;2#CmX3)hi{tha9TIr$spYpKeI6G zqYf4ANlJn;d`LH^>trkwT1Zk8pH9(AarQUlOxfQ8Z&gV9`zZoU&=rVB#)RKhVXn{Q zJM#@+zwDRfM~5G-&Aj@4W`#fvV}URskum%-zj5XK8`BJ}>wBBZ#Y~bm42?Y7{5RrN zeDL|{M@14U*GEY^lA$3VWloL{1IyyeD1r<%2SU&*d1s?@M8Fdq(YG-&R2Ox$l`02P zcKMejOO&o?PlS{;T&PN}`TRP2y~6kgEtceSLNaBu>}Sc-+t1?+t}tI81C*Oxtf5tT zLc!&fIg~r^WQU^^tY#!+_z)TwNkV&m*oSyWS?{3w`~XL!C{jhAXV zwQF}_DPCoi=GqknA(|5^1Q%lQH&TdmDs*P8vWd)Y-^kCJ7U@jTo>7-7l2>)rg!(gIBWvB!qx3O4 zPwT=k(7r>Imke!wy)8!H)sPT!ElO$f5kwnj`K$Bj!e&F0Z}o1NpT{zg`(hj{Ecd;Pb)N>hIQEHy!)ED`b3;Tl&s zwBKK`cY3i!W4?jk<-kWtl;1M`NI2pY(|;!&2d8KYa$B!N%mTvFA4ARj`0->6t!&(e z!fsR$$l3OWS2f3R73?I>azgOIE^+XcvsVQk&WjYyG}S_{-gmxwQ(T#{ z@XR(Vzv_6SQ2q$%6}McddeoHUq0Rc1W#jm;rBk;th^J0Kc!#TgQquUKwPEpXRd_#f z>H74Z*{G7;I7yPZ3gx6L-N97LSp8+JveB+bL!Q!!wN$XL^22y^RG!Yf;G#*)ncU{Pt_L3bsr(md6sAR~} zdDSCvFR|ue&Md0XRgJ8n`|BIUm4cH{BBrht=H~hXJoGohmE=-PCq4HJh{so}9WO@X z;9I7Mv5Q2#=f>d{P@#s~rK;!@&kNP~iuVcC%)YxQNiK=ohJT-!TdB&|{jz!N93(=S zOJefk4e)X+J5B!8EUzQq!SjlO21KL5H$bE#Tq>5TZyk6ICaz3}O^9mZzHV0;lf_g; zFKfhTX4+#=42xS+FHm5cRRQ$gUG{{~H#)p2vk3+^hrVCaVKEA`dHa~OD2H=$vs!+< zTmnX>p1*ceCu{vqi~TD=aqYTJnj348`suo}4v%O;gV6_{;|4k2kTJ!^216G1OH`k1 ztvZ4xg6lRd?*MA zxJ1dZE3X=ba!k5T8R(c_TUoh9Q_AdNVoA`B>PDglxoP!|Mc=l!Sz$?Iv|qwtjdv>7 z5ad3PL}7|^ho`6=*nild{f8G;|FCJ~>m(!>7wFG1MXZLt;y4OZtQS9fOvf2AJbvrC zl5PPe4mp;3tTi<-CZ#$=)>!i-9roam|4{vqu`DI~S|z6TLyvX-#f6FaK_}bTQj#2( z`;0CfDlDzXVjZ9H15&!p_R7UXTTPgnu*~t6?UY{PV#w~<@8R#b$CP5GGd0f($a_!f z?Ft8obFWR$;B8U#OZikCe!kfZCZwhlm^M2X9~w>LNCQt*l8Z$d*hL)fU`$yxhq+Qwe@JqJ2`LX zgVnlpsSgiY+QkD1ZdMt+m6xc!x^Q)HA+rdKw z(j_fsXV&(-9oE;gmZf7f%qCQay^vqIA}{dh{)~-sz?(9b0Qj|Xh2XT?A?8xI$qOjs z+uBre3OLvF1ZN1b4$uWZn=FirK5vQb*CIa+i+0=ou<}ZO$MORB)G}Q{Ey?lOaDG(&Nm%SYg(y3tV8_!l=Sdp7nrMipZR(<_b8k{C zb;OOsZ)K8Y6E6YNbs1wpV;cO3Q1Y%uJ&A@-uz?Xg5Cd=o_?Z^pcI>0- zeX51F0gV^3B%A8RT18h#stqA@8A*m4$EuEr zGiC-nqqC3q(aulxe>5ytlD8SD&`nv^YbY`xd~%&T9jm54I|goqj-4u?TqO6=?Bt35 zI$jMu<~tGInn(p#=4avH`NW9$)KELNrFV<5M^EY?NxF<_C9)=*x7@78sy=IU@9s;` z<+~p1F^36wOG*npxa%JdrK=ICZ@Xmxb9{Lo-H_O}#9xe0OSQbF^i+9E>14{!m#@>t zHeuNLE?Z|y)4M{AxWn3wflB=g^m^~xnL-?BN|o<3=!9=(n}P_mNec<6<8pYnk6HGg z(_nYi`zp(*=w?NzVy_{pEDoyQvW#!{JbxTtsod#VXm*lyTkeh{{57{`d0YyW0^JH> zJ)e(i=ZC~LP-=@Z-z8dKg~vt*M)FT&fQ$+s+%VgcV5^T|8iFMTs-LJ|FvaR=xD{Ks zSyq3t+i&ljZk0VH0HNW;sXh9E75!nZ`o|qK=f5zbQt3`$hkE|&?snEn^$sRx=4S&V zKp=4BiInVMCs1YtsL&(TEkB6s;3UlQIbxe_JFa?r6%B(B?dCYy%O5oByH$ICX56TR zCVy_+R*}Nz{Xit*yZnJhwzzhZw}I(575iQS2IG?}wdOw1UbwC_(e3RDZNFNw{rH2L z8oCKL+pt^)9g-vH;v*s>&|jWhOFiIoTjCMw zmEA0nE%p!C{%)P$pRo=37tyk`zgUX-8HVvq4j)wiLAi?=^n<#-TTJ&$UEe3gKy6Y! zTT24t{B6hd`Qt*8`bgV5O(2ri7ah;7rrBz?6?I55^w}|S&yQHoybXI? zpe8|a8FxHydPpMe^5wON$j%s=n!0InLLbZ^03)D~8zcN>*f;|glAOkr#$PD-nn-b5 z(C2#BT#WPc7c{m%2|ska5=4=$5^0u={sAs7M~ZYM<{?&5QAqxLcq|H?f`p^HTBzT> z&A5DRO=2qUat_f<(E=JE|H<}>;U@Xg;S8Jo+w87f5`Fvg;^N&@9G&Olsk=WB99_gL zX)8mUAPXN}7!@8NU5mQTm$A7!OfLIgm7~I-U&7%iJ|04&v^ROkc^Vi`N!(-o+^S2- zT5!-TXHoWjsbtV;rEh(iuZ!~Ky}Srz^)&02@yn#c!OqGIb8HNjqxl{=M0Lu=wjG0I z(L~!;@Z!&Y{6oC`@}AYxws{Mlg<8Uw*X|C-Kio-*{m7Cv@@4?`Lem4~tKOHl>VBXx z)W+T4c4z)ml|i`Rzv>K?69}a<=s#7Ohg!SMxDeqn<(2UmGdgp03|az)dpPoh<6I25 zZR(g8>}jj-jvaZnXQ&NTTm!U%lH1zatWz;DdqhKLNS@An}+zOPPQN>6= z2m5dd;WX-|&+J{nxamSn2O*?$3nFRhibM;!NJht);nTG(Alie|=L2uD*eAqVdZ}ZA z&i`n|_i391*0o{8zWWUMzWY0<>r1R*-G*KdNN{5Bjo}s#3kEyAFhcr;=0xAb2W+zD z0wVoA=18Hg3xxBDz-N^yDa#5n#)it>Tx|<&mQGgG%)Rdu4i|Pm6uFvj4$W;0c#PJ| ztYVe9d0Rhz|G{O7YG%%kd&nsRaUjyf$kAGwOk26g52bo}?0V{`f4euqFz3LSyWqve zTf6`q8R8be2fvHL2LOp!g5< zIDNj&v%;#f(Da0#&vWq_ykZ|3(05~h}R zRE`I|*dp7)UK{b2)&+aaUdrQNtu{~I$+qw1TfHQ~UzoJhHUIGLKulM4Udm3Y(#IS8 z#}$joV|2qt6iO@chP%T?$nBx$hALqnBpnKyhj)r;(Ofw@GO}_90|5r4$m}hf?u1&5 zC&Ugu$8SUW>F$mgpO8M?L;~ADEb<91SXryq?(pPlf~F74<%pBcY{r`K-0@u7HW1FU zt0N7adId#6$5#mTipN)nB@4thMx6R_mfhjuzJ0_Skp#2Z00_Bg`#r`03Z{UeE(9P5J3H;qoAPJ5hv%rIeckLe)({&BX1F z36Ckgpa21%3$Kf{oi!@W5WvOS%Epn`g`eICm9GoIi#k5L45SBqDT2JiPcM9S1E3|Z z1Q4}#KmcGI5DqZA2?z!Sz#$wkC~C~Y!-mQY0tNv=+&~bF9n8fG1@VHoeoZ$;FL0KM ziqFB+j8{cW;%kMdGk$stB+`x-2y}LK=5U5`*gBX4!8|-XKoA57fv}@WusgciAdOtu zZ5$bXA@VC7F@&RugQXo3m0k&OM%T#L)(Oc^Pyf4|S6?}^w)?Fj8%K^WTymJ$S_55- z>`i=nbz2%z}lWCugo!EiMQm>0^)3*u%4fq6k7KHwk9eyjZp z9Z^*NFH{0EHCtONfnQ`OXL%PP>S|(z0H7GKG`B%G0K`=RJRDz%egD8e(2}<`wKQ}6 zsTO@J`42R{kVpIsjqgf+rNIY06Z3as|Dx0X19jMar}y8f19bzX4(RXIfx5_xO6Xzg zWP)(`*E0NF>A$++C~eEj|G6buTmQcGUmoEVvo&$DMrB`-787uCvNYv|8k_LId0-~& zP%zwt9R@PxWal;mqmH0pQ<$+S2xex6_+ni0^4~xEdu5{TN!y^ZzS)={exu6`g2K5` zbP+I42sI(1OUoyLW>3Zyp92^m6@4fixe@#R4eUI|1i`+G`LZx;?xjO0x z!hz~lARtZ%7wV9k3x-Oo_=|u2Qs|qe&mKN&0xv4Fp8ybvu(mtPe+YDRGCq@-E?6HW zqF--+d+O`OuZ{f8QGe*cztoHBmQgVQ_*+o;^%+$C|HjX^7XP=BpOC z;`%0wpE&xjxPC%N--`TKT;F8z6G#6Q*G~xPTao|k;v)DqtVF%a;-_~;4H|zmZvDej znIFuZ{Wc8)KbtxGx<=@aGiQ(rHIUM?l78nNU^lBWXmGbjj`=<(6q5kw)$%Gl z8x7;S1QdaD9jBF8$WNO@>KU)URzIMLND3r}+wO%+$^$SamztxGoS8 Date: Thu, 15 Dec 2016 22:58:27 +0100 Subject: [PATCH 3/4] Button widget annotations: implement radio button value fetching according to the specification --- src/core/annotation.js | 46 +++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index 615bc3f96..432f5c3dc 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -776,24 +776,42 @@ var ButtonWidgetAnnotation = (function ButtonWidgetAnnotationClosure() { this.data.pushbutton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); this.data.radio = !this.data.pushbutton && this.hasFieldFlag(AnnotationFieldFlag.RADIO); + if (isName(this.data.fieldValue)) { this.data.fieldValue = this.data.fieldValue.name; + } else { + warn('Button widget annotation: field value is not a `Name` object.'); } - // Get the value of the radio button + if (this.data.radio) { - // Generate a unique ID in case no value is found - this.data.buttonValue = Math.random().toString(16).slice(2); - var appearanceState = params.dict.get('AP'); - if (isDict(appearanceState)) { - var appearances = appearanceState.get('N'); - if (isDict(appearances) && appearances.has('Off')) { - var keys = appearances.getKeys(); - for (var i = 0, ii = keys.length; i < ii; i++) { - if (keys[i] !== 'Off') { - this.data.buttonValue = keys[i]; - break; - } - } + this.data.fieldValue = this.data.buttonValue = null; + + // The parent field's `V` entry holds a `Name` object with the appearance + // state of whichever child field is currently in the "on" state. + var fieldParent = params.dict.get('Parent'); + if (!isDict(fieldParent) || !fieldParent.has('V')) { + return; + } + var fieldParentValue = fieldParent.get('V'); + if (!isName(fieldParentValue)) { + return; + } + this.data.fieldValue = fieldParentValue.name; + + // The button's value corresponds to its appearance state. + var appearanceStates = params.dict.get('AP'); + if (!isDict(appearanceStates)) { + return; + } + var normalAppearanceState = appearanceStates.get('N'); + if (!isDict(normalAppearanceState)) { + return; + } + var keys = normalAppearanceState.getKeys(); + for (var i = 0, ii = keys.length; i < ii; i++) { + if (keys[i] !== 'Off') { + this.data.buttonValue = keys[i]; + break; } } } From a428899b3c92bb276409fa5f7a969d109d89460c Mon Sep 17 00:00:00 2001 From: Tim van der Meij Date: Thu, 15 Dec 2016 23:49:46 +0100 Subject: [PATCH 4/4] Button widget annotations: improve unit tests, simplify code and remove labels Modern browsers support styling radio buttons and checkboxes with CSS. This makes the implementation much easier, and the fallback for older browsers is still decent. --- src/core/annotation.js | 19 ++--- src/display/annotation_layer.js | 33 +++----- test/annotation_layer_test.css | 28 ++++--- test/unit/annotation_layer_spec.js | 118 +++++++++++------------------ web/annotation_layer_builder.css | 74 +++--------------- 5 files changed, 88 insertions(+), 184 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index 432f5c3dc..75d030535 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -773,17 +773,18 @@ var ButtonWidgetAnnotation = (function ButtonWidgetAnnotationClosure() { function ButtonWidgetAnnotation(params) { WidgetAnnotation.call(this, params); - this.data.pushbutton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); - this.data.radio = !this.data.pushbutton && - this.hasFieldFlag(AnnotationFieldFlag.RADIO); - - if (isName(this.data.fieldValue)) { + this.data.checkBox = !this.hasFieldFlag(AnnotationFieldFlag.RADIO) && + !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); + if (this.data.checkBox) { + if (!isName(this.data.fieldValue)) { + return; + } this.data.fieldValue = this.data.fieldValue.name; - } else { - warn('Button widget annotation: field value is not a `Name` object.'); } - if (this.data.radio) { + this.data.radioButton = this.hasFieldFlag(AnnotationFieldFlag.RADIO) && + !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); + if (this.data.radioButton) { this.data.fieldValue = this.data.buttonValue = null; // The parent field's `V` entry holds a `Name` object with the appearance @@ -831,7 +832,7 @@ var ButtonWidgetAnnotation = (function ButtonWidgetAnnotationClosure() { if (this.appearance) { return Annotation.prototype.getOperatorList.call(this, evaluator, task, - renderForms); + renderForms); } return Promise.resolve(operatorList); } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index e5382cdd4..b70992adf 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -77,14 +77,12 @@ AnnotationElementFactory.prototype = case 'Tx': return new TextWidgetAnnotationElement(parameters); case 'Btn': - if (!parameters.data.pushbutton) { - if (parameters.data.radio) { - return new RadioButtonWidgetAnnotationElement(parameters); - } else { - return new CheckboxWidgetAnnotationElement(parameters); - } + if (parameters.data.radioButton) { + return new RadioButtonWidgetAnnotationElement(parameters); + } else if (parameters.data.checkBox) { + return new CheckboxWidgetAnnotationElement(parameters); } else { - warn('Unimplemented push button'); + warn('Unimplemented button widget annotation: pushbutton'); } break; case 'Ch': @@ -152,7 +150,6 @@ var AnnotationElement = (function AnnotationElementClosure() { var height = data.rect[3] - data.rect[1]; container.setAttribute('data-annotation-id', data.id); - container.setAttribute('data-annotation-name', data.fieldName); // Do *not* modify `data.rect`, since that will corrupt the annotation // position on subsequent calls to `_createContainer` (see issue 6804). @@ -568,15 +565,11 @@ var CheckboxWidgetAnnotationElement = var element = document.createElement('input'); element.disabled = this.data.readOnly; element.type = 'checkbox'; - element.id = this.data.fieldName; if (this.data.fieldValue && this.data.fieldValue !== 'Off') { - element.checked = true; + element.setAttribute('checked', true); } - this.container.appendChild(element); - element = document.createElement('label'); - element.htmlFor = this.data.fieldName; - this.container.appendChild(element); + this.container.appendChild(element); return this.container; } }); @@ -608,22 +601,16 @@ var RadioButtonWidgetAnnotationElement = this.container.className = 'buttonWidgetAnnotation radioButton'; var element = document.createElement('input'); - var id = this.data.fieldName + '.' + this.data.buttonValue; element.disabled = this.data.readOnly; element.type = 'radio'; - element.id = id; element.name = this.data.fieldName; - element.value = this.data.buttonValue; if (this.data.fieldValue === this.data.buttonValue) { - element.checked = true; + element.setAttribute('checked', true); } - this.container.appendChild(element); - element = document.createElement('label'); - element.htmlFor = id; - this.container.appendChild(element); + this.container.appendChild(element); return this.container; - }, + } }); return RadioButtonWidgetAnnotationElement; diff --git a/test/annotation_layer_test.css b/test/annotation_layer_test.css index be7935b64..a18dd77ec 100644 --- a/test/annotation_layer_test.css +++ b/test/annotation_layer_test.css @@ -15,6 +15,11 @@ /* Used for annotation layer tests */ +* { + padding: 0; + margin: 0; +} + .annotationLayer { position: absolute; left: 0; @@ -46,8 +51,8 @@ .annotationLayer .textWidgetAnnotation input, .annotationLayer .textWidgetAnnotation textarea, .annotationLayer .choiceWidgetAnnotation select, -.annotationLayer .buttonWidgetAnnotation.checkBox label, -.annotationLayer .buttonWidgetAnnotation.radioButton label { +.annotationLayer .buttonWidgetAnnotation.checkBox input, +.annotationLayer .buttonWidgetAnnotation.radioButton input { background-color: rgba(0, 54, 255, 0.13); border: 1px solid transparent; box-sizing: border-box; @@ -67,8 +72,8 @@ .annotationLayer .textWidgetAnnotation input[disabled], .annotationLayer .textWidgetAnnotation textarea[disabled], .annotationLayer .choiceWidgetAnnotation select[disabled], -.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled] + label, -.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] + label { +.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled], +.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] { background: none; border: 1px solid transparent; } @@ -79,19 +84,12 @@ padding-right: 0; } -.annotationLayer .buttonWidgetAnnotation.checkBox label, -.annotationLayer .buttonWidgetAnnotation.radioButton label { - position: absolute; -} - .annotationLayer .buttonWidgetAnnotation.checkBox input, .annotationLayer .buttonWidgetAnnotation.radioButton input { - position: absolute; - left: -9999px; -} - -.annotationLayer .buttonWidgetAnnotation.radioButton label { - border-radius: 50%; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + appearance: none; } .annotationLayer .popupAnnotation { diff --git a/test/unit/annotation_layer_spec.js b/test/unit/annotation_layer_spec.js index 5bdec7f9f..ba10db204 100644 --- a/test/unit/annotation_layer_spec.js +++ b/test/unit/annotation_layer_spec.js @@ -869,95 +869,63 @@ describe('Annotation layer', function() { }); }); - describe('CheckboxWidgetAnnotation', function() { - var checkboxWidgetDict; + describe('ButtonWidgetAnnotation', function() { + var buttonWidgetDict; beforeEach(function (done) { - checkboxWidgetDict = new Dict(); - checkboxWidgetDict.set('Type', Name.get('Annot')); - checkboxWidgetDict.set('Subtype', Name.get('Widget')); - checkboxWidgetDict.set('FT', Name.get('Btn')); + buttonWidgetDict = new Dict(); + buttonWidgetDict.set('Type', Name.get('Annot')); + buttonWidgetDict.set('Subtype', Name.get('Widget')); + buttonWidgetDict.set('FT', Name.get('Btn')); done(); }); afterEach(function () { - checkboxWidgetDict = null; + buttonWidgetDict = null; }); - it('should have proper flags', - function() { - var checkboxWidgetRef = new Ref(124, 0); - var xref = new XRefMock([ - { ref: checkboxWidgetRef, data: checkboxWidgetDict, } - ]); + it('should handle checkboxes', function() { + buttonWidgetDict.set('V', Name.get('1')); - var checkboxWidgetAnnotation = - annotationFactory.create(xref, checkboxWidgetRef); - expect(checkboxWidgetAnnotation.data.radio).toEqual(false); - expect(checkboxWidgetAnnotation.data.pushbutton).toEqual(false); - expect(checkboxWidgetAnnotation.data.fieldValue).toEqual(null); - }); + var buttonWidgetRef = new Ref(124, 0); + var xref = new XRefMock([ + { ref: buttonWidgetRef, data: buttonWidgetDict, } + ]); - it('should have a proper value', - function() { - checkboxWidgetDict.set('V', Name.get('1')); - - var checkboxWidgetRef = new Ref(124, 0); - var xref = new XRefMock([ - { ref: checkboxWidgetRef, data: checkboxWidgetDict, } - ]); - - var checkboxWidgetAnnotation = - annotationFactory.create(xref, checkboxWidgetRef); - expect(checkboxWidgetAnnotation.data.fieldValue).toEqual('1'); - }); - }); - - describe('RadioButtonWidgetAnnotation', function() { - var radioButtonWidgetDict; - - beforeEach(function (done) { - radioButtonWidgetDict = new Dict(); - radioButtonWidgetDict.set('Type', Name.get('Annot')); - radioButtonWidgetDict.set('Subtype', Name.get('Widget')); - radioButtonWidgetDict.set('FT', Name.get('Btn')); - radioButtonWidgetDict.set('Ff', AnnotationFieldFlag.RADIO); - - done(); + var buttonWidgetAnnotation = + annotationFactory.create(xref, buttonWidgetRef); + expect(buttonWidgetAnnotation.data.checkBox).toEqual(true); + expect(buttonWidgetAnnotation.data.fieldValue).toEqual('1'); + expect(buttonWidgetAnnotation.data.radioButton).toEqual(false); }); - afterEach(function () { - radioButtonWidgetDict = null; + it('should handle radio buttons', function() { + var parentDict = new Dict(); + parentDict.set('V', Name.get('1')); + + var normalAppearanceStateDict = new Dict(); + normalAppearanceStateDict.set('2', null); + + var appearanceStatesDict = new Dict(); + appearanceStatesDict.set('N', normalAppearanceStateDict); + + buttonWidgetDict.set('Ff', AnnotationFieldFlag.RADIO); + buttonWidgetDict.set('Parent', parentDict); + buttonWidgetDict.set('AP', appearanceStatesDict); + + var buttonWidgetRef = new Ref(124, 0); + var xref = new XRefMock([ + { ref: buttonWidgetRef, data: buttonWidgetDict, } + ]); + + var buttonWidgetAnnotation = + annotationFactory.create(xref, buttonWidgetRef); + expect(buttonWidgetAnnotation.data.checkBox).toEqual(false); + expect(buttonWidgetAnnotation.data.radioButton).toEqual(true); + expect(buttonWidgetAnnotation.data.fieldValue).toEqual('1'); + expect(buttonWidgetAnnotation.data.buttonValue).toEqual('2'); }); - - it('should have proper flags', - function() { - var radioButtonWidgetRef = new Ref(124, 0); - var xref = new XRefMock([ - { ref: radioButtonWidgetRef, data: radioButtonWidgetDict, } - ]); - - var radioButtonWidgetAnnotation = - annotationFactory.create(xref, radioButtonWidgetRef); - expect(radioButtonWidgetAnnotation.data.radio).toEqual(true); - expect(radioButtonWidgetAnnotation.data.pushbutton).toEqual(false); - expect(radioButtonWidgetAnnotation.data.fieldValue).toEqual(null); - }); - - it('should have a proper value', - function() { - radioButtonWidgetDict.set('V', Name.get('1')); - - var radioButtonWidgetRef = new Ref(124, 0); - var xref = new XRefMock([ - { ref: radioButtonWidgetRef, data: radioButtonWidgetDict, } - ]); - - var radioButtonWidgetAnnotation = - annotationFactory.create(xref, radioButtonWidgetRef); - expect(radioButtonWidgetAnnotation.data.fieldValue).toEqual('1'); - }); }); describe('ChoiceWidgetAnnotation', function() { diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 15e2bedc7..982f66134 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -43,7 +43,9 @@ .annotationLayer .textWidgetAnnotation input, .annotationLayer .textWidgetAnnotation textarea, -.annotationLayer .choiceWidgetAnnotation select { +.annotationLayer .choiceWidgetAnnotation select, +.annotationLayer .buttonWidgetAnnotation.checkBox input, +.annotationLayer .buttonWidgetAnnotation.radioButton input { background-color: rgba(0, 54, 255, 0.13); border: 1px solid transparent; box-sizing: border-box; @@ -63,8 +65,8 @@ .annotationLayer .textWidgetAnnotation input[disabled], .annotationLayer .textWidgetAnnotation textarea[disabled], .annotationLayer .choiceWidgetAnnotation select[disabled], -.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled] + label, -.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] + label { +.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled], +.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] { background: none; border: 1px solid transparent; cursor: not-allowed; @@ -72,7 +74,9 @@ .annotationLayer .textWidgetAnnotation input:hover, .annotationLayer .textWidgetAnnotation textarea:hover, -.annotationLayer .choiceWidgetAnnotation select:hover { +.annotationLayer .choiceWidgetAnnotation select:hover, +.annotationLayer .buttonWidgetAnnotation.checkBox input:hover, +.annotationLayer .buttonWidgetAnnotation.radioButton input:hover { border: 1px solid #000; } @@ -99,66 +103,12 @@ width: 115%; } -.annotationLayer .buttonWidgetAnnotation.checkBox label, -.annotationLayer .buttonWidgetAnnotation.radioButton label { - background-color: rgba(0, 54, 255, 0.13); - border: 1px solid transparent; - box-sizing: border-box; - cursor: pointer; - height: 100%; - position: absolute; - width: 100%; -} - .annotationLayer .buttonWidgetAnnotation.checkBox input, .annotationLayer .buttonWidgetAnnotation.radioButton input { - position: absolute; - left: -9999px; -} - -.annotationLayer .buttonWidgetAnnotation.radioButton label { - border-radius: 50%; -} - -.annotationLayer .buttonWidgetAnnotation.checkBox label:hover, -.annotationLayer .buttonWidgetAnnotation.checkBox label:focus, -.annotationLayer .buttonWidgetAnnotation.checkBox input:focus + label, -.annotationLayer .buttonWidgetAnnotation.radioButton label:hover, -.annotationLayer .buttonWidgetAnnotation.radioButton label:focus, -.annotationLayer .buttonWidgetAnnotation.radioButton input:focus + label { - border: 1px solid #000; -} - -.annotationLayer .buttonWidgetAnnotation.checkBox input:checked + label:before, -.annotationLayer .buttonWidgetAnnotation.radioButton input:checked + label:before { - content: ''; - left: 50%; - top: 50%; - position: absolute; -} - -.annotationLayer .buttonWidgetAnnotation.checkBox input:checked + label:before { - border-bottom: 1px solid #000; - border-left: 1px solid #000; - height: 25%; - -webkit-transform: translate(-50%, -65%) rotateZ(-45deg); - -moz-transform: translate(-50%, -65%) rotateZ(-45deg); - -o-transform: translate(-50%, -65%) rotateZ(-45deg); - -ms-transform: translate(-50%, -65%) rotateZ(-45deg); - transform: translate(-50%, -65%) rotateZ(-45deg); - width: 60%; -} - -.annotationLayer .buttonWidgetAnnotation.radioButton input:checked + label:before { - background-color: #000; - border-radius: 50%; - height: 50%; - -webkit-transform: translate(-50%, -50%); - -moz-transform: translate(-50%, -50%); - -o-transform: translate(-50%, -50%); - -ms-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - width: 50%; + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + appearance: none; } .annotationLayer .popupWrapper {