@@ -464,6 +464,15 @@ def __init__(self, styles, ftype, uistate=None):
464464 self .first_page = 1
465465 self .stylelist_notes = [] # styles to create for styled notes.
466466 self .stylelist_photos = [] # styles to create for clipped images.
467+ # bug #5733: init() writes the named "F<name>" draw-text styles up
468+ # front from the (then unscaled) style sheet. A graphical report
469+ # ("scale tree to fit") scales the style sheet's fonts AFTER init(),
470+ # so a drawn text span must be able to override to the current
471+ # (scaled) size at draw time — see _scaled_text_style().
472+ self ._draw_text_props = {} # para style name -> init "F<name>" props
473+ self ._draw_text_style_names = set () # every "F<name>" init() wrote
474+ self ._scaled_text_styles = {} # props -> registered override name
475+ self ._scaled_text_defs = [] # (name, props) overrides to serialize
467476
468477 def open (self , filename ):
469478 """
@@ -648,40 +657,18 @@ def init(self):
648657 + "</style:style>\n "
649658 )
650659
660+ # bug #5733: remember the properties this "F<name>" text style is
661+ # written with (from the CURRENT, still-unscaled font) so a later
662+ # scaled draw can tell whether the font changed and, if so, emit an
663+ # override instead of the stale named style (see _scaled_text_style).
664+ self ._draw_text_props [style_name ] = self ._draw_text_properties (style )
665+ self ._draw_text_style_names .add ("F%s" % style_name )
651666 wrt (
652667 '<style:style style:name="F%s" ' % style_name
653668 + 'style:family="text">\n '
654669 + "<style:text-properties "
655- )
656-
657- align = style .get_alignment ()
658- if align == PARA_ALIGN_LEFT :
659- wrt ('fo:text-align="start" ' )
660- elif align == PARA_ALIGN_RIGHT :
661- wrt ('fo:text-align="end" ' )
662- elif align == PARA_ALIGN_CENTER :
663- wrt ('fo:text-align="center" ' 'style:justify-single-word="false" ' )
664-
665- font = style .get_font ()
666- wrt (
667- 'style:font-name="%s" '
668- % (
669- "Arial"
670- if font .get_type_face () == FONT_SANS_SERIF
671- else "Times New Roman"
672- )
673- )
674-
675- color = font .get_color ()
676- wrt ('fo:color="#%02x%02x%02x" ' % color )
677- if font .get_bold ():
678- wrt ('fo:font-weight="bold" ' )
679- if font .get_italic ():
680- wrt ('fo:font-style="italic" ' )
681-
682- wrt (
683- 'fo:font-size="%.2fpt" ' % font .get_size ()
684- + 'style:font-size-asian="%.2fpt"/> ' % font .get_size ()
670+ + self ._draw_text_props [style_name ]
671+ + "/> "
685672 + "</style:style>\n "
686673 )
687674
@@ -782,6 +769,7 @@ def finish_cntnt_creation(self):
782769 self .add_styled_notes_fonts ()
783770 self .add_styled_notes_styles ()
784771 self .add_styled_photo_styles ()
772+ self .add_scaled_text_styles () # bug #5733: scaled draw-text overrides
785773 self .cntntx .write (self .cntnt1 .getvalue ())
786774 self .cntntx .write (self .cntnt2 .getvalue ())
787775 self .cntntx .write (self .cntnt .getvalue ())
@@ -1858,6 +1846,110 @@ def draw_line(self, style, x1, y1, x2, y2):
18581846 + "</draw:line>\n "
18591847 )
18601848
1849+ def _draw_text_properties (self , style ):
1850+ """
1851+ Return the body of the ``<style:text-properties …>`` element for a
1852+ paragraph style's draw-text ("F<name>") style, computed from the
1853+ style's CURRENT font.
1854+
1855+ Shared by :meth:`init` — which writes the named "F<name>" styles once,
1856+ up front — and :meth:`_scaled_text_style`, which runs later, after a
1857+ graphical report may have scaled the style sheet's fonts (bug #5733).
1858+ Because both paths format the font identically, an unscaled draw
1859+ reproduces the named style byte-for-byte, so :meth:`_scaled_text_style`
1860+ can reuse "F<name>" when nothing changed and register an override only
1861+ when the (scaled) font differs.
1862+ """
1863+ props = ""
1864+ align = style .get_alignment ()
1865+ if align == PARA_ALIGN_LEFT :
1866+ props += 'fo:text-align="start" '
1867+ elif align == PARA_ALIGN_RIGHT :
1868+ props += 'fo:text-align="end" '
1869+ elif align == PARA_ALIGN_CENTER :
1870+ props += 'fo:text-align="center" ' 'style:justify-single-word="false" '
1871+ font = style .get_font ()
1872+ props += 'style:font-name="%s" ' % (
1873+ "Arial" if font .get_type_face () == FONT_SANS_SERIF else "Times New Roman"
1874+ )
1875+ props += 'fo:color="#%02x%02x%02x" ' % font .get_color ()
1876+ if font .get_bold ():
1877+ props += 'fo:font-weight="bold" '
1878+ if font .get_italic ():
1879+ props += 'fo:font-style="italic" '
1880+ props += 'fo:font-size="%.2fpt" ' % font .get_size ()
1881+ props += 'style:font-size-asian="%.2fpt"' % font .get_size ()
1882+ return props
1883+
1884+ def _scaled_text_style (self , para_name ):
1885+ """
1886+ Return the name of the text style a drawn text span must reference so
1887+ that its font size reflects any per-report scaling applied AFTER
1888+ :meth:`init` wrote the fixed named "F<name>" styles (bug #5733).
1889+
1890+ "F<para_name>" is written once, up front, from the unscaled style
1891+ sheet. A graphical report ("scale tree to fit") then scales the style
1892+ sheet's fonts, but that pre-written style keeps the original size, so
1893+ ODT box text would render unscaled while the cairo (PDF) backend —
1894+ which reads the font at draw time — scales it. Here we read the
1895+ CURRENT font (post-scale): if it matches what "F<name>" already holds,
1896+ we reuse "F<name>" (unscaled output stays byte-identical); otherwise we
1897+ register an automatic text style carrying the current size and return
1898+ its name (flushed by :meth:`add_scaled_text_styles`).
1899+ """
1900+ if para_name not in self ._draw_text_props :
1901+ # No named "F<name>" text style was written for this paragraph
1902+ # style — e.g. a GraphicsStyle with an unset paragraph style, whose
1903+ # name defaults to "". Preserve the original, pre-#5733 behaviour
1904+ # (emit the "F<name>" reference verbatim) rather than resolving the
1905+ # style sheet and risking a KeyError.
1906+ return "F%s" % para_name
1907+ style = self .get_style_sheet ().get_paragraph_style (para_name )
1908+ props = self ._draw_text_properties (style )
1909+ if props == self ._draw_text_props [para_name ]:
1910+ return "F%s" % para_name
1911+ name = self ._scaled_text_styles .get (props )
1912+ if name is None :
1913+ name = self ._new_scaled_style_name ()
1914+ self ._scaled_text_styles [props ] = name
1915+ self ._scaled_text_defs .append ((name , props ))
1916+ return name
1917+
1918+ def _new_scaled_style_name (self ):
1919+ """
1920+ Allocate a text-style name for a scaled draw-text override that cannot
1921+ collide with any "F<name>" style :meth:`init` wrote nor any override
1922+ already registered (bug #5733). A fixed prefix alone is unsafe: a user
1923+ paragraph style literally named "Scaled1" yields an init style
1924+ "FScaled1", so we skip any candidate already reserved.
1925+ """
1926+ n = len (self ._scaled_text_defs ) + 1
1927+ while True :
1928+ name = "FScaled%d" % n
1929+ if name not in self ._draw_text_style_names :
1930+ self ._draw_text_style_names .add (name )
1931+ return name
1932+ n += 1
1933+
1934+ def add_scaled_text_styles (self ):
1935+ """
1936+ Write the automatic text styles registered by
1937+ :meth:`_scaled_text_style` into the automatic-styles section (bug
1938+ #5733). Mirrors :meth:`add_styled_notes_styles`: collected while the
1939+ body is generated and flushed, via ``cntnt2``, ahead of the body
1940+ content at :meth:`finish_cntnt_creation` time.
1941+ """
1942+ wrt2 = self .cntnt2 .write
1943+ for name , props in self ._scaled_text_defs :
1944+ wrt2 (
1945+ '<style:style style:name="%s" ' % name
1946+ + 'style:family="text">\n '
1947+ + "<style:text-properties "
1948+ + props
1949+ + "/> "
1950+ + "</style:style>\n "
1951+ )
1952+
18611953 def draw_text (self , style , text , x , y , mark = None ):
18621954 """
18631955 Draw a text
@@ -1869,6 +1961,7 @@ def draw_text(self, style, text, x, y, mark=None):
18691961 pstyle = style_sheet .get_paragraph_style (para_name )
18701962 font = pstyle .get_font ()
18711963 sw = utils .pt2cm (string_width (font , text )) * 1.3
1964+ text_style = self ._scaled_text_style (para_name ) # bug #5733
18721965
18731966 self ._write_mark (mark , text )
18741967
@@ -1882,7 +1975,7 @@ def draw_text(self, style, text, x, y, mark=None):
18821975 + 'svg:y="%.2fcm">' % float (y )
18831976 + "<draw:text-box> "
18841977 + '<text:p text:style-name="F%s">' % para_name
1885- + '<text:span text:style-name="F %s">' % para_name
1978+ + '<text:span text:style-name="%s">' % text_style
18861979 +
18871980 #' fo:max-height="%.2f">' % font.get_size() +
18881981 escape (text , ESC_MAP )
@@ -1928,9 +2021,10 @@ def draw_box(self, style, text, x, y, w, h, mark=None):
19282021 + 'svg:y="%.2fcm">\n ' % float (y )
19292022 )
19302023 if text :
2024+ text_style = self ._scaled_text_style (para_name ) # bug #5733
19312025 self .cntnt .write (
19322026 '<text:p text:style-name="%s">' % para_name
1933- + '<text:span text:style-name="F %s">' % para_name
2027+ + '<text:span text:style-name="%s">' % text_style
19342028 + escape (text , ESC_MAP )
19352029 + "</text:span>"
19362030 "</text:p>\n "
@@ -1963,10 +2057,11 @@ def center_text(self, style, text, x, y, mark=None):
19632057 )
19642058
19652059 if text :
2060+ text_style = self ._scaled_text_style (para_name ) # bug #5733
19662061 self .cntnt .write (
19672062 "<draw:text-box>"
19682063 + '<text:p text:style-name="X%s">' % para_name
1969- + '<text:span text:style-name="F %s">' % para_name
2064+ + '<text:span text:style-name="%s">' % text_style
19702065 + escape (text , ESC_MAP )
19712066 + "</text:span>\n "
19722067 + "</text:p>\n "
0 commit comments