|
42 | 42 | DEBUG = False |
43 | 43 |
|
44 | 44 |
|
| 45 | +def _is_vector_renderer(renderer): |
| 46 | + # 1. Check the mode name |
| 47 | + # If it's MixedModeRenderer, get the actual renderer recursively |
| 48 | + while type(renderer).__name__ == 'MixedModeRenderer': |
| 49 | + eff = getattr(renderer, '_renderer', renderer) |
| 50 | + mod = getattr(eff.__class__, '__module__', '').lower() |
| 51 | + if any(x in mod for x in ('pdf', 'svg', 'ps')): |
| 52 | + return True |
| 53 | + # 2. The vectorized backends |
| 54 | + vector_bases = ( |
| 55 | + 'RendererPdf', 'RendererSVG', 'RendererPS' |
| 56 | + ) |
| 57 | + if any(base in type(renderer).__name__ for base in vector_bases): |
| 58 | + return True |
| 59 | + # 3. fallback: Agg/bitmap are not vector renderers |
| 60 | + if 'agg' in mod or 'bitmap' in mod: |
| 61 | + return False |
| 62 | + # 4. For unknown renderers, assume they are not vector |
| 63 | + return False |
| 64 | + |
| 65 | + |
45 | 66 | def _compat_get_offset(meth): |
46 | 67 | """ |
47 | 68 | Decorator for the get_offset method of OffsetBox and subclasses, that |
@@ -689,18 +710,33 @@ def draw(self, renderer): |
689 | 710 | self.dpi_transform.clear() |
690 | 711 | self.dpi_transform.scale(dpi_cor) |
691 | 712 |
|
692 | | - # At this point the DrawingArea has a transform |
693 | | - # to the display space so the path created is |
694 | | - # good for clipping children |
695 | 713 | tpath = mtransforms.TransformedPath( |
696 | 714 | mpath.Path([[0, 0], [0, self.height], |
697 | 715 | [self.width, self.height], |
698 | 716 | [self.width, 0]]), |
699 | 717 | self.get_transform()) |
700 | 718 | for c in self._children: |
701 | | - if self._clip_children and not (c.clipbox or c._clippath): |
702 | | - c.set_clip_path(tpath) |
703 | | - c.draw(renderer) |
| 719 | + is_rasterized = getattr(c, 'get_rasterized', lambda: False)() |
| 720 | + if is_rasterized and _is_vector_renderer(renderer): |
| 721 | + # PDF/SVG backend uses 72 dpi in display units, |
| 722 | + # so we need to scale the rasterized content accordingly |
| 723 | + target_dpi = getattr(renderer, 'dpi', 72) or 72 |
| 724 | + dpi_correction = target_dpi / 72 |
| 725 | + # New transform to apply the dpi correction |
| 726 | + corrected_transform = self.get_transform() \ |
| 727 | + + mtransforms.Affine2D().scale(dpi_correction) |
| 728 | + orig_transform = c.get_transform() \ |
| 729 | + if hasattr(c, 'get_transform') else None |
| 730 | + c.set_transform(corrected_transform) |
| 731 | + if self._clip_children and not (c.clipbox or c._clippath): |
| 732 | + c.set_clip_path(tpath) |
| 733 | + c.draw(renderer) |
| 734 | + if orig_transform is not None: |
| 735 | + c.set_transform(orig_transform) |
| 736 | + else: |
| 737 | + if self._clip_children and not (c.clipbox or c._clippath): |
| 738 | + c.set_clip_path(tpath) |
| 739 | + c.draw(renderer) |
704 | 740 |
|
705 | 741 | _bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) |
706 | 742 | self.stale = False |
|
0 commit comments