@@ -1116,6 +1116,7 @@ class CanvasGraphics {
11161116 // the transformation must already be set in canvasCtx._transformMatrix.
11171117 addContextCurrentTransform ( canvasCtx ) ;
11181118 }
1119+ this . _cachedScaleForStroking = null ;
11191120 this . _cachedGetSinglePixelWidth = null ;
11201121 }
11211122
@@ -1165,10 +1166,6 @@ class CanvasGraphics {
11651166 this . viewportScale = viewport . scale ;
11661167
11671168 this . baseTransform = this . ctx . mozCurrentTransform . slice ( ) ;
1168- this . _combinedScaleFactor = Math . hypot (
1169- this . baseTransform [ 0 ] ,
1170- this . baseTransform [ 2 ]
1171- ) ;
11721169
11731170 if ( this . imageLayer ) {
11741171 this . imageLayer . beginLayout ( ) ;
@@ -1425,6 +1422,9 @@ class CanvasGraphics {
14251422
14261423 // Graphics state
14271424 setLineWidth ( width ) {
1425+ if ( width !== this . current . lineWidth ) {
1426+ this . _cachedScaleForStroking = null ;
1427+ }
14281428 this . current . lineWidth = width ;
14291429 this . ctx . lineWidth = width ;
14301430 }
@@ -1633,13 +1633,15 @@ class CanvasGraphics {
16331633 // Ensure that the clipping path is reset (fixes issue6413.pdf).
16341634 this . pendingClip = null ;
16351635
1636+ this . _cachedScaleForStroking = null ;
16361637 this . _cachedGetSinglePixelWidth = null ;
16371638 }
16381639 }
16391640
16401641 transform ( a , b , c , d , e , f ) {
16411642 this . ctx . transform ( a , b , c , d , e , f ) ;
16421643
1644+ this . _cachedScaleForStroking = null ;
16431645 this . _cachedGetSinglePixelWidth = null ;
16441646 }
16451647
@@ -1776,33 +1778,17 @@ class CanvasGraphics {
17761778 ctx . globalAlpha = this . current . strokeAlpha ;
17771779 if ( this . contentVisible ) {
17781780 if ( typeof strokeColor === "object" && strokeColor ?. getPattern ) {
1779- const lineWidth = this . getSinglePixelWidth ( ) ;
17801781 ctx . save ( ) ;
17811782 ctx . strokeStyle = strokeColor . getPattern (
17821783 ctx ,
17831784 this ,
17841785 ctx . mozCurrentTransformInverse ,
17851786 PathType . STROKE
17861787 ) ;
1787- // Prevent drawing too thin lines by enforcing a minimum line width.
1788- ctx . lineWidth = Math . max ( lineWidth , this . current . lineWidth ) ;
1789- ctx . stroke ( ) ;
1788+ this . rescaleAndStroke ( /* saveRestore */ false ) ;
17901789 ctx . restore ( ) ;
17911790 } else {
1792- const lineWidth = this . getSinglePixelWidth ( ) ;
1793- if ( lineWidth < 0 && - lineWidth >= this . current . lineWidth ) {
1794- // The current transform will transform a square pixel into a
1795- // parallelogram where both heights are lower than 1 and not equal.
1796- ctx . save ( ) ;
1797- ctx . resetTransform ( ) ;
1798- ctx . lineWidth = Math . floor ( this . _combinedScaleFactor ) ;
1799- ctx . stroke ( ) ;
1800- ctx . restore ( ) ;
1801- } else {
1802- // Prevent drawing too thin lines by enforcing a minimum line width.
1803- ctx . lineWidth = Math . max ( lineWidth , this . current . lineWidth ) ;
1804- ctx . stroke ( ) ;
1805- }
1791+ this . rescaleAndStroke ( /* saveRestore */ true ) ;
18061792 }
18071793 }
18081794 if ( consumePath ) {
@@ -2027,7 +2013,7 @@ class CanvasGraphics {
20272013 this . moveText ( 0 , this . current . leading ) ;
20282014 }
20292015
2030- paintChar ( character , x , y , patternTransform , resetLineWidthToOne ) {
2016+ paintChar ( character , x , y , patternTransform ) {
20312017 const ctx = this . ctx ;
20322018 const current = this . current ;
20332019 const font = current . font ;
@@ -2063,10 +2049,6 @@ class CanvasGraphics {
20632049 fillStrokeMode === TextRenderingMode . STROKE ||
20642050 fillStrokeMode === TextRenderingMode . FILL_STROKE
20652051 ) {
2066- if ( resetLineWidthToOne ) {
2067- ctx . resetTransform ( ) ;
2068- ctx . lineWidth = Math . floor ( this . _combinedScaleFactor ) ;
2069- }
20702052 ctx . stroke ( ) ;
20712053 }
20722054 ctx . restore ( ) ;
@@ -2081,16 +2063,7 @@ class CanvasGraphics {
20812063 fillStrokeMode === TextRenderingMode . STROKE ||
20822064 fillStrokeMode === TextRenderingMode . FILL_STROKE
20832065 ) {
2084- if ( resetLineWidthToOne ) {
2085- ctx . save ( ) ;
2086- ctx . moveTo ( x , y ) ;
2087- ctx . resetTransform ( ) ;
2088- ctx . lineWidth = Math . floor ( this . _combinedScaleFactor ) ;
2089- ctx . strokeText ( character , 0 , 0 ) ;
2090- ctx . restore ( ) ;
2091- } else {
2092- ctx . strokeText ( character , x , y ) ;
2093- }
2066+ ctx . strokeText ( character , x , y ) ;
20942067 }
20952068 }
20962069
@@ -2181,7 +2154,6 @@ class CanvasGraphics {
21812154 }
21822155
21832156 let lineWidth = current . lineWidth ;
2184- let resetLineWidthToOne = false ;
21852157 const scale = current . textMatrixScale ;
21862158 if ( scale === 0 || lineWidth === 0 ) {
21872159 const fillStrokeMode =
@@ -2190,9 +2162,7 @@ class CanvasGraphics {
21902162 fillStrokeMode === TextRenderingMode . STROKE ||
21912163 fillStrokeMode === TextRenderingMode . FILL_STROKE
21922164 ) {
2193- this . _cachedGetSinglePixelWidth = null ;
21942165 lineWidth = this . getSinglePixelWidth ( ) ;
2195- resetLineWidthToOne = lineWidth < 0 ;
21962166 }
21972167 } else {
21982168 lineWidth /= scale ;
@@ -2260,13 +2230,7 @@ class CanvasGraphics {
22602230 // common case
22612231 ctx . fillText ( character , scaledX , scaledY ) ;
22622232 } else {
2263- this . paintChar (
2264- character ,
2265- scaledX ,
2266- scaledY ,
2267- patternTransform ,
2268- resetLineWidthToOne
2269- ) ;
2233+ this . paintChar ( character , scaledX , scaledY , patternTransform ) ;
22702234 if ( accent ) {
22712235 const scaledAccentX =
22722236 scaledX + ( fontSize * accent . offset . x ) / fontSizeScale ;
@@ -2276,8 +2240,7 @@ class CanvasGraphics {
22762240 accent . fontChar ,
22772241 scaledAccentX ,
22782242 scaledAccentY ,
2279- patternTransform ,
2280- resetLineWidthToOne
2243+ patternTransform
22812244 ) ;
22822245 }
22832246 }
@@ -2302,6 +2265,7 @@ class CanvasGraphics {
23022265 }
23032266 ctx . restore ( ) ;
23042267 this . compose ( ) ;
2268+
23052269 return undefined ;
23062270 }
23072271
@@ -2325,6 +2289,7 @@ class CanvasGraphics {
23252289 if ( isTextInvisible || fontSize === 0 ) {
23262290 return ;
23272291 }
2292+ this . _cachedScaleForStroking = null ;
23282293 this . _cachedGetSinglePixelWidth = null ;
23292294
23302295 ctx . save ( ) ;
@@ -3121,46 +3086,112 @@ class CanvasGraphics {
31213086 }
31223087
31233088 getSinglePixelWidth ( ) {
3124- if ( this . _cachedGetSinglePixelWidth === null ) {
3125- // If transform is [a b] then a pixel (square) is transformed
3126- // [c d]
3127- // into a parallelogram: its area is the abs value of the determinant.
3128- // This parallelogram has 2 heights:
3129- // - Area / |col_1|;
3130- // - Area / |col_2|.
3131- // so in order to get a height of at least 1, pixel height
3132- // must be computed as followed:
3133- // h = max(sqrt(a² + c²) / |det(M)|, sqrt(b² + d²) / |det(M)|).
3134- // This is equivalent to:
3135- // h = max(|line_1_inv(M)|, |line_2_inv(M)|)
3089+ if ( ! this . _cachedGetSinglePixelWidth ) {
31363090 const m = this . ctx . mozCurrentTransform ;
3091+ if ( m [ 1 ] === 0 && m [ 2 ] === 0 ) {
3092+ // Fast path
3093+ this . _cachedGetSinglePixelWidth =
3094+ 1 / Math . min ( Math . abs ( m [ 0 ] ) , Math . abs ( m [ 3 ] ) ) ;
3095+ } else {
3096+ const absDet = Math . abs ( m [ 0 ] * m [ 3 ] - m [ 2 ] * m [ 1 ] ) ;
3097+ const normX = Math . hypot ( m [ 0 ] , m [ 2 ] ) ;
3098+ const normY = Math . hypot ( m [ 1 ] , m [ 3 ] ) ;
3099+ this . _cachedGetSinglePixelWidth = Math . max ( normX , normY ) / absDet ;
3100+ }
3101+ }
3102+ return this . _cachedGetSinglePixelWidth ;
3103+ }
31373104
3138- const absDet = Math . abs ( m [ 0 ] * m [ 3 ] - m [ 2 ] * m [ 1 ] ) ;
3139- const sqNorm1 = m [ 0 ] ** 2 + m [ 2 ] ** 2 ;
3140- const sqNorm2 = m [ 1 ] ** 2 + m [ 3 ] ** 2 ;
3141- const pixelHeight = Math . sqrt ( Math . max ( sqNorm1 , sqNorm2 ) ) / absDet ;
3142- if ( sqNorm1 !== sqNorm2 && this . _combinedScaleFactor * pixelHeight > 1 ) {
3143- // The parallelogram isn't a square and at least one height
3144- // is lower than 1 so the resulting line width must be 1
3145- // but it cannot be achieved with one scale: when scaling a pixel
3146- // we'll get a rectangle (see issue #12295).
3147- // For example with matrix [0.001 0, 0, 100], a pixel is transformed
3148- // in a rectangle 0.001x100. If we just scale by 1000 (to have a 1)
3149- // then we'll get a rectangle 1x1e5 which is wrong.
3150- // In this case, we must reset the transform, set linewidth to 1
3151- // and then stroke.
3152- this . _cachedGetSinglePixelWidth = - (
3153- this . _combinedScaleFactor * pixelHeight
3154- ) ;
3155- } else if ( absDet > Number . EPSILON ) {
3156- this . _cachedGetSinglePixelWidth = pixelHeight ;
3105+ getScaleForStroking ( ) {
3106+ // A pixel has thicknessX = thicknessY = 1;
3107+ // A transformed pixel is a parallelogram and the thicknesses
3108+ // corresponds to the heights.
3109+ // The goal of this function is to rescale before setting the
3110+ // lineWidth in order to have both thicknesses greater or equal
3111+ // to 1 after transform.
3112+ if ( ! this . _cachedScaleForStroking ) {
3113+ const { lineWidth } = this . current ;
3114+ const m = this . ctx . mozCurrentTransform ;
3115+ let scaleX , scaleY ;
3116+
3117+ if ( m [ 1 ] === 0 && m [ 2 ] === 0 ) {
3118+ // Fast path
3119+ const normX = Math . abs ( m [ 0 ] ) ;
3120+ const normY = Math . abs ( m [ 3 ] ) ;
3121+ if ( lineWidth === 0 ) {
3122+ scaleX = 1 / normX ;
3123+ scaleY = 1 / normY ;
3124+ } else {
3125+ const scaledXLineWidth = normX * lineWidth ;
3126+ const scaledYLineWidth = normY * lineWidth ;
3127+ scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1 ;
3128+ scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1 ;
3129+ }
31573130 } else {
3158- // Matrix is non-invertible.
3159- this . _cachedGetSinglePixelWidth = 1 ;
3131+ // A pixel (base (x, y)) is transformed by M into a parallelogram:
3132+ // - its area is |det(M)|;
3133+ // - heightY (orthogonal to Mx) has a length: |det(M)| / norm(Mx);
3134+ // - heightX (orthogonal to My) has a length: |det(M)| / norm(My).
3135+ // heightX and heightY are the thicknesses of the transformed pixel
3136+ // and they must be both greater or equal to 1.
3137+ const absDet = Math . abs ( m [ 0 ] * m [ 3 ] - m [ 2 ] * m [ 1 ] ) ;
3138+ const normX = Math . hypot ( m [ 0 ] , m [ 1 ] ) ;
3139+ const normY = Math . hypot ( m [ 2 ] , m [ 3 ] ) ;
3140+ if ( lineWidth === 0 ) {
3141+ scaleX = normY / absDet ;
3142+ scaleY = normX / absDet ;
3143+ } else {
3144+ const baseArea = lineWidth * absDet ;
3145+ scaleX = normY > baseArea ? normY / baseArea : 1 ;
3146+ scaleY = normX > baseArea ? normX / baseArea : 1 ;
3147+ }
31603148 }
3149+ this . _cachedScaleForStroking = [ scaleX , scaleY ] ;
31613150 }
3151+ return this . _cachedScaleForStroking ;
3152+ }
31623153
3163- return this . _cachedGetSinglePixelWidth ;
3154+ // Rescale before stroking in order to have a final lineWidth
3155+ // with both thicknesses greater or equal to 1.
3156+ rescaleAndStroke ( saveRestore ) {
3157+ const { ctx } = this ;
3158+ const { lineWidth } = this . current ;
3159+ const [ scaleX , scaleY ] = this . getScaleForStroking ( ) ;
3160+
3161+ ctx . lineWidth = lineWidth || 1 ;
3162+
3163+ if ( scaleX === 1 && scaleY === 1 ) {
3164+ ctx . stroke ( ) ;
3165+ return ;
3166+ }
3167+
3168+ let savedMatrix , savedDashes , savedDashOffset ;
3169+ if ( saveRestore ) {
3170+ savedMatrix = ctx . mozCurrentTransform . slice ( ) ;
3171+ savedDashes = ctx . getLineDash ( ) . slice ( ) ;
3172+ savedDashOffset = ctx . lineDashOffset ;
3173+ }
3174+
3175+ ctx . scale ( scaleX , scaleY ) ;
3176+
3177+ // How the dashed line is rendered depends on the current transform...
3178+ // so we added a rescale to handle too thin lines and consequently
3179+ // the way the line is dashed will be modified.
3180+ // If scaleX === scaleY, the dashed lines will be rendered correctly
3181+ // else we'll have some bugs (but only with too thin lines).
3182+ // Here we take the max... why not taking the min... or something else.
3183+ // Anyway, as said it's buggy when scaleX !== scaleY.
3184+ const scale = Math . max ( scaleX , scaleY ) ;
3185+ ctx . setLineDash ( ctx . getLineDash ( ) . map ( x => x / scale ) ) ;
3186+ ctx . lineDashOffset /= scale ;
3187+
3188+ ctx . stroke ( ) ;
3189+
3190+ if ( saveRestore ) {
3191+ ctx . setTransform ( ...savedMatrix ) ;
3192+ ctx . setLineDash ( savedDashes ) ;
3193+ ctx . lineDashOffset = savedDashOffset ;
3194+ }
31643195 }
31653196
31663197 getCanvasPosition ( x , y ) {
0 commit comments