@@ -1110,7 +1110,7 @@ class CanvasGraphics {
1110
1110
// the transformation must already be set in canvasCtx._transformMatrix.
1111
1111
addContextCurrentTransform ( canvasCtx ) ;
1112
1112
}
1113
- this . _cachedGetSinglePixelWidth = null ;
1113
+ this . _cachedScaleForStroking = null ;
1114
1114
}
1115
1115
1116
1116
beginDrawing ( {
@@ -1159,10 +1159,6 @@ class CanvasGraphics {
1159
1159
this . viewportScale = viewport . scale ;
1160
1160
1161
1161
this . baseTransform = this . ctx . mozCurrentTransform . slice ( ) ;
1162
- this . _combinedScaleFactor = Math . hypot (
1163
- this . baseTransform [ 0 ] ,
1164
- this . baseTransform [ 2 ]
1165
- ) ;
1166
1162
1167
1163
if ( this . imageLayer ) {
1168
1164
this . imageLayer . beginLayout ( ) ;
@@ -1419,6 +1415,9 @@ class CanvasGraphics {
1419
1415
1420
1416
// Graphics state
1421
1417
setLineWidth ( width ) {
1418
+ if ( width !== this . current . lineWidth ) {
1419
+ this . _cachedScaleForStroking = null ;
1420
+ }
1422
1421
this . current . lineWidth = width ;
1423
1422
this . ctx . lineWidth = width ;
1424
1423
}
@@ -1608,14 +1607,14 @@ class CanvasGraphics {
1608
1607
// Ensure that the clipping path is reset (fixes issue6413.pdf).
1609
1608
this . pendingClip = null ;
1610
1609
1611
- this . _cachedGetSinglePixelWidth = null ;
1610
+ this . _cachedScaleForStroking = null ;
1612
1611
}
1613
1612
}
1614
1613
1615
1614
transform ( a , b , c , d , e , f ) {
1616
1615
this . ctx . transform ( a , b , c , d , e , f ) ;
1617
1616
1618
- this . _cachedGetSinglePixelWidth = null ;
1617
+ this . _cachedScaleForStroking = null ;
1619
1618
}
1620
1619
1621
1620
// Path
@@ -1751,33 +1750,17 @@ class CanvasGraphics {
1751
1750
ctx . globalAlpha = this . current . strokeAlpha ;
1752
1751
if ( this . contentVisible ) {
1753
1752
if ( typeof strokeColor === "object" && strokeColor ?. getPattern ) {
1754
- const lineWidth = this . getSinglePixelWidth ( ) ;
1755
1753
ctx . save ( ) ;
1756
1754
ctx . strokeStyle = strokeColor . getPattern (
1757
1755
ctx ,
1758
1756
this ,
1759
1757
ctx . mozCurrentTransformInverse ,
1760
1758
PathType . STROKE
1761
1759
) ;
1762
- // Prevent drawing too thin lines by enforcing a minimum line width.
1763
- ctx . lineWidth = Math . max ( lineWidth , this . current . lineWidth ) ;
1764
- ctx . stroke ( ) ;
1760
+ this . rescaleAndStrokeNoSave ( ) ;
1765
1761
ctx . restore ( ) ;
1766
1762
} else {
1767
- const lineWidth = this . getSinglePixelWidth ( ) ;
1768
- if ( lineWidth < 0 && - lineWidth >= this . current . lineWidth ) {
1769
- // The current transform will transform a square pixel into a
1770
- // parallelogram where both heights are lower than 1 and not equal.
1771
- ctx . save ( ) ;
1772
- ctx . resetTransform ( ) ;
1773
- ctx . lineWidth = Math . floor ( this . _combinedScaleFactor ) ;
1774
- ctx . stroke ( ) ;
1775
- ctx . restore ( ) ;
1776
- } else {
1777
- // Prevent drawing too thin lines by enforcing a minimum line width.
1778
- ctx . lineWidth = Math . max ( lineWidth , this . current . lineWidth ) ;
1779
- ctx . stroke ( ) ;
1780
- }
1763
+ this . rescaleAndStroke ( ) ;
1781
1764
}
1782
1765
}
1783
1766
if ( consumePath ) {
@@ -2002,7 +1985,7 @@ class CanvasGraphics {
2002
1985
this . moveText ( 0 , this . current . leading ) ;
2003
1986
}
2004
1987
2005
- paintChar ( character , x , y , patternTransform , resetLineWidthToOne ) {
1988
+ paintChar ( character , x , y , patternTransform ) {
2006
1989
const ctx = this . ctx ;
2007
1990
const current = this . current ;
2008
1991
const font = current . font ;
@@ -2038,11 +2021,8 @@ class CanvasGraphics {
2038
2021
fillStrokeMode === TextRenderingMode . STROKE ||
2039
2022
fillStrokeMode === TextRenderingMode . FILL_STROKE
2040
2023
) {
2041
- if ( resetLineWidthToOne ) {
2042
- ctx . resetTransform ( ) ;
2043
- ctx . lineWidth = Math . floor ( this . _combinedScaleFactor ) ;
2044
- }
2045
- ctx . stroke ( ) ;
2024
+ this . _cachedScaleForStroking = null ;
2025
+ this . rescaleAndStrokeNoSave ( ) ;
2046
2026
}
2047
2027
ctx . restore ( ) ;
2048
2028
} else {
@@ -2056,16 +2036,8 @@ class CanvasGraphics {
2056
2036
fillStrokeMode === TextRenderingMode . STROKE ||
2057
2037
fillStrokeMode === TextRenderingMode . FILL_STROKE
2058
2038
) {
2059
- if ( resetLineWidthToOne ) {
2060
- ctx . save ( ) ;
2061
- ctx . moveTo ( x , y ) ;
2062
- ctx . resetTransform ( ) ;
2063
- ctx . lineWidth = Math . floor ( this . _combinedScaleFactor ) ;
2064
- ctx . strokeText ( character , 0 , 0 ) ;
2065
- ctx . restore ( ) ;
2066
- } else {
2067
- ctx . strokeText ( character , x , y ) ;
2068
- }
2039
+ this . _cachedScaleForStroking = null ;
2040
+ this . rescaleAndStrokeText ( character , x , y ) ;
2069
2041
}
2070
2042
}
2071
2043
@@ -2156,18 +2128,15 @@ class CanvasGraphics {
2156
2128
}
2157
2129
2158
2130
let lineWidth = current . lineWidth ;
2159
- let resetLineWidthToOne = false ;
2160
2131
const scale = current . textMatrixScale ;
2161
- if ( scale === 0 || lineWidth === 0 ) {
2132
+ if ( scale === 0 ) {
2162
2133
const fillStrokeMode =
2163
2134
current . textRenderingMode & TextRenderingMode . FILL_STROKE_MASK ;
2164
2135
if (
2165
2136
fillStrokeMode === TextRenderingMode . STROKE ||
2166
2137
fillStrokeMode === TextRenderingMode . FILL_STROKE
2167
2138
) {
2168
- this . _cachedGetSinglePixelWidth = null ;
2169
- lineWidth = this . getSinglePixelWidth ( ) ;
2170
- resetLineWidthToOne = lineWidth < 0 ;
2139
+ lineWidth = 0 ;
2171
2140
}
2172
2141
} else {
2173
2142
lineWidth /= scale ;
@@ -2178,6 +2147,8 @@ class CanvasGraphics {
2178
2147
lineWidth /= fontSizeScale ;
2179
2148
}
2180
2149
2150
+ const savedCurrentLineWidth = current . lineWidth ;
2151
+ current . lineWidth = lineWidth ;
2181
2152
ctx . lineWidth = lineWidth ;
2182
2153
2183
2154
let x = 0 ,
@@ -2235,13 +2206,7 @@ class CanvasGraphics {
2235
2206
// common case
2236
2207
ctx . fillText ( character , scaledX , scaledY ) ;
2237
2208
} else {
2238
- this . paintChar (
2239
- character ,
2240
- scaledX ,
2241
- scaledY ,
2242
- patternTransform ,
2243
- resetLineWidthToOne
2244
- ) ;
2209
+ this . paintChar ( character , scaledX , scaledY , patternTransform ) ;
2245
2210
if ( accent ) {
2246
2211
const scaledAccentX =
2247
2212
scaledX + ( fontSize * accent . offset . x ) / fontSizeScale ;
@@ -2251,8 +2216,7 @@ class CanvasGraphics {
2251
2216
accent . fontChar ,
2252
2217
scaledAccentX ,
2253
2218
scaledAccentY ,
2254
- patternTransform ,
2255
- resetLineWidthToOne
2219
+ patternTransform
2256
2220
) ;
2257
2221
}
2258
2222
}
@@ -2277,6 +2241,9 @@ class CanvasGraphics {
2277
2241
}
2278
2242
ctx . restore ( ) ;
2279
2243
this . compose ( ) ;
2244
+
2245
+ current . lineWidth = savedCurrentLineWidth ;
2246
+
2280
2247
return undefined ;
2281
2248
}
2282
2249
@@ -2300,7 +2267,7 @@ class CanvasGraphics {
2300
2267
if ( isTextInvisible || fontSize === 0 ) {
2301
2268
return ;
2302
2269
}
2303
- this . _cachedGetSinglePixelWidth = null ;
2270
+ this . _cachedScaleForStroking = null ;
2304
2271
2305
2272
ctx . save ( ) ;
2306
2273
ctx . transform . apply ( ctx , current . textMatrix ) ;
@@ -3103,47 +3070,121 @@ class CanvasGraphics {
3103
3070
ctx . beginPath ( ) ;
3104
3071
}
3105
3072
3106
- getSinglePixelWidth ( ) {
3107
- if ( this . _cachedGetSinglePixelWidth === null ) {
3108
- // If transform is [a b] then a pixel (square) is transformed
3109
- // [c d]
3110
- // into a parallelogram: its area is the abs value of the determinant.
3111
- // This parallelogram has 2 heights:
3112
- // - Area / |col_1|;
3113
- // - Area / |col_2|.
3114
- // so in order to get a height of at least 1, pixel height
3115
- // must be computed as followed:
3116
- // h = max(sqrt(a² + c²) / |det(M)|, sqrt(b² + d²) / |det(M)|).
3117
- // This is equivalent to:
3118
- // h = max(|line_1_inv(M)|, |line_2_inv(M)|)
3073
+ getScaleForStroking ( ) {
3074
+ // A pixel has thicknessX = thicknessY = 1;
3075
+ // A transformed pixel is a parallelogram and the thicknesses
3076
+ // corresponds to the heights.
3077
+ // The goal of this function is to rescale before setting the
3078
+ // lineWidth in order to have both thicknesses greater or equal
3079
+ // to 1 after transform.
3080
+ if ( ! this . _cachedScaleForStroking ) {
3081
+ const { lineWidth } = this . current ;
3119
3082
const m = this . ctx . mozCurrentTransform ;
3120
-
3121
- const absDet = Math . abs ( m [ 0 ] * m [ 3 ] - m [ 2 ] * m [ 1 ] ) ;
3122
- const sqNorm1 = m [ 0 ] ** 2 + m [ 2 ] ** 2 ;
3123
- const sqNorm2 = m [ 1 ] ** 2 + m [ 3 ] ** 2 ;
3124
- const pixelHeight = Math . sqrt ( Math . max ( sqNorm1 , sqNorm2 ) ) / absDet ;
3125
- if ( sqNorm1 !== sqNorm2 && this . _combinedScaleFactor * pixelHeight > 1 ) {
3126
- // The parallelogram isn't a square and at least one height
3127
- // is lower than 1 so the resulting line width must be 1
3128
- // but it cannot be achieved with one scale: when scaling a pixel
3129
- // we'll get a rectangle (see issue #12295).
3130
- // For example with matrix [0.001 0, 0, 100], a pixel is transformed
3131
- // in a rectangle 0.001x100. If we just scale by 1000 (to have a 1)
3132
- // then we'll get a rectangle 1x1e5 which is wrong.
3133
- // In this case, we must reset the transform, set linewidth to 1
3134
- // and then stroke.
3135
- this . _cachedGetSinglePixelWidth = - (
3136
- this . _combinedScaleFactor * pixelHeight
3137
- ) ;
3138
- } else if ( absDet > Number . EPSILON ) {
3139
- this . _cachedGetSinglePixelWidth = pixelHeight ;
3083
+ let scaleX , scaleY ;
3084
+
3085
+ // We must take into account that outputScale is a part of the
3086
+ // current transform.
3087
+ if ( m [ 1 ] === 0 && m [ 2 ] === 0 ) {
3088
+ // Fast path
3089
+ const normX = Math . abs ( m [ 0 ] ) ;
3090
+ const normY = Math . abs ( m [ 3 ] ) ;
3091
+ const scaledXLineWidth = normX * lineWidth ;
3092
+ const scaledYLineWidth = normY * lineWidth ;
3093
+ scaleX =
3094
+ scaledXLineWidth < this . outputScaleX
3095
+ ? this . outputScaleX / scaledXLineWidth
3096
+ : 1 ;
3097
+ scaleY =
3098
+ scaledYLineWidth < this . outputScaleY
3099
+ ? this . outputScaleY / scaledYLineWidth
3100
+ : 1 ;
3140
3101
} else {
3141
- // Matrix is non-invertible.
3142
- this . _cachedGetSinglePixelWidth = 1 ;
3102
+ // A pixel (base (x, y)) is transformed by M into a parallelogram:
3103
+ // - its area is |det(M)|;
3104
+ // - heightY (orthogonal to Mx) has a length: |det(M)| / norm(Mx);
3105
+ // - heightX (orthogonal to My) has a length: |det(M)| / norm(My).
3106
+ // heightX and heightY are the thicknesses of the transformed pixel
3107
+ // and they must be both greater or equal to 1.
3108
+ const absDet = Math . abs ( m [ 0 ] * m [ 3 ] - m [ 2 ] * m [ 1 ] ) ;
3109
+ const normX = Math . hypot ( m [ 0 ] , m [ 1 ] ) ;
3110
+ const normY = Math . hypot ( m [ 2 ] , m [ 3 ] ) ;
3111
+ const baseArea = lineWidth * absDet ;
3112
+ const scaledNormY = normY * this . outputScaleX ;
3113
+ const scaledNormX = normX * this . outputScaleY ;
3114
+ scaleX = scaledNormY > baseArea ? scaledNormY / baseArea : 1 ;
3115
+ scaleY = scaledNormX > baseArea ? scaledNormX / baseArea : 1 ;
3143
3116
}
3117
+ this . _cachedScaleForStroking = [ scaleX , scaleY ] ;
3118
+ }
3119
+
3120
+ return this . _cachedScaleForStroking ;
3121
+ }
3122
+
3123
+ // Rescale before stroking in order to have a final lineWidth
3124
+ // with both thicknesses greater or equal to 1.
3125
+ rescaleAndStroke ( ) {
3126
+ const { ctx } = this ;
3127
+ const { lineWidth } = this . current ;
3128
+ const [ scaleX , scaleY ] = this . getScaleForStroking ( ) ;
3129
+ if ( scaleX === 1 && scaleY === 1 ) {
3130
+ ctx . lineWidth = lineWidth ;
3131
+ ctx . stroke ( ) ;
3132
+ return ;
3144
3133
}
3145
3134
3146
- return this . _cachedGetSinglePixelWidth ;
3135
+ const savedMatrix = ctx . mozCurrentTransform . slice ( ) ;
3136
+
3137
+ if ( lineWidth === 0 ) {
3138
+ ctx . resetTransform ( ) ;
3139
+ ctx . lineWidth = 1 ;
3140
+ } else {
3141
+ ctx . scale ( scaleX , scaleY ) ;
3142
+ ctx . lineWidth = lineWidth ;
3143
+ }
3144
+ ctx . stroke ( ) ;
3145
+ ctx . setTransform ( ...savedMatrix ) ;
3146
+ }
3147
+
3148
+ // Same as above, but it's expected to be called between a ctx.save()
3149
+ // and a ctx.restore() (so no need save/restore the currentTransform).
3150
+ rescaleAndStrokeNoSave ( ) {
3151
+ const { ctx } = this ;
3152
+ let { lineWidth } = this . current ;
3153
+ if ( lineWidth === 0 ) {
3154
+ ctx . resetTransform ( ) ;
3155
+ lineWidth = 1 ;
3156
+ } else {
3157
+ const [ scaleX , scaleY ] = this . getScaleForStroking ( ) ;
3158
+ if ( scaleX !== 1 || scaleY !== 1 ) {
3159
+ ctx . scale ( scaleX , scaleY ) ;
3160
+ }
3161
+ }
3162
+
3163
+ ctx . lineWidth = lineWidth ;
3164
+ ctx . stroke ( ) ;
3165
+ }
3166
+
3167
+ rescaleAndStrokeText ( text , x , y ) {
3168
+ const { ctx } = this ;
3169
+ const { lineWidth } = this . current ;
3170
+ const savedMatrix = ctx . mozCurrentTransform . slice ( ) ;
3171
+
3172
+ if ( lineWidth === 0 ) {
3173
+ ctx . resetTransform ( ) ;
3174
+ ctx . lineWidth = 1 ;
3175
+ ctx . setTransform ( ...savedMatrix ) ;
3176
+ ctx . translate ( x , y ) ;
3177
+ } else {
3178
+ ctx . translate ( x , y ) ;
3179
+ const [ scaleX , scaleY ] = this . getScaleForStroking ( ) ;
3180
+ if ( scaleX !== 1 || scaleY !== 1 ) {
3181
+ ctx . scale ( scaleX , scaleY ) ;
3182
+ }
3183
+
3184
+ ctx . lineWidth = lineWidth ;
3185
+ }
3186
+ ctx . strokeText ( text , 0 , 0 ) ;
3187
+ ctx . setTransform ( ...savedMatrix ) ;
3147
3188
}
3148
3189
3149
3190
getCanvasPosition ( x , y ) {
0 commit comments