From d1bc8a40eee2ff7337d54d34467d6675cbcb28a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Holl=C3=A4nder?= Date: Tue, 20 Jul 2021 11:28:23 +0200 Subject: [PATCH] fix marker rendering (#184) - fix bounding box calculation - fix attribute inheritance fix #170 --- src/applyparseattributes.ts | 114 ++++++++++++++++++ src/markerlist.ts | 1 - src/nodes/marker.ts | 29 +++-- .../complete-social-network/reference.pdf | Bin 198633 -> 197558 bytes test/specs/markers/reference.pdf | Bin 7726 -> 8270 bytes test/specs/markers/spec.svg | 8 +- 6 files changed, 140 insertions(+), 12 deletions(-) diff --git a/src/applyparseattributes.ts b/src/applyparseattributes.ts index 24cc7e26..e85a8a03 100644 --- a/src/applyparseattributes.ts +++ b/src/applyparseattributes.ts @@ -308,3 +308,117 @@ export function applyAttributes( ) } } + +export function applyContext(context: Context): void { + const { attributeState, pdf } = context + + let fillOpacity = 1.0, + strokeOpacity = 1.0 + + fillOpacity *= attributeState.fillOpacity + fillOpacity *= attributeState.opacity + if ( + attributeState.fill instanceof ColorFill && + typeof attributeState.fill.color.a !== 'undefined' + ) { + fillOpacity *= attributeState.fill.color.a + } + + strokeOpacity *= attributeState.strokeOpacity + strokeOpacity *= attributeState.opacity + if ( + attributeState.stroke instanceof ColorFill && + typeof attributeState.stroke.color.a !== 'undefined' + ) { + strokeOpacity *= attributeState.stroke.color.a + } + + const gState: GState = {} + gState['opacity'] = fillOpacity + gState['stroke-opacity'] = strokeOpacity + pdf.setGState(new GState(gState)) + + if ( + attributeState.fill && + attributeState.fill instanceof ColorFill && + attributeState.fill.color.ok + ) { + // text fill color will be applied through setTextColor() + pdf.setFillColor( + attributeState.fill.color.r, + attributeState.fill.color.g, + attributeState.fill.color.b + ) + } else { + pdf.setFillColor(0, 0, 0) + } + + pdf.setLineWidth(attributeState.strokeWidth) + + if (attributeState.stroke instanceof ColorFill) { + pdf.setDrawColor( + attributeState.stroke.color.r, + attributeState.stroke.color.g, + attributeState.stroke.color.b + ) + } else { + pdf.setDrawColor(0, 0, 0) + } + + pdf.setLineCap(attributeState.strokeLinecap) + pdf.setLineJoin(attributeState.strokeLinejoin) + + if (attributeState.strokeDasharray) { + pdf.setLineDashPattern(attributeState.strokeDasharray, attributeState.strokeDashoffset) + } else { + pdf.setLineDashPattern([], 0) + } + + pdf.setLineMiterLimit(attributeState.strokeMiterlimit) + + let font: string | undefined + if (fontAliases.hasOwnProperty(attributeState.fontFamily)) { + font = fontAliases[attributeState.fontFamily] + } else { + font = attributeState.fontFamily + } + + if ( + attributeState.fill && + attributeState.fill instanceof ColorFill && + attributeState.fill.color.ok + ) { + const fillColor = attributeState.fill.color + pdf.setTextColor(fillColor.r, fillColor.g, fillColor.b) + } else { + pdf.setTextColor(0, 0, 0) + } + + let fontStyle: string | undefined = '' + if (attributeState.fontWeight === 'bold') { + fontStyle = 'bold' + } + if (attributeState.fontStyle === 'italic') { + fontStyle += 'italic' + } + + if (fontStyle === '') { + fontStyle = 'normal' + } + + if (font !== undefined || fontStyle !== undefined) { + if (font === undefined) { + if (fontAliases.hasOwnProperty(attributeState.fontFamily)) { + font = fontAliases[attributeState.fontFamily] + } else { + font = attributeState.fontFamily + } + } + pdf.setFont(font, fontStyle) + } else { + pdf.setFont('helvetica', fontStyle) + } + + // correct for a jsPDF-instance measurement unit that differs from `pt` + pdf.setFontSize(attributeState.fontSize * pdf.internal.scaleFactor) +} diff --git a/src/markerlist.ts b/src/markerlist.ts index 9959241d..f1c79813 100644 --- a/src/markerlist.ts +++ b/src/markerlist.ts @@ -44,7 +44,6 @@ export class MarkerList { // as the marker is already scaled by the current line width we must not apply the line width twice! context.pdf.saveGraphicsState() - context.pdf.setLineWidth(1.0) await context.refsHandler.getRendered(marker.id, null, node => (node as MarkerNode).apply(context) ) diff --git a/src/nodes/marker.ts b/src/nodes/marker.ts index f2c55f7f..0c894847 100644 --- a/src/nodes/marker.ts +++ b/src/nodes/marker.ts @@ -5,6 +5,7 @@ import { NonRenderedNode } from './nonrenderednode' import { svgNodeAndChildrenVisible } from '../utils/node' import { Rect } from '../utils/geometry' import { Matrix } from 'jspdf' +import { applyContext } from '../applyparseattributes' export class MarkerNode extends NonRenderedNode { async apply(parentContext: Context): Promise { @@ -13,15 +14,23 @@ export class MarkerNode extends NonRenderedNode { const bBox = this.getBoundingBox(parentContext) parentContext.pdf.beginFormObject(bBox[0], bBox[1], bBox[2], bBox[3], tfMatrix) + + const childContext = new Context(parentContext.pdf, { + refsHandler: parentContext.refsHandler, + styleSheets: parentContext.styleSheets, + viewport: parentContext.viewport, + svg2pdfParameters: parentContext.svg2pdfParameters + }) + + // "Properties do not inherit from the element referencing the 'marker' into the contents of the + // marker. However, by using the context-stroke value for the fill or stroke on elements in its + // definition, a single marker can be designed to match the style of the element referencing the + // marker." + // -> we need to reset all attributes + applyContext(childContext) + for (const child of this.children) { - await child.render( - new Context(parentContext.pdf, { - refsHandler: parentContext.refsHandler, - styleSheets: parentContext.styleSheets, - viewport: parentContext.viewport, - svg2pdfParameters: parentContext.svg2pdfParameters - }) - ) + await child.render(childContext) } parentContext.pdf.endFormObject(this.element.getAttribute('id')) } @@ -36,8 +45,8 @@ export class MarkerNode extends NonRenderedNode { return [ (vb && vb[0]) || 0, (vb && vb[1]) || 0, - (vb && vb[2]) || parseFloat(this.element.getAttribute('marker-width') || '0'), - (vb && vb[3]) || parseFloat(this.element.getAttribute('marker-height') || '0') + (vb && vb[2]) || parseFloat(this.element.getAttribute('markerWidth') || '3'), + (vb && vb[3]) || parseFloat(this.element.getAttribute('markerHeight') || '3') ] } diff --git a/test/specs/complete-social-network/reference.pdf b/test/specs/complete-social-network/reference.pdf index e7222aaf1a4d4039dff7e49008b976c8d9006132..f27feadcd4bc8f0b982b8b1fc5ad4eb36e0b3e71 100644 GIT binary patch delta 5617 zcmZuz30zgh8vo4!ByXhHJu2>yp(F}tU$iVT(QGlZGA%O=GdJ8ybID7kuf=DelboX7 zOTA^QsGankHw1m%I2t znrk1rfv?scOLiee#Y+C1DJ9qEM^ug*Ixy23VQ-|>(yX*8pXsihR~$`$<_sR+ zGNaD!KNE5*m_>6dna$(Tv+M0)bD%$WJPKC#Ja*O|HWvbomVsikEbq<;Nn0tE-txigaf6aU`ZM6M%4;Gu2^v@_&khvDGuWm zFE-;9tEk2PazB2j{FGf`0j@PKfvY!PcJaFv5xepgnEI`+M(iE`i1rk{1}*<*y}kQ2 z7>)g_5k9mgVpp$)$v+mPVY1$Usek=&@VhsV{lhx&ck)f}SN~QRpLyH08{Pr4Ki)++ z{9a^jB{eA6;K!XeoU(lzK`(uiYxD1eRTmqoll1{mIh$d$g)Lz$Ibp19Z3;@;kgabA z$-o_8anpwpJY;7G5B(@&-|{gqMtnkZT2bZ4Q>tq5y`P5hn9p4Mfq#Ll=ySLsyPELB zUxe`^Uqh0Wr15^4Bcs343@PI?j_JIEa=f?j*mP4zNecN{+8hO~YM|}_M2adop@hHsH z^dF#Lj~+w8`NxsH_(v3N&`;2}pPoPx-c;+yxhEDP$r3fh;~j*=s1c^^=LIow_0Nl9 z)Z~+Ks)JB>tGQM1JsWG?X9DJx4GSN$Po`NfInSAIIIR z0jMb2Ee&z;;9ujFIP@z~9Qhjwh8o-B3%?N@U<6)*X5qv~`u_+$#xr>BKX(skrAwOQ%OmwnBVv8dzMNpBjt)}Nx-%`VNifa4f}Y&yMux~*+lH7^G-6w7 zO@LKVl8%{mXL?J3<57BSJL-qVblXw!!Yf-?-TagV(RgdUpQ6vDCrXM!Q?sok9VZq$_YCtfC&2Lwo=0CG=C72^{HJBp`&~t<_lxZr)WC z21WN#l%`H@@0n2fzGuwPZ%w^A)&SlLU7g@A%ch7g>qX&^?!42BdhOLa!TGXx!rwhP zvA)Q6UYsCCax#9E&lL*Tcr>q{)6Be170o!r8H)F$%+Z`Jfe zkAoB@U>R(u^J4YotLWo>nLe zlzFR{N*%9JQ7O2AD#GT#9X~c1iPqZHs3#JfU8~V>WQwBf8#vH6=h4{{$4~d=4;d+D zPMYHr@-ip+#JtRTgc{-#GKazc6yJSgGvQq1Q}QxzbF$W=e1t@2L=YLt;C93bG}NlR zm3!DR-a>DrfaK_U^d$$9@k_Z}r}}Pg408Iuk9Ngac~15iE`+RZ4%!x1N*&8pB4^_^ z)Bv6>@I2V5*?~?bMco%#r>l_P$@v6TgL`Y#joct-@Gdl$9LlOuALrz5bdKo4A=m1$ z8QuWlTa@B-{ua$9L8*>&0KrE%b|^oHn!vyT$uq=D#t-NMY!LWnXY?_2fyf}Q39VrN zpbpJTg}rYBGKelHY1ZCkF2k`eAOsa~HaDYWvTt|M_sPkmkd~On@lItjXOap*9sE{) z8rR3kZp}5u%HVf^qx2;XPaw^yFu2D^l=K_8LeOv^$^EftuR5O(b~3U0TnF9ICEY9?iT{LA~(5zp=oDq<9Rq;p#+0acdPZ0!`Cn^%7C;f%3 zBCvX@EP`#|QdokbX)@zbH`&0Zk>!wW_i1l?O1T~GvXb}(b${m;qif=T-xPk7a^B%!^xzq$l1Syh_UKcF+Q zWl`{A)MZ&>;>eonEvzmZ3WHk_bq2R0n^D_9OL25Xml!aL$y^Ful@tbys;J%(fd&}f zuIQ?%|49$JBkGzgFp8SWg3%17dpfj*vDIbWJL@_`W|GrQDZrH45SfdjLsgj$8k+W} zOh_Vz$vi~5DVmI)sm5=2ATgV|$zA~CFXqq?B+Z+#A*iM|V?!{QO9r`lE?Gbj4N;c8 z2n|tVgNTOet%4zmrgs($7!Ssu1U>Ow6hks)HQ;5+5>r+~Ry6OV8!|MHu{GH$D6+(W zR$AV@K>UMYC#p)sEXcr#VTAxt_m3#M$adhq(iK?h>r zYq&YgqhbmY^E#S>5_`E?K%0Ugd9Sevk7y!D{5OFpYu=d&n4+qAOGpy&_S2LE!`pXL zQZD_{lJ9_MN*Z&UOvz-*4<89~o2;F!lCH{7*sE_UhAdb^Qj=ctDR57G+1Z1K7yciI CGp%O; delta 6745 zcmZuzd3+Q_7XDRdlE@@L%ydWsL}NIF1W0#JpQtQ&;KdbP!CSKmDgp}1q2LYyF$m;< zHfa(f0Y?QMHDo|V^tR9P;j|TSU@1aR#jK^3|;a^>h-JlzW2SaUe&O< zIQd9fa(P*ttjfz50iW0}dun(gaEwSf7f-CW;s8iQoBoM@v)LCg>wG70aHKLmfut=V zMx;8C0EDZrA^BD9IL9)p{g<37l}RM8BAEoe)jp0`l^qCzS=sSQv~CKln{ouFn~Sb> zaLm%w%MRfD04Xt#zv6i2rV&qZr#2U*F`YN7(h2)H89q{SZYHH)(wT6Lly;$3Syx}Q z|6N(Uxhm@cHmQ)_WHwW@BQEiK8RzM5{dW^H$Bz0-qO;T1g{Lg9CBB;>=r zoCYJydQ(-FD8+iG(^C|Ex#SUw3Y6P)BoiAWE+M54$gL9#ht zrhFGDG;D!N`Ic$aSEkdra)bKH`;)HCMK=++@|)vg1iS?bc*|Z#z&Qh`M8$ui<2HXF zvBG78h#aN2l7lL_je?imPC8m}2a#jmV8;m0y_2LB-$hh>U)m4I&Hl- zU{>FI)ES!DL!v|*hr(GIN;YNv>P>=mhq3FW!gVOt(0f!?MQo$i|SJ_HemJ{`>WGL>hpyA zwc{vgV0_H8Q80lwb0;|T%(NGx$1&nX-t7J2arZb1CI-yhiKLITNmT4kFa74U7XLEE zPn=9PBiBr!R=cTmH)$I6CBH&^;SR473EEF@#c1Z`>Exi=X0$n;bFW==jF!KWOLZdT zycgjgXFBQOABqUh_p_YC41YVDc)od^05;DdM$;RP5&p8+iHYo-OQ?mve3PW@oaYdV zY?)7v-rTalsUN8?q0>-b>RM$qtiGJg)8>Uv2jLCmDUYl-Y502hgu4p=R>7N}R~*A# zh9i}P!A8rW8Ck!WoVNKvRdl>ss(JH^YVz#aOQ_2AHB{w>w<)8Br3C52S{l}{%-vkx zf(u9L-l62`!Zd8ny9CW#yMhQ?_Z|^#)%&zk-AW2pw~AC+Sx3QEu6C_8ROMA`3C7BG zv{K#QDB9}v)VFp6y;tiu5>3}eqJw&1Q^0K8^ozqj{6WlYAK1*B4V%fxuCQqj?|ta7 zGvBQzleDaX@Ll>5F9K<69CH$^4FGCbTWUS30;1qR@QF% zBxV1H`f~PAket1)^;66PoBt|j~zZ%c6d6J(nGk5G_3M-cRbM-eotf8i+FpM1~O&9r_)JU}jNu15S|k2D>@=-> z>NJe7PH-UIob|scv`q3D#1FBkK0^!kK8vgsXHn+Xa{+VkITW+?9Qq#qJMFaPcZ64n zL?}8>K{{Tbt<+w?+epIvi*$R-MOtG>8`{Q)x~C0pJLdD3Q2*H5cF9TN&H0yUdgc|B zUU3CdsOSzLor;aretf%^{%4iO95C6KNz$NNI<^YMliQk9Dgbr6YN*WS}#u2Sa(E>%u*uIpEad6Gr_Sn^>L)5jU19rh`yfw5VX1u*4-X4{Lm&497`C9yi zJ4;W+af?!M8v?$X{X-Ht){=7~&Iw2ibs7S|$fI3q% zqVb<)pdbu0G84z6bF~gtigL=3p2P19EaXrs=bMel0!y+Ks zuXlI7sXbhCSr44uwI|jD#eLkCnrARhq9 z5@k-ZN7RA@pYx&GC1H@203>_$DzKe{&gL0hUwdT%_aLy(uLawPE6+al0CxpgM>m3c zCn(QuihyNt1k-|;0c+q#fVV%l!PmsCS`a-lT*aOS(_Q4%sDk_XUunceQ+y#->d<7P`Zk#%{8MM+=yZAfs7(y;O3^J@s zyTLwaqka*$bi3ysa4wcss&)54kYZ2S5B#Wom^3vJuIEnx9f8n9Uqv;SVb44bfB)d_ ztaHGJ*5Zp`4^r^O_FVc0(>Vj#P`H5AmdNGWtv>E7(Jf#@kK6~QK)}_gz}}mSr2o5blwx+Z;xbp#}%(H)bl6xA#RF`vt{nBVI4dv`v$cc8t7%m%q)QshN*>TTuiy?pw zKlY;a)$!aglm=JHzBrK^jugCi8aD?zZK%VeF13e>IQV}>EKZ&+zRqnRJo2qGbGh!e zTFmuFfRI@kS|O>$2m|sN1gqPS%57+cgK!XK!ZpwCSIJFyDnr&qEn7hUvL|Hw(JC6U zri<35^PC=%bxro{3bJ7sUY{aLtWQ!UCNB5~xr)Tbsfq}5L$S03Mbm=}pdm7aRYCT) zB&Z_Wh#C|aJXH>=(Hf|}APTAy)T2(~Q-h2$s;UW0oF>Eol3XpQuyHEu3+V!q0seCj zM^N=3BbTbHOeY$9Bw{eAc#BAq%-~77s^A*kV+zYgQ1Ysv7>1%*kG63)rFn5RNf1

-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 diff --git a/test/specs/markers/reference.pdf b/test/specs/markers/reference.pdf index 0e2aa0c02bb290a912d8c065018f24661c26c075..770c790f5a8f2e8243133ed4ea91d520b3e9da24 100644 GIT binary patch delta 1377 zcmbVL%}*0S6wd-J>;U!vf!5M6Bv$mmc6VoIw?hbuf&?NGAVf?hrqa@tz&3?VmhvUY~&Ag!?6rFvB7GEFooVHszN7;rcgZb3jjy7 zvOzTO^tTP7|HkWNfmCpzci@BISc`6%r81-AWiscldYYW6P^K(M1(Jqg;99q>K=RA?GIT^M+lxFzZEAf1kr${}6hsJ>owO0G>cLpvQK4hbWduts)5x)}58)peW%*KiX8Uc?8wu^Gs|PH@8v z5ZL4jyiI)Vp4fIL7u>vCq$49i>oftivk664sY>U)k?T~)NC9Tl3ym22jbG6zl<%$) zoq?QOlnl6&bR-V7WU9x?Qq>dcP~rpC(liyMK2g;eZ`9!gk?y3u!9@6t>nvnv{=wJE zhV6ugsjF(WJ}IsmMq@y(WkbZAsiA_n5iW#tGaSzpRvicOqi{=zy2s!xD?**&f?@ML zIqsy%^^l#JWoDzDWrVwB@g`NkZmMs9lgxa%>{1;Q_J%v518EKLHFqKP3PF delta 982 zcmZ`$&ubGw7-jRbJJ}?pHpWzgBc!nwHM=uA`vXJuphA(-5R|sGipHke7L#s`^w#J} z5kc+eKM?4}ix`T3gEtSN;LU@0t|!5pyP4f>o7G_s?|bikkNLj&bm?8?)r+v9lGiW4 zM3nP*6Y4^VgQvm@2YbS`xFaD8!F%B}Y=$y$J2V2%LT|XCo2bHCILC`8;RXjk!*0LN z^djS2`W89EK`&B*QgneAkE0VDyp6h@nb$9Yln1%Y!x1=1b z4_)^RM`1lRvMpxdmpB0z;v)QzreRAo{-K}sxc`OL?0_t+C3N37R=h%RqRGAl_Q{%f|(--6LJYR&x+-#Z}1Wit&hwY8Vod?L;PG_N- zkL`-b9tnaBo&1iZnfnG<9G!=c$G?-gnfhwuZW~uBs65wFuwr5v*BneRk^9(AwQr-j z)T*x{qJU7i0Ut((;eO$!N|EdztT9FzUdAq`%8H8nDsFn5rl~soDVQBuR|YjzF|`0k zNsv>S{YdBR=h{@zr#r(8_zcx>xXz(w)37H!Y-EZO2xLSJ48&+~5XS8E!90!91KfOH zX1cwvL8Czfo6)Ed + + + @@ -31,5 +34,8 @@ - + + + +