Coverage for liitos/figures.py: 100.00%

55 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-05 17:22:35 +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 

16SCALE_START_TRIGGER_STARTSWITH = r'\scale=' 

17BARE_GRAPHICS_START_STARTSWITH = r'\includegraphics{' 

18WRAPPED_GRAPHICS_START_IN = r'\pandocbounded{\includegraphics' 

19 

20 

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

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

23 

24 Examples: 

25 

26 >>> o = [] 

27 >>> line = SCALE_START_TRIGGER_STARTSWITH # r'\scale=' 

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

29 >>> assert not o 

30 >>> assert m == Modus.SCALE 

31 >>> assert math.isnan(r) 

32 

33 >>> o = [] 

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

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

36 >>> assert m == Modus.COPY 

37 >>> assert math.isnan(r) 

38 

39 >>> o = [] 

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

41 >>> assert not o 

42 >>> assert m == Modus.SCALE 

43 >>> assert r == 0.8 

44 """ 

45 if line.startswith(SCALE_START_TRIGGER_STARTSWITH): 

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

47 modus = Modus.SCALE 

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

49 try: 

50 sca = scale.split('=', 1)[1].strip() # \scale = 75\% --> 75\% 

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

52 except Exception as err: 

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

54 else: 

55 outgoing.append(line) 

56 

57 return modus, rescale 

58 

59 

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

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

62 

63 Examples: 

64 

65 >>> o = [] 

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

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

68 >>> assert m == Modus.COPY 

69 >>> assert math.isnan(r) 

70 

71 >>> o = [] 

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

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

74 >>> assert m == Modus.COPY 

75 >>> assert r == 0.8 

76 

77 >>> o = [] 

78 >>> rescale = 0.8 

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

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

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

82 >>> assert m == Modus.COPY 

83 >>> assert math.isnan(r) 

84 

85 >>> o = [] 

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

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

88 >>> assert m == Modus.COPY 

89 >>> assert math.isnan(r) 

90 """ 

91 if line.startswith(BARE_GRAPHICS_START_STARTSWITH): 

92 if not math.isnan(rescale): 

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

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

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

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

97 else: 

98 outgoing.append(line) 

99 modus = Modus.COPY 

100 rescale = NAN 

101 elif WRAPPED_GRAPHICS_START_IN in line: 

102 if not math.isnan(rescale): 

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

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

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

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

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

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

109 else: 

110 outgoing.append(line) 

111 modus = Modus.COPY 

112 rescale = NAN 

113 else: 

114 outgoing.append(line) 

115 

116 return modus, rescale 

117 

118 

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

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

121 

122 Examples: 

123 

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

125 >>> scaled = scale(in_lines) 

126 >>> scaled 

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

128 

129 

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

131 >>> scaled = scale(in_lines) 

132 >>> scaled 

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

134 """ 

135 outgoing: list[str] = [] 

136 modus = Modus.COPY 

137 rescale = NAN 

138 for slot, line in enumerate(incoming): 

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

140 if modus == Modus.COPY: 

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

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

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

144 

145 return outgoing