@@ -21,9 +21,9 @@ import {
21
21
AnnotationReplyType ,
22
22
AnnotationType ,
23
23
assert ,
24
+ escapeString ,
24
25
isString ,
25
26
OPS ,
26
- stringToBytes ,
27
27
stringToPDFString ,
28
28
Util ,
29
29
warn ,
@@ -33,7 +33,7 @@ import { Dict, isDict, isName, isRef, isStream } from "./primitives.js";
33
33
import { ColorSpace } from "./colorspace.js" ;
34
34
import { getInheritableProperty } from "./core_utils.js" ;
35
35
import { OperatorList } from "./operator_list.js" ;
36
- import { Stream } from "./stream.js" ;
36
+ import { StringStream } from "./stream.js" ;
37
37
38
38
class AnnotationFactory {
39
39
/**
@@ -893,19 +893,199 @@ class WidgetAnnotation extends Annotation {
893
893
if ( renderForms ) {
894
894
return Promise . resolve ( new OperatorList ( ) ) ;
895
895
}
896
- return super . getOperatorList (
897
- evaluator ,
898
- task ,
899
- renderForms ,
900
- annotationStorage
896
+
897
+ if ( ! this . _hasText ) {
898
+ return super . getOperatorList (
899
+ evaluator ,
900
+ task ,
901
+ renderForms ,
902
+ annotationStorage
903
+ ) ;
904
+ }
905
+
906
+ return this . _getAppearance ( evaluator , task , annotationStorage ) . then (
907
+ content => {
908
+ if ( this . appearance && content === null ) {
909
+ return super . getOperatorList (
910
+ evaluator ,
911
+ task ,
912
+ renderForms ,
913
+ annotationStorage
914
+ ) ;
915
+ }
916
+
917
+ const operatorList = new OperatorList ( ) ;
918
+
919
+ // Even if there is an appearance stream, ignore it. This is the
920
+ // behaviour used by Adobe Reader.
921
+ if ( ! this . data . defaultAppearance || content === null ) {
922
+ return operatorList ;
923
+ }
924
+
925
+ const matrix = [ 1 , 0 , 0 , 1 , 0 , 0 ] ;
926
+ const bbox = [
927
+ 0 ,
928
+ 0 ,
929
+ this . data . rect [ 2 ] - this . data . rect [ 0 ] ,
930
+ this . data . rect [ 3 ] - this . data . rect [ 1 ] ,
931
+ ] ;
932
+
933
+ const transform = getTransformMatrix ( this . data . rect , bbox , matrix ) ;
934
+ operatorList . addOp ( OPS . beginAnnotation , [
935
+ this . data . rect ,
936
+ transform ,
937
+ matrix ,
938
+ ] ) ;
939
+
940
+ const stream = new StringStream ( content ) ;
941
+ return evaluator
942
+ . getOperatorList ( {
943
+ stream,
944
+ task,
945
+ resources : this . fieldResources ,
946
+ operatorList,
947
+ } )
948
+ . then ( function ( ) {
949
+ operatorList . addOp ( OPS . endAnnotation , [ ] ) ;
950
+ return operatorList ;
951
+ } ) ;
952
+ }
953
+ ) ;
954
+ }
955
+
956
+ async _getAppearance ( evaluator , task , annotationStorage ) {
957
+ const isPassword = this . hasFieldFlag ( AnnotationFieldFlag . PASSWORD ) ;
958
+ if ( ! annotationStorage || isPassword ) {
959
+ return null ;
960
+ }
961
+ let value = annotationStorage [ this . data . id ] || "" ;
962
+ if ( value === "" ) {
963
+ return null ;
964
+ }
965
+ value = escapeString ( value ) ;
966
+
967
+ const defaultPadding = 2 ;
968
+ const hPadding = defaultPadding ;
969
+ const totalHeight = this . data . rect [ 3 ] - this . data . rect [ 1 ] ;
970
+ const totalWidth = this . data . rect [ 2 ] - this . data . rect [ 0 ] ;
971
+
972
+ const fontInfo = await this . _getFontData ( evaluator , task ) ;
973
+ const [ font , fontName ] = fontInfo ;
974
+ let fontSize = fontInfo [ 2 ] ;
975
+
976
+ fontSize = this . _computeFontSize ( font , fontName , fontSize , totalHeight ) ;
977
+
978
+ let descent = font . descent ;
979
+ if ( isNaN ( descent ) ) {
980
+ descent = 0 ;
981
+ }
982
+
983
+ const vPadding = defaultPadding + Math . abs ( descent ) * fontSize ;
984
+ const defaultAppearance = this . data . defaultAppearance ;
985
+ const alignment = this . data . textAlignment ;
986
+ if ( alignment === 0 || alignment > 2 ) {
987
+ // Left alignment: nothing to do
988
+ return (
989
+ "/Tx BMC q BT " +
990
+ defaultAppearance +
991
+ ` 1 0 0 1 ${ hPadding } ${ vPadding } Tm (${ value } ) Tj` +
992
+ " ET Q EMC"
993
+ ) ;
994
+ }
995
+
996
+ const renderedText = this . _renderText (
997
+ value ,
998
+ font ,
999
+ fontSize ,
1000
+ totalWidth ,
1001
+ alignment ,
1002
+ hPadding ,
1003
+ vPadding
1004
+ ) ;
1005
+ return (
1006
+ "/Tx BMC q BT " +
1007
+ defaultAppearance +
1008
+ ` 1 0 0 1 0 0 Tm ${ renderedText } ` +
1009
+ " ET Q EMC"
901
1010
) ;
902
1011
}
1012
+
1013
+ async _getFontData ( evaluator , task ) {
1014
+ const operatorList = new OperatorList ( ) ;
1015
+ const initialState = {
1016
+ fontSize : 0 ,
1017
+ font : null ,
1018
+ fontName : null ,
1019
+ clone ( ) {
1020
+ return this ;
1021
+ } ,
1022
+ } ;
1023
+
1024
+ await evaluator . getOperatorList ( {
1025
+ stream : new StringStream ( this . data . defaultAppearance ) ,
1026
+ task,
1027
+ resources : this . fieldResources ,
1028
+ operatorList,
1029
+ initialState,
1030
+ } ) ;
1031
+
1032
+ return [ initialState . font , initialState . fontName , initialState . fontSize ] ;
1033
+ }
1034
+
1035
+ _computeFontSize ( font , fontName , fontSize , height ) {
1036
+ if ( fontSize === null || fontSize === 0 ) {
1037
+ const em = font . charsToGlyphs ( "M" , true ) [ 0 ] . width / 1000 ;
1038
+ // According to https://en.wikipedia.org/wiki/Em_(typography)
1039
+ // an average cap height should be 70% of 1em
1040
+ const capHeight = 0.7 * em ;
1041
+ // 1.5 * capHeight * fontSize seems to be a good value for lineHeight
1042
+ fontSize = Math . max ( 1 , Math . floor ( height / ( 1.5 * capHeight ) ) ) ;
1043
+
1044
+ let fontRegex = new RegExp ( `/${ fontName } \\s+[0-9\.]+\\s+Tf` ) ;
1045
+ if ( this . data . defaultAppearance . search ( fontRegex ) === - 1 ) {
1046
+ // The font size is missing
1047
+ fontRegex = new RegExp ( `/${ fontName } \\s+Tf` ) ;
1048
+ }
1049
+ this . data . defaultAppearance = this . data . defaultAppearance . replace (
1050
+ fontRegex ,
1051
+ `/${ fontName } ${ fontSize } Tf`
1052
+ ) ;
1053
+ }
1054
+ return fontSize ;
1055
+ }
1056
+
1057
+ _renderText ( text , font , fontSize , totalWidth , alignment , hPadding , vPadding ) {
1058
+ // We need to get the width of the text in order to align it correctly
1059
+ const glyphs = font . charsToGlyphs ( text ) ;
1060
+ const scale = fontSize / 1000 ;
1061
+ let width = 0 ;
1062
+ for ( const glyph of glyphs ) {
1063
+ width += glyph . width * scale ;
1064
+ }
1065
+
1066
+ let shift ;
1067
+ if ( alignment === 1 ) {
1068
+ // Center
1069
+ shift = ( totalWidth - width ) / 2 ;
1070
+ } else if ( alignment === 2 ) {
1071
+ // Right
1072
+ shift = totalWidth - width - hPadding ;
1073
+ } else {
1074
+ shift = hPadding ;
1075
+ }
1076
+ shift = shift . toFixed ( 2 ) ;
1077
+ vPadding = vPadding . toFixed ( 2 ) ;
1078
+
1079
+ return `${ shift } ${ vPadding } Td (${ text } ) Tj` ;
1080
+ }
903
1081
}
904
1082
905
1083
class TextWidgetAnnotation extends WidgetAnnotation {
906
1084
constructor ( params ) {
907
1085
super ( params ) ;
908
1086
1087
+ this . _hasText = true ;
1088
+
909
1089
const dict = params . dict ;
910
1090
911
1091
// The field value is always a string.
@@ -934,37 +1114,6 @@ class TextWidgetAnnotation extends WidgetAnnotation {
934
1114
! this . hasFieldFlag ( AnnotationFieldFlag . FILESELECT ) &&
935
1115
this . data . maxLen !== null ;
936
1116
}
937
-
938
- getOperatorList ( evaluator , task , renderForms , annotationStorage ) {
939
- if ( renderForms || this . appearance ) {
940
- return super . getOperatorList (
941
- evaluator ,
942
- task ,
943
- renderForms ,
944
- annotationStorage
945
- ) ;
946
- }
947
-
948
- const operatorList = new OperatorList ( ) ;
949
-
950
- // Even if there is an appearance stream, ignore it. This is the
951
- // behaviour used by Adobe Reader.
952
- if ( ! this . data . defaultAppearance ) {
953
- return Promise . resolve ( operatorList ) ;
954
- }
955
-
956
- const stream = new Stream ( stringToBytes ( this . data . defaultAppearance ) ) ;
957
- return evaluator
958
- . getOperatorList ( {
959
- stream,
960
- task,
961
- resources : this . fieldResources ,
962
- operatorList,
963
- } )
964
- . then ( function ( ) {
965
- return operatorList ;
966
- } ) ;
967
- }
968
1117
}
969
1118
970
1119
class ButtonWidgetAnnotation extends WidgetAnnotation {
@@ -1148,6 +1297,7 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation {
1148
1297
// Process field flags for the display layer.
1149
1298
this . data . combo = this . hasFieldFlag ( AnnotationFieldFlag . COMBO ) ;
1150
1299
this . data . multiSelect = this . hasFieldFlag ( AnnotationFieldFlag . MULTISELECT ) ;
1300
+ this . _hasText = true ;
1151
1301
}
1152
1302
}
1153
1303
0 commit comments