Coverage for liitos/figures.py: 97.85%

73 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-14 21:47:10 +00:00

1"""Apply any scale command to subsequent figure environment. 

2 

3Implementation Note: The not a number (NAN) marker is used to indicate absence of scale command. 

4""" 

5 

6from collections.abc import Iterable 

7from enum import Enum 

8import math 

9from typing import Union 

10 

11from liitos import log 

12 

13Modus = Enum('Modus', 'COPY SCALE') 

14NAN = float('nan') 

15 

16EQ = '=' 

17SP = ' ' 

18SCALE_START_TRIGGER_STARTSWITH = r'\scale' 

19BARE_GRAPHICS_START_STARTSWITH = r'\includegraphics{' 

20WRAPPED_GRAPHICS_START_IN = r'\pandocbounded{\includegraphics' 

21EVEN_MORE_SO = r'\pandocbounded{\includegraphics[keepaspectratio,alt={' # HACK A DID ACK 

22 

23 

24def filter_seek_scale(line: str, slot: int, modus: Modus, rescale: float, outgoing: list[str]) -> tuple[Modus, float]: 

25 r"""Filter line, seek for a scale command, and return updated mnodus, rescale pair. 

26 

27 Examples: 

28 

29 >>> o = [] 

30 >>> line = SCALE_START_TRIGGER_STARTSWITH + EQ # r'\scale=' 

31 >>> m, r = filter_seek_scale(line, 0, Modus.COPY, NAN, o) 

32 >>> assert not o 

33 >>> assert m == Modus.SCALE 

34 >>> assert math.isnan(r) 

35 

36 >>> o = [] 

37 >>> m, r = filter_seek_scale('foo', 0, Modus.COPY, NAN, o) 

38 >>> assert o == ['foo'] 

39 >>> assert m == Modus.COPY 

40 >>> assert math.isnan(r) 

41 

42 >>> o = [] 

43 >>> m, r = filter_seek_scale(r'\scale=80\%', 0, Modus.COPY, NAN, o) 

44 >>> assert not o 

45 >>> assert m == Modus.SCALE 

46 >>> assert r == 0.8 

47 

48 >>> o = [] 

49 >>> m, r = filter_seek_scale(r'\scale 80\%', 0, Modus.COPY, NAN, o) 

50 >>> assert not o 

51 >>> assert m == Modus.SCALE 

52 >>> assert r == 0.8 

53 

54 >>> o = [] 

55 >>> m, r = filter_seek_scale(r'\scale = 0.8', 0, Modus.COPY, NAN, o) 

56 >>> assert not o 

57 >>> assert m == Modus.SCALE 

58 >>> assert r == 0.8 

59 """ 

60 if any(line.startswith(SCALE_START_TRIGGER_STARTSWITH + other) for other in (EQ, SP)): 

61 log.info(f'trigger a scale mod for the next figure environment at line #{slot + 1}|{line}') 

62 modus = Modus.SCALE 

63 scale = line # only for reporting will not pass the filter 

64 try: 

65 # \scale = 75\% --> 75\% 

66 # \scale 75\% --> 75\% 

67 sca = scale.split(EQ, 1)[1].strip() if EQ in scale else SP.join(scale.split()).split(SP, 1)[1].strip() 

68 rescale = float(sca.replace(r'\%', '')) / 100 if r'\%' in sca else float(sca) 

69 except Exception as err: 

70 log.error(f'failed to parse scale value from {line.strip()} with err: {err}') 

71 else: 

72 outgoing.append(line) 

73 

74 return modus, rescale 

75 

76 

77def filter_seek_figure(line: str, slot: int, modus: Modus, rescale: float, outgoing: list[str]) -> tuple[Modus, float]: 

78 r"""Filter line, seek for a figure, rescale if applicable, and return updated mnodus, rescale pair. 

79 

80 Examples: 

81 

82 >>> o = [] 

83 >>> m, r = filter_seek_figure(r'\includegraphics{', 0, Modus.COPY, NAN, o) 

84 >>> assert o == [r'\includegraphics{'] 

85 >>> assert m == Modus.COPY 

86 >>> assert math.isnan(r) 

87 

88 >>> o = [] 

89 >>> m, r = filter_seek_figure('foo', 0, Modus.COPY, 0.8, o) 

90 >>> assert o == ['foo'] 

91 >>> assert m == Modus.COPY 

92 >>> assert r == 0.8 

93 

94 >>> o = [] 

95 >>> rescale = 0.8 

96 >>> m, r = filter_seek_figure(r'\pandocbounded{\includegraphics', 0, Modus.COPY, rescale, o) 

97 >>> assert o[0].startswith(r'\pandocbounded{\includegraphics') 

98 >>> assert f'textwidth,height={rescale}' in o[0] 

99 >>> assert m == Modus.COPY 

100 >>> assert math.isnan(r) 

101 

102 >>> o = [] 

103 >>> m, r = filter_seek_figure(r'\pandocbounded{\includegraphics', 0, Modus.COPY, NAN, o) 

104 >>> assert o[0].startswith(r'\pandocbounded{\includegraphics') 

105 >>> assert m == Modus.COPY 

106 >>> assert math.isnan(r) 

107 """ 

108 if line.startswith(BARE_GRAPHICS_START_STARTSWITH): 

109 if not math.isnan(rescale): 

110 log.info(f'- found the scale target start at line #{slot + 1}|{line}') 

111 target = line.replace(BARE_GRAPHICS_START_STARTSWITH, '{') 

112 option = f'[width={round(rescale, 2)}\\textwidth,height={round(rescale, 2)}' '\\textheight,keepaspectratio]' 

113 outgoing.append(f'\\includegraphics{option}{target}') 

114 else: 

115 outgoing.append(line) 

116 modus = Modus.COPY 

117 rescale = NAN 

118 elif EVEN_MORE_SO in line: 

119 if not math.isnan(rescale): 119 ↛ 131line 119 didn't jump to line 131 because the condition on line 119 was always true

120 log.info(f'- found the scale target start at line #{slot + 1}|{line}') 

121 target = line.replace(WRAPPED_GRAPHICS_START_IN, '').replace('[keepaspectratio', '') 

122 parts = target.split('}}') 

123 rest, inside = ('', '') if len(parts) < 2 else (parts[1].lstrip('}'), parts[0] + '}') 

124 option = f'[width={round(rescale, 2)}\\textwidth,height={round(rescale, 2)}' '\\textheight,keepaspectratio' 

125 patched = f'{WRAPPED_GRAPHICS_START_IN}{option}{inside}}}{rest}' 

126 hack = '}}' 

127 if not patched.endswith(hack): 

128 patched += hack 

129 outgoing.append(patched) 

130 else: 

131 outgoing.append(line) 

132 modus = Modus.COPY 

133 rescale = NAN 

134 elif WRAPPED_GRAPHICS_START_IN in line: 

135 if not math.isnan(rescale): 

136 log.info(f'- found the scale target start at line #{slot + 1}|{line}') 

137 target = line.replace(WRAPPED_GRAPHICS_START_IN, '').replace('[keepaspectratio]', '') 

138 parts = target.split('}}') 

139 rest, inside = ('', '') if len(parts) < 2 else (parts[1].lstrip('}'), parts[0] + '}') 

140 option = f'[width={round(rescale, 2)}\\textwidth,height={round(rescale, 2)}' '\\textheight,keepaspectratio' 

141 outgoing.append(f'{WRAPPED_GRAPHICS_START_IN}{option}{inside}}}{rest}') 

142 else: 

143 outgoing.append(line) 

144 modus = Modus.COPY 

145 rescale = NAN 

146 else: 

147 outgoing.append(line) 

148 

149 return modus, rescale 

150 

151 

152def scale(incoming: Iterable[str], lookup: Union[dict[str, str], None] = None) -> list[str]: 

153 r"""Scan for scale command and if, apply it to the includegraphics LaTeX command. 

154 

155 Examples: 

156 

157 >>> in_lines = [r'\scale=80\%', '', r'\includegraphics{', '', 'quux'] 

158 >>> scaled = scale(in_lines) 

159 >>> scaled 

160 ['', '\\includegraphics[width=0.8\\textwidth,height=0.8\\textheight,keepaspectratio]{', '', 'quux'] 

161 

162 

163 >>> in_lines = ['foo', '', r'\includegraphics{', '', 'quux'] 

164 >>> scaled = scale(in_lines) 

165 >>> scaled 

166 ['foo', '', '\\includegraphics{', '', 'quux'] 

167 """ 

168 outgoing: list[str] = [] 

169 modus = Modus.COPY 

170 rescale = NAN 

171 for slot, line in enumerate(incoming): 

172 line = line.rstrip('\n') 

173 if modus == Modus.COPY: 

174 modus, rescale = filter_seek_scale(line, slot, modus, rescale, outgoing) 

175 else: # if modus == Modus.SCALE: 

176 modus, rescale = filter_seek_figure(line, slot, modus, rescale, outgoing) 

177 

178 return outgoing