@@ -137,3 +137,138 @@ def subpath(self, box) -> str:
137137 # x,y is the point the arc is drawn to
138138
139139 return f"M{ x0 } ,{ yh } A{ h } ,{ h } 0 0 0 { x1 } ,{ yh } A{ h } ,{ h } 0 0 0 { x0 } ,{ yh } z"
140+
141+
142+ class SvgRoundedModuleDrawer (SvgPathQRModuleDrawer ):
143+ """
144+ Draws the modules with all 90 degree corners replaced with rounded edges.
145+
146+ radius_ratio determines the radius of the rounded edges - a value of 1
147+ means that an isolated module will be drawn as a circle, while a value of 0
148+ means that the radius of the rounded edge will be 0 (and thus back to 90
149+ degrees again).
150+ """
151+ needs_neighbors = True
152+
153+ def __init__ (self , radius_ratio : Decimal = Decimal (1 ), ** kwargs ):
154+ super ().__init__ (** kwargs )
155+ self .radius_ratio = radius_ratio
156+
157+ def initialize (self , * args , ** kwargs ) -> None :
158+ super ().initialize (* args , ** kwargs )
159+ self .corner_radius = self .box_half * self .radius_ratio
160+
161+ def drawrect (self , box , is_active ):
162+ if not is_active :
163+ return
164+
165+ # Check if is_active has neighbor information (ActiveWithNeighbors object)
166+ if hasattr (is_active , 'N' ):
167+ # Neighbor information is available
168+ self .img ._subpaths .append (self .subpath (box , is_active ))
169+ else :
170+ # No neighbor information available, draw a square with all corners rounded
171+ self .img ._subpaths .append (self .subpath_all_rounded (box ))
172+
173+ def subpath_all_rounded (self , box ) -> str :
174+ """Draw a module with all corners rounded."""
175+ coords = self .coords (box )
176+ x0 = self .img .units (coords .x0 , text = False )
177+ y0 = self .img .units (coords .y0 , text = False )
178+ x1 = self .img .units (coords .x1 , text = False )
179+ y1 = self .img .units (coords .y1 , text = False )
180+ r = self .img .units (self .corner_radius , text = False )
181+
182+ # Build the path with all corners rounded
183+ path = []
184+
185+ # Start at top-left after the rounded part
186+ path .append (f"M{ x0 + r } ,{ y0 } " )
187+
188+ # Top edge to top-right corner
189+ path .append (f"H{ x1 - r } " )
190+ # Top-right rounded corner
191+ path .append (f"A{ r } ,{ r } 0 0 1 { x1 } ,{ y0 + r } " )
192+
193+ # Right edge to bottom-right corner
194+ path .append (f"V{ y1 - r } " )
195+ # Bottom-right rounded corner
196+ path .append (f"A{ r } ,{ r } 0 0 1 { x1 - r } ,{ y1 } " )
197+
198+ # Bottom edge to bottom-left corner
199+ path .append (f"H{ x0 + r } " )
200+ # Bottom-left rounded corner
201+ path .append (f"A{ r } ,{ r } 0 0 1 { x0 } ,{ y1 - r } " )
202+
203+ # Left edge to top-left corner
204+ path .append (f"V{ y0 + r } " )
205+ # Top-left rounded corner
206+ path .append (f"A{ r } ,{ r } 0 0 1 { x0 + r } ,{ y0 } " )
207+
208+ # Close the path
209+ path .append ("z" )
210+
211+ return "" .join (path )
212+
213+ def subpath (self , box , is_active ) -> str :
214+ """Draw a module with corners rounded based on neighbor information."""
215+ # Determine which corners should be rounded
216+ nw_rounded = not is_active .W and not is_active .N
217+ ne_rounded = not is_active .N and not is_active .E
218+ se_rounded = not is_active .E and not is_active .S
219+ sw_rounded = not is_active .S and not is_active .W
220+
221+ coords = self .coords (box )
222+ x0 = self .img .units (coords .x0 , text = False )
223+ y0 = self .img .units (coords .y0 , text = False )
224+ x1 = self .img .units (coords .x1 , text = False )
225+ y1 = self .img .units (coords .y1 , text = False )
226+ r = self .img .units (self .corner_radius , text = False )
227+
228+ # Build the path
229+ path = []
230+
231+ # Start at top-left and move clockwise
232+ if nw_rounded :
233+ # Start at top-left corner, after the rounded part
234+ path .append (f"M{ x0 + r } ,{ y0 } " )
235+ else :
236+ # Start at the top-left corner
237+ path .append (f"M{ x0 } ,{ y0 } " )
238+
239+ # Top edge to top-right corner
240+ if ne_rounded :
241+ path .append (f"H{ x1 - r } " )
242+ # Top-right rounded corner
243+ path .append (f"A{ r } ,{ r } 0 0 1 { x1 } ,{ y0 + r } " )
244+ else :
245+ path .append (f"H{ x1 } " )
246+
247+ # Right edge to bottom-right corner
248+ if se_rounded :
249+ path .append (f"V{ y1 - r } " )
250+ # Bottom-right rounded corner
251+ path .append (f"A{ r } ,{ r } 0 0 1 { x1 - r } ,{ y1 } " )
252+ else :
253+ path .append (f"V{ y1 } " )
254+
255+ # Bottom edge to bottom-left corner
256+ if sw_rounded :
257+ path .append (f"H{ x0 + r } " )
258+ # Bottom-left rounded corner
259+ path .append (f"A{ r } ,{ r } 0 0 1 { x0 } ,{ y1 - r } " )
260+ else :
261+ path .append (f"H{ x0 } " )
262+
263+ # Left edge back to start
264+ if nw_rounded :
265+ path .append (f"V{ y0 + r } " )
266+ # Top-left rounded corner
267+ path .append (f"A{ r } ,{ r } 0 0 1 { x0 + r } ,{ y0 } " )
268+ else :
269+ path .append (f"V{ y0 } " )
270+
271+ # Close the path
272+ path .append ("z" )
273+
274+ return "" .join (path )
0 commit comments