text-sizing-protocol.rst 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. The text sizing protocol
  2. ==============================================
  3. .. versionadded:: 0.40.0
  4. Classically, because the terminal is a grid of equally sized characters, only
  5. a single text size was supported in terminals, with one minor exception, some
  6. characters were allowed to be rendered in two cells, to accommodate East Asian
  7. square aspect ratio characters and Emoji. Here, by single text size we mean the
  8. font size of all text on the screen is the same.
  9. This protocol allows text to be displayed in the terminal in different sizes
  10. both larger and smaller than the base text. It also solves the long standing
  11. problem of robustly determining the width (in cells) a character should have.
  12. Applications can interleave text of different sizes on the screen allowing for
  13. typographic niceties like headlines, superscripts, etc.
  14. Note that this protocol is fully backwards compatible, terminals that implement
  15. it will continue to work just the same with applications that do not use it.
  16. Because of this, it is not fully flexible in the font sizes it allows, as it
  17. still has to work with the character cell grid based fundamental nature of the
  18. terminal. Public discussion of this protocol is :iss:`here <8226>`.
  19. Quickstart
  20. --------------
  21. Using this protocol to display different sized text is very simple, let's
  22. illustrate with a few examples to give us a flavor:
  23. .. code-block:: sh
  24. printf "\e]_text_size_code;s=2;Double sized text\a\n\n"
  25. printf "\e]_text_size_code;s=3;Triple sized text\a\n\n\n"
  26. printf "\e]_text_size_code;n=1:d=2;Half sized text\a\n"
  27. Note that the last example, of half sized text, has half height characters, but
  28. they still each take one cell, this can be fixed with a little more work:
  29. .. code-block:: sh
  30. printf "\e]_text_size_code;n=1:d=2:w=1;Ha\a\e]66;n=1:d=2:w=1;lf\a\n"
  31. The ``w=1`` mechanism allows the program to tell the terminal what width the text
  32. should take. This not only fixes using smaller text but also solves the long
  33. standing terminal ecosystem bugs caused by the client program not knowing how
  34. many cells the terminal will render some text in.
  35. The escape code
  36. -----------------
  37. There is a single escape code used by this protocol. It is sent by client
  38. programs to the terminal emulator to tell it to render the specified text
  39. at the specified size. It is an ``OSC`` code of the form::
  40. <OSC> _text_size_code ; metadata ; text <terminator>
  41. Here, ``OSC`` is the bytes ``ESC ] (0x1b 0x5b)``. The ``metadata`` is a colon
  42. separated list of ``key=value`` pairs. The final part of the escape code is the
  43. text which is simply plain text encoded as :ref:`safe_utf8`, the text must be
  44. no longer than ``4096`` bytes. Longer strings than that must be broken up into
  45. multiple escape codes. Spaces in this definition are for clarity only and
  46. should be ignored. The ``terminator`` is either the byte ``BEL (0x7)`` or the
  47. bytes ``ESC ST (0x1b 0x5c)``.
  48. There are only a handful of metadata keys, defined in the table below:
  49. .. csv-table:: The text sizing metadata keys
  50. :header: "Key", "Value", "Default", "Description"
  51. "s", "Integer from 1 to 7", "1", "The overall scale, the text will be rendered in a block of ``s * w`` by ``s`` cells"
  52. "w", "Integer from 0 to 7", "0", "The width, in cells, in which the text should be rendered. When zero, the terminal should calculate the width as it would for normal text, splitting it up into scaled cells."
  53. "n", "Integer from 0 to 15", "0", "The numerator for the fractional scale."
  54. "d", "Integer from 0 to 15", "0", "The denominator for the fractional scale. Must be ``> n`` when non-zero."
  55. "v", "Integer from 0 to 2", "0", "The vertical alignment to use for fractionally scaled text. ``0`` - top, ``1`` - bottom, ``2`` - centered"
  56. How it works
  57. ------------------
  58. This protocol works by allowing the client program to tell the terminal to
  59. render text in multiple cells. The terminal can then adjust the actual font
  60. size used to render the specified text as appropriate for the specified space.
  61. The space to render is controlled by four metadata keys, ``s (scale)``, ``w (width)``, ``n (numerator)``
  62. and ``d (denominator)``. The most important are the ``s`` and ``w`` keys. The text
  63. will be rendered in a block of ``s * w`` by ``s`` cells. A special case is ``w=0``
  64. (the default), which means the terminal splits up the text into cells as it
  65. would normally without this protocol, but now each cell is an ``s by s`` block of
  66. cells instead. So, for example, if the text is ``abc`` and ``s=2`` the terminal would normally
  67. split it into three cells::
  68. │a│b│c│
  69. But, because ``s=2`` it instead gets split as::
  70. │a░│b░│c░│
  71. │░░│░░│░░│
  72. The terminal multiplies the font size by ``s`` when rendering these
  73. characters and thus ends up rendering text at twice the base size.
  74. When ``w`` is a non-zero value, it specifies the width in scaled cells of the
  75. following text. Note that **all** the text in that escape code must be rendered
  76. in ``s * w`` cells. When both ``s`` and ``w`` are present, the resulting multicell
  77. contains all the text in the escape code rendered in a grid of ``(s * w, s)``
  78. cells, i.e. the multicell is ``s*w`` cells wide and ``s`` cells high.
  79. If the text does not fit, the terminal is free to do whatever it
  80. feels is best, including truncating the text or downsizing the font size when
  81. rendering it. It is up to client applications to use the ``w`` key wisely and not
  82. try to render too much text in too few cells. When sending a string of text
  83. with non zero ``w`` to the terminal emulator, the way to do it is to split up the
  84. text into chunks that fit in ``w`` cells and send one escape code per chunk. So
  85. for the string: ``cool-🐈`` the actual escape codes would be (ignoring the header
  86. and trailers)::
  87. w=1;c w=1;o w=1;o w=1;l w=1;- w=2:🐈
  88. Note, in particular, how the last character, the cat emoji, ``🐈`` has ``w=2``.
  89. In practice client applications can assume that terminal emulators get the
  90. width of all ASCII characters correct and use the ``w=0`` form for efficient
  91. transmission, so that the above becomes::
  92. cool- w=2:🐈
  93. The use of non-zero ``w`` should mainly be restricted to non-ASCII characters and
  94. when using fractional scaling, as described below.
  95. .. note:: Text sizes specified by scale are relative to the base font size,
  96. thus if the base font size is changed, these sizes are changed as well.
  97. So if the terminal emulator is using a base font size of ``11pt``, then
  98. ``s=2`` will be rendered in approximately ``22pt`` (approx. because the
  99. terminal may need to slightly adjust font size to ensure it fits as not all
  100. fonts scale sizes linearly). If the user changes the base font size of the
  101. terminal emulator to ``12pt`` then the scaled font size becomes ``~24pt``
  102. and so on.
  103. Fractional scaling
  104. ^^^^^^^^^^^^^^^^^^^^^^^
  105. Using the main scale parameter (``s``) gives us only 7 font sizes. Fortunately,
  106. this protocol allows specifying fractional scaling, fractional scaling is
  107. applied on top of the main scale specified by ``s``. It allows niceties like:
  108. * Normal sized text but with half a line of blank space above and half a line below (``s=2:n=1:d=2:v=2``)
  109. * Superscripts (``n=1:d=2``)
  110. * Subscripts (``n=1:d=2:v=1``)
  111. * ...
  112. The fractional scale **does not** affect the number of cells the text occupies,
  113. instead, it just adjusts the rendered font size within those cells.
  114. The fraction is specified using an integer numerator and denominator (``n`` and
  115. ``d``). In addition, by using the ``v`` key one can vertically align the
  116. fractionally scaled text at top, bottom or middle.
  117. When using fractional scaling one often wants to fit more than a single
  118. character per cell. To accommodate that, there is the ``w`` key. This specifies
  119. the number of cells in which to render the text. For example, for a superscript
  120. one would typically split the string into pairs of characters and use the
  121. following for each pair::
  122. OSC _text_size_code ; n=1:d=2:w=1 ; ab <terminator>
  123. ... repeat for each pair of characters
  124. Fixing the character width issue for the terminal ecosystem
  125. ---------------------------------------------------------------------
  126. Terminals create user interfaces using text displayed in a cell grid. For
  127. terminal software that creates sophisticated user interfaces it is particularly
  128. important that the client program running in the terminal and the terminal
  129. itself agree on how many cells a particular string should be rendered in. If
  130. the two disagree, then the entire user interface can be broken, leading to
  131. catastrophic failures.
  132. Fundamentally, this is a co-ordination problem. Both the client program and the
  133. terminal have to somehow share the same database of character properties and
  134. the same algorithm for computing string lengths in cells based on that shared
  135. database. Sadly, there is no such shared database in reality. The closest we
  136. have is the Unicode standard. Unfortunately, the Unicode standard has a new
  137. version almost every year and actually changes the width assigned to some
  138. characters in different versions. Furthermore, to actually get the "correct"
  139. width for a string using that standard one has to do grapheme segmentation,
  140. which is an `extremely complex algorithm
  141. <https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries>`__.
  142. Expecting all terminals and all terminal programs to have both up-to-date
  143. character databases and a bug free implementation of this algorithm is not
  144. realistic.
  145. So instead, this protocol solves the issue robustly by removing the
  146. co-ordination problem and putting only one actor in charge of determining
  147. string width. The client becomes responsible for doing whatever level of
  148. grapheme segmentation it is comfortable with using whatever Unicode database is
  149. at its disposal and then it can transmit the segmented string to the terminal
  150. with the appropriate ``w`` values so that the terminal renders the text in the
  151. exact number of cells the client expects.
  152. .. note::
  153. It is possible for a terminal to implement only the width part of this spec
  154. and ignore the scale part. This escape code works with only the `w` key as
  155. well, as a means of specifying how many cells each piece of text occupies.
  156. In such cases ``s`` defaults to 1.
  157. See the section on :ref:`detect_text_sizing` on how client applications can
  158. query for terminal emulator support.
  159. Wrapping and overwriting behavior
  160. -------------------------------------
  161. If the multicell block (``s * w by s`` cells) is larger than the screen size in either
  162. dimension, the terminal must discard the character. Note that in particular
  163. this means that resizing a terminal screen so that it is too small to fit a
  164. multicell character can cause the character to be lost.
  165. When drawing a multicell character, if wrapping is enabled (DECAWM is set) and
  166. the character's width (``s * w``) does not fit on the current line, the cursor is
  167. moved to the start of the next line and the character is drawn there.
  168. If wrapping is disabled and the character's width does not fit on the current
  169. line, the cursor is moved back as far as needed to fit ``s * w`` cells and then
  170. the character is drawn, following the overwriting rules described below.
  171. When drawing text either normal text or text specified via this escape code,
  172. and this text would overwrite an existing multicell character, the following
  173. rules must be followed, in decreasing order of precedence:
  174. #. If the text is a combining character it is added to the existing multicell
  175. character
  176. #. If the text will overwrite the top-left cell of the multicell character, the
  177. entire multicell character must be erased
  178. #. If the text will overwrite any cell in the topmost row of the multicell
  179. character, the entire multicell character must be replaced by spaces (this
  180. rule is present for backwards compatibility with how overwriting works for
  181. wide characters)
  182. #. If the text will overwrite cells from a row after the first row, then cursor should be moved past the
  183. cells of the multicell character on that row and only then the text should be
  184. written. Note that this behavior is independent of the value of DECAWM. This
  185. is done for simplicity of implementation.
  186. The skipping behavior of the last rule can be complex requiring the terminal to
  187. skip over lots of cells, but it is needed to allow wrapping in the presence of
  188. multicell characters that extend over more than a single line.
  189. .. _detect_text_sizing:
  190. Detecting if the terminal supports this protocol
  191. -----------------------------------------------------
  192. To detect support for this protocol use the `CPR (Cursor Position Report)
  193. <https://vt100.net/docs/vt510-rm/CPR.html>`__ escape code. Send a ``CPR``
  194. followed by ``\e]_text_size_code;w=2; \a`` which will draw a space character in
  195. two cells, followed by another ``CPR``. Then send ``\e]_text_size_code;s=2; \a``
  196. which will draw a space in a ``2 by 2`` block of cells, followed by another
  197. ``CPR``.
  198. Then wait for the three responses from the terminal to the three CPR queries.
  199. If the cursor position in the three responses is the same, the terminal does
  200. not support this protocol at all, if the second response has a different cursor
  201. position then the width part is supported and if the third response has yet
  202. another position, the scale part is supported.
  203. Interaction with other terminal controls
  204. --------------------------------------------------
  205. This protocol does not change the character grid based nature of the terminal.
  206. Most terminal controls assume one character per cell so it is important to
  207. specify how these controls interact with the multicell characters created by
  208. this protocol.
  209. Cursor movement
  210. ^^^^^^^^^^^^^^^^^^^
  211. Cursor movement is unaffected by multicell characters, all cursor movement
  212. commands move the cursor position by single cell increments, as has always been
  213. the case for terminals. This means that the cursor can be placed at any
  214. individual single cell inside a larger multicell character.
  215. When a multicell character is created using this protocol, the cursor moves
  216. `s * w` cells to the right, in the same row it was in.
  217. Terminals *should* display a large cursor covering the entire multicell block
  218. when the actual cursor position is on any cell within the block. Block cursors
  219. cover all the cells of the multicell character, bar cursors appear in all the
  220. cells in the first column of the character and so on.
  221. Editing controls
  222. ^^^^^^^^^^^^^^^^^^^^^^^^^
  223. There are many controls used to edit existing screen content such as
  224. inserting characters, deleting characters and lines, etc. These were all
  225. originally specified for the one character per cell paradigm. Here we specify
  226. their interactions with multicell characters.
  227. **Insert characters** (``CSI @`` aka ``ICH``)
  228. When inserting ``n`` characters at cursor position ``x, y`` all characters
  229. after ``x`` on line ``y`` are supposed to be right shifted. This means
  230. that any multi-line character that intersects with the cells on line ``y`` at ``x``
  231. and beyond must be erased. Any single line multicell character that is
  232. split by the cells at ``x`` and ``x + n - 1`` must also be erased.
  233. **Delete characters** (``CSI P`` aka ``DCH``)
  234. When deleting ``n`` characters at cursor position ``x, y`` all characters
  235. after ``x`` on line ``y`` are supposed to be left shifted. This means
  236. that any multi-line character that intersects with the cells on line ``y`` at ``x``
  237. and beyond must be erased. Any single line multicell character that is
  238. split by the cells at ``x`` and ``x + n - 1`` must also be erased.
  239. **Erase characters** (``CSI X`` aka ``ECH``)
  240. When erasing ``n`` characters at cursor position ``x, y`` the ``n`` cells
  241. starting at ``x`` are supposed to be cleared. This means that any multicell
  242. character that intersects with the ``n`` cells starting at ``x`` must be
  243. erased.
  244. **Erase display** (``CSI J`` aka ``ED``)
  245. Any multicell character intersecting with the erased region of the screen
  246. must be erased. When using mode ``22`` the contents of the screen are first
  247. copied into the history, including all multicell characters.
  248. **Erase in line** (``CSI K`` aka ``EL``)
  249. Works just like erase characters above. Any multicell character
  250. intersecting with the erased cells in the line is erased.
  251. **Insert lines** (``CSI L`` aka ``IL``)
  252. When inserting ``n`` lines at cursor position ``y`` any multi-line
  253. characters that are split at the line ``y`` must be erased. A split happens
  254. when the second or subsequent row of the multi-line character is on the line
  255. ``y``. The insertion causes ``n`` lines to be removed from the bottom of
  256. the screen, any multi-line characters are split at the bottom of the screen
  257. must be erased. A split is when any row of the multi-line character except
  258. the last row is on the last line of the screen after the insertion of ``n``
  259. lines.
  260. **Delete lines** (``CSI M`` aka ``DL``)
  261. When deleting ``n`` lines at cursor position ``y`` any multicell character
  262. that intersects the deleted lines must be erased.