Tcl_GetEncoding(3tcl)
Tcl_GetEncoding(3tcl) π
okay bestie so this is a page from the Tcl Library Procedures manual, and it's the one about encodings β you know, the thing that makes your bytes make sense. π¦
β¨ NAME β¨
Tcl_GetEncoding, Tcl_FreeEncoding, Tcl_GetEncodingFromObj, Tcl_ExternalToUtfDString, Tcl_ExternalToUtf, Tcl_UtfToExternalDString, Tcl_UtfToExternal, Tcl_WinTCharToUtf, Tcl_WinUtfToTChar, Tcl_GetEncodingName, Tcl_SetSystemEncoding, Tcl_GetEncodingNameFromEnvironment, Tcl_GetEncodingNames, Tcl_CreateEncoding, Tcl_GetEncodingSearchPath, Tcl_SetEncodingSearchPath, Tcl_GetDefaultEncodingDir, Tcl_SetDefaultEncodingDir β procedures for creating and using encodings
πΈ SYNOPSIS πΈ
#include <tcl.h>
Tcl_Encoding
Tcl_GetEncoding(interp, name)
void
Tcl_FreeEncoding(encoding)
int
Tcl_GetEncodingFromObj(interp, objPtr, encodingPtr)
char *
Tcl_ExternalToUtfDString(encoding, src, srcLen, dstPtr)
char *
Tcl_UtfToExternalDString(encoding, src, srcLen, dstPtr)
int
Tcl_ExternalToUtf(interp, encoding, src, srcLen, flags, statePtr,
dst, dstLen, srcReadPtr, dstWrotePtr, dstCharsPtr)
int
Tcl_UtfToExternal(interp, encoding, src, srcLen, flags, statePtr,
dst, dstLen, srcReadPtr, dstWrotePtr, dstCharsPtr)
char *
Tcl_WinTCharToUtf(tsrc, srcLen, dstPtr)
TCHAR *
Tcl_WinUtfToTChar(src, srcLen, dstPtr)
const char *
Tcl_GetEncodingName(encoding)
int
Tcl_SetSystemEncoding(interp, name)
const char *
Tcl_GetEncodingNameFromEnvironment(bufPtr)
void
Tcl_GetEncodingNames(interp)
Tcl_Encoding
Tcl_CreateEncoding(typePtr)
Tcl_Obj *
Tcl_GetEncodingSearchPath()
int
Tcl_SetEncodingSearchPath(searchPath)
const char *
Tcl_GetDefaultEncodingDir(void)
void
Tcl_SetDefaultEncodingDir(path)
π ARGUMENTS π
Tcl_Interp *interp(in) β the interpreter you wanna use for reporting errors. OrNULLif you literally do not want error reporting at all, like a girl who refuses to be perceived. πconst char *name(in) β the name of the encoding you wanna load. that's it, that's the whole vibe check. β¨Tcl_Encoding encoding(in) β the encoding you're querying, freeing, or using to convert text. hot tip: ifencodingisNULL, the current system encoding gets used instead. iconic behavior honestly.Tcl_Obj *objPtr(in) β the name of the encoding you want a token for.Tcl_Encoding *encodingPtr(out) β points to storage where the encoding token is gonna be written. output girlie πconst char *src(in) β depends which function you're in, bestie:- for the
Tcl_ExternalToUtffunctions, it's an array of bytes in the specified encoding that are to be converted to UTF-8. - for the
Tcl_UtfToExternalandTcl_WinUtfToTCharfunctions, it's an array of UTF-8 characters to be converted to the specified encoding.
- for the
const TCHAR *tsrc(in) β an array of WindowsTCHARcharacters to convert to UTF-8. Windows only, no cap. πͺint srcLen(in) β length ofsrcortsrcin bytes. and if the length is negative? then the encoding-specific length of the string is used instead. sneaky but valid. πTcl_DString *dstPtr(out) β pointer to an uninitialized or freeTcl_DStringwhere the converted result will be stored.int flags(in) β various flag bits OR-ed together. the trio of destiny:TCL_ENCODING_STARTβ says the source buffer is the first block in a (potentially multi-block) input stream. it tells the conversion routine to reset to an initial state and do any initialization that needs to happen before the first byte gets converted. πTCL_ENCODING_ENDβ says the source buffer is the last block in a (potentially multi-block) input stream. it tells the conversion routine to do any finalization that needs to happen after the last byte is converted, and then reset to an initial state. π¬TCL_ENCODING_STOPONERRORβ says the conversion routine should return immediately upon reading a source character that doesn't exist in the target encoding. otherwise, a default fallback character gets automatically substituted. no silent weirdness if you don't want it, periodt.
Tcl_EncodingState *statePtr(in/out) β used when you're converting a (generally long or indefinite length) byte stream piece-by-piece, which is the moodiest possible way to convert things. the conversion routine stores its current state in*statePtraftersrc(the buffer containing the current piece) has been converted; that state info must be passed back when converting the next piece of the stream, so the conversion routine knows what state it was in when it left off at the end of the last piece. it may beNULL, in which case the value specified forflagsis ignored and the source buffer is assumed to contain the complete string to convert. π§char *dst(out) β the buffer where the converted result will be stored. no more thandstLenbytes will be stored indst, so likeβ¦ respect the limit bestie.int dstLen(in) β the maximum length of the output bufferdst, in bytes. π
int *srcReadPtr(out) β this one gets filled with the number of bytes fromsrcthat were actually converted, fr. Heads up though: it can be less than the original source length if something went sideways converting some of the source characters. Also, it's allowed to beNULL. πint *dstWrotePtr(out) β filled with how many bytes genuinely made it into the output buffer because of the conversion. Can also beNULL, no pressure.int *dstCharsPtr(out) β filled with the number of characters that line up with the number of bytes that got stored in the output buffer. Yeah, it can beNULLtoo. Everybody here can beNULL, it's that kind of party. πTcl_DString *bufPtr(out) β storage for the prescribed system encoding name. That's where the name lives.const Tcl_EncodingType *typePtr(in) β a structure that defines a brand new type of encoding. New encoding, who dis. β¨Tcl_Obj *searchPath(in) β a list of filesystem directories where we go hunting for encoding data files.const char *path(in) β a path to the location of the encoding file. Just the address, bestie.
π INTRODUCTION π
Okay so here's the whole vibe: these routines convert between Tcl's internal character representation, which is UTF-8, and the character representations used by all these different operating systems or file systems β think Unicode, ASCII, or Shift-JIS. π¦
When we're out here operating on strings β like getting the names of files, or displaying characters with international fonts β those strings have to be translated into one or possibly multiple formats that the various system calls can actually use. No translation, no service. π
Real example, because I know you love a real example: on a Japanese Unix workstation, a user might grab a filename that's represented in the EUC-JP file encoding, and then translate those characters over to the jisx0208 font encoding so the filename can actually show up in a Tk widget. That's the translation gap, and the entire purpose of the encoding package is to help bridge it. π
And the way it bridges it? UTF-8 is the intermediate staging ground for all the various encodings. In that example up there, text gets translated into UTF-8 from whatever file encoding the operating system is using, and then translated from UTF-8 into whatever font encoding the display routines need. Middle-man UTF-8, iconic behavior honestly. π
Some basic encodings are compiled right into Tcl. Others can be defined by the user, or dynamically loaded from encoding files in a platform-independent manner. Options, we love to see it. π
β¨ DESCRIPTION β¨
Tcl_GetEncoding finds an encoding given its name. That name could point to a built-in Tcl encoding, a user-defined encoding registered by calling Tcl_CreateEncoding, or a dynamically-loadable encoding file. The return value is a token that represents the encoding, and you can use it in subsequent calls to procedures like Tcl_GetEncodingName, Tcl_FreeEncoding, and Tcl_UtfToExternal. If the name didn't refer to any known or loadable encoding, NULL is returned and an error message gets returned in interp. π
The encoding package keeps a database of all encodings currently in use. The first time name is seen, Tcl_GetEncoding returns an encoding with a reference count of 1. If the same name gets requested again later, then the reference count for that encoding is incremented β and no cap, it skips the overhead of allocating a new encoding plus all its associated data structures. Reuse queen. β»οΈ
When an encoding isn't needed anymore, you should call Tcl_FreeEncoding to release it. And when an encoding is no longer in use anywhere β meaning it's been freed exactly as many times as it's been gotten β Tcl_FreeEncoding will release all the storage the encoding was using and delete it from the database. Balance restored. βοΈ
Tcl_GetEncodingFromObj treats the string representation of objPtr as an encoding name and finds an encoding with that name, exactly like Tcl_GetEncoding does. When an encoding is found, it gets cached within the objPtr value for future reference, the Tcl_Encoding token is written to the storage pointed to by encodingPtr, and the value TCL_OK is returned. If no such encoding is found, the value TCL_ERROR is returned, and no writing to *encodingPtr takes place. And just like with Tcl_GetEncoding, the caller should call Tcl_FreeEncoding on the resulting encoding token once that token will no longer be used. Clean up after yourself bestie. π§Ή
Tcl_ExternalToUtfDString converts a source buffer src from the specified encoding into UTF-8. The converted bytes get stored in dstPtr, which is then null-terminated. The caller should eventually call Tcl_DStringFree to free any information stored in dstPtr. When converting, if any of the characters in the source buffer can't be represented in the target encoding, a default fallback character will be used. The return value is a pointer to the value stored in the DString. π«Ά
Tcl_ExternalToUtf converts a source buffer src from the specified encoding into UTF-8. Up to srcLen bytes get converted from the source buffer, and up to dstLen converted bytes get stored in dst. In all cases, *srcReadPtr is filled with the number of bytes that were successfully converted from src, and *dstWrotePtr is filled with the corresponding number of bytes that were stored in dst. The return value is one of these, and you're gonna want to know which:
TCL_OKβ all bytes ofsrcwere converted. Slay, no notes. β¨TCL_CONVERT_NOSPACEβ the destination buffer wasn't large enough for all of the converted data. As many characters as could fit were converted though, so it tried its best. πTCL_CONVERT_MULTIBYTEβ the last few bytes in the source buffer were the beginning of a multibyte sequence, but more bytes were needed to complete that sequence. A subsequent call to the conversion routine should pass a buffer containing the unconverted bytes that remained insrcplus some further bytes from the source stream, so the formerly split-up multibyte sequence can get properly converted. It's giving "to be continued". πTCL_CONVERT_SYNTAXβ the source buffer contained an invalid character sequence. This can happen if the input stream has been damaged, or if the input encoding method was misidentified. Ruh roh. π¨TCL_CONVERT_UNKNOWNβ the source buffer contained a character that could not be represented in the target encoding, andTCL_ENCODING_STOPONERRORwas specified.
Tcl_UtfToExternalDString converts a source buffer src from UTF-8 into the specified encoding. The converted bytes get stored in dstPtr, which is then terminated with the appropriate encoding-specific null. The caller should eventually call Tcl_DStringFree to free any information stored in dstPtr. When converting, if any of the characters in the source buffer can't be represented in the target encoding, a default fallback character will be used. The return value is a pointer to the value stored in the DString. π
Tcl_UtfToExternal converts a source buffer src from UTF-8 into the specified encoding. Up to srcLen bytes get converted from the source buffer, and up to dstLen converted bytes get stored in dst. In all cases, *srcReadPtr is filled with the number of bytes that were successfully converted from src, and *dstWrotePtr is filled with the corresponding number of bytes that were stored in dst. The return values are the same as the return values for Tcl_ExternalToUtf. Same energy, opposite direction. π
Tcl_WinUtfToTChar and Tcl_WinTCharToUtf are Windows-only convenience functions for converting between UTF-8 and Windows strings based on the TCHAR type, which is by convention a Unicode character on Windows NT. Windows exclusives, iykyk. πͺ
Tcl_GetEncodingName is roughly the inverse of Tcl_GetEncoding. Give it an encoding, and the return value is the name argument that was used to create that encoding. One thing though: the string returned by Tcl_GetEncodingName is only guaranteed to persist until the encoding is deleted, and the caller must not modify this string. Don't touch, just look. π
Tcl_SetSystemEncoding sets the default encoding that should be used whenever the user passes a NULL value for the encoding argument to any of the other encoding functions. If name is NULL, the system encoding gets reset to the default system encoding, binary. If the name didn't refer to any known or loadable encoding, TCL_ERROR is returned and an error message is left in interp. Otherwise, this procedure increments the reference count of the new system encoding, decrements the reference count of the old system encoding, and returns TCL_OK. In with the new, out with the old. π
Tcl_GetEncodingNameFromEnvironment gives the Tcl library a way to report the encoding name it believes is the correct one to use as the system encoding, based on system calls and an examination of the environment suitable for the platform. It accepts bufPtr, a pointer to an uninitialized or freed Tcl_DString, and writes the encoding name to it. The Tcl_DStringValue is returned. πΈ
Tcl_GetEncodingNames is the one that gives you the receipts, bestie π
β it sets the interp result to a list of the names of all the encodings that are currently defined or that can be dynamically loaded, and it goes hunting through the encoding path that you set with Tcl_SetDefaultEncodingDir.
β οΈ Real talk though: this procedure does not check that the dynamically-loadable encoding files actually contain valid data. It literally just checks that they exist. Vibe check only, no content review. π
β¨ Tcl_CreateEncoding β making a whole new encoding β¨
Tcl_CreateEncoding defines a brand-new encoding and registers the C procedures that get called back to convert between that encoding and UTF-8. Once you make one, it's thereafter visible in the database that Tcl_GetEncoding uses. And just like with Tcl_GetEncoding, what you get back is a token representing the encoding, which you can pass into later calls to other encoding functions.
Tcl_CreateEncoding returns an encoding with a reference count of 1.
Now, the plot twist π«’ β if an encoding with that name already exists, its entry in the database gets replaced with the new one. The token for the old encoding stays valid and keeps behaving exactly as before, but anybody using the new token will now call the new encoding procedures. Two tokens, two truths. iykyk.
The typePtr argument is where you spill all the tea about the encoding's name and the procedures that convert between it and UTF-8. It's defined like this:
typedef struct Tcl_EncodingType {
const char *encodingName;
Tcl_EncodingConvertProc *toUtfProc;
Tcl_EncodingConvertProc *fromUtfProc;
Tcl_EncodingFreeProc *freeProc;
ClientData clientData;
int nullSize;
} Tcl_EncodingType;
Okay so, field by field, no cap π:
encodingNameβ the string name for the encoding, so other procedures likeTcl_GetEncodingcan refer to it.toUtfProcβ the callback procedure to invoke when converting text from this encoding into UTF-8.fromUtfProcβ the callback procedure to invoke when converting text from UTF-8 into this encoding.freeProcβ the callback to invoke when this encoding is deleted. This one may beNULL.clientDataβ an arbitrary one-word value that gets passed totoUtfProc,fromUtfProc, andfreeProcevery single time they're called. Usually it's a pointer to a data structure holding encoding-specific info the callbacks can use. Example: two extremely similar encodings likeasciiandmacRomancould share the same callback procedure but use differentclientDatavalues to steer its behavior. πnullSizeβ how many zero bytes mean end-of-string in this encoding. It must be1(for single-byte or multi-byte encodings like ASCII or Shift-JIS) or2(for double-byte encodings like Unicode).
π Heads up: constant-sized encodings with 3 or more bytes per character (such as CNS11643) are not accepted.
π¦ the callback procedures π¦
The callback procedures toUtfProc and fromUtfProc should match the type Tcl_EncodingConvertProc:
typedef int Tcl_EncodingConvertProc(
ClientData clientData,
const char *src,
int srcLen,
int flags,
Tcl_EncodingState *statePtr,
char *dst,
int dstLen,
int *srcReadPtr,
int *dstWrotePtr,
int *dstCharsPtr);
toUtfProc and fromUtfProc get called by the Tcl_ExternalToUtf or Tcl_UtfToExternal family of functions to do the actual conversion β they're the ones doing the work behind the scenes, main character energy fr fr β¨.
The clientData parameter handed to these procedures is the same as the clientData field you specified to Tcl_CreateEncoding when the encoding was created. And the remaining arguments to the callback procedures are the same as the arguments documented at the top for Tcl_ExternalToUtf or Tcl_UtfToExternal, with these two exceptions:
- If the
srcLenargument to one of those high-level functions is negative, the value passed to the callback procedure will be the appropriate encoding-specific string length ofsrc. - If any of the
srcReadPtr,dstWrotePtr, ordstCharsPtrarguments to one of the high-level functions isNULL, the corresponding value passed to the callback procedure will be a non-NULLlocation.
The callback procedure freeProc, if it's non-NULL, should match the type Tcl_EncodingFreeProc:
typedef void Tcl_EncodingFreeProc(
ClientData clientData);
This freeProc function gets called when the encoding is deleted πͺ¦. Its clientData parameter is the same as the clientData field you specified to Tcl_CreateEncoding when the encoding was created.
πΈ encoding search paths πΈ
Tcl_GetEncodingSearchPath and Tcl_SetEncodingSearchPath are called to access and set the list of filesystem directories that get searched for encoding data files.
The value returned by Tcl_GetEncodingSearchPath is the value stored by the last successful call to Tcl_SetEncodingSearchPath. If nobody ever called Tcl_SetEncodingSearchPath, Tcl computes an initial value based on the environment. And important: there is one encoding search path for the entire process, shared by all threads in the process. One path to rule them all, bestie π
Tcl_SetEncodingSearchPath stores searchPath and returns TCL_OK β unless searchPath is not a valid Tcl list, in which case you get TCL_ERROR back. π€
π Also: the elements ofsearchPathare not verified as existing readable filesystem directories. So when the search for encoding data files actually happens, any non-existent or non-readable filesystem directories on thesearchPathare silently ignored. No warnings, no drama, just vibes.
β the obsolete girlies β
Tcl_GetDefaultEncodingDir and Tcl_SetDefaultEncodingDir are obsolete interfaces, and the best move is to replace them with calls to Tcl_GetEncodingSearchPath and Tcl_SetEncodingSearchPath. π«Ά
They're called to access and set the first element of the searchPath list. And since Tcl searches searchPath for encoding data files in list order, these routines are basically establishing the βdefaultβ directory in which to find encoding data files. Cute, but deprecated β the search path functions ate and left no crumbs. π
πΎ ENCODING FILES πΎ
Okay so here's the tea, bestie: precompiling every single encoding algorithm into Tcl would eat up all the space, and honestly? Not it. So a ton of encodings just chill on disk as dynamically-loadable encoding files. And the cute part is β this also lets you make your own extra encoding files and load them with the exact same mechanism. πΈ
These files carry the info about the tables and/or escape sequences used to map between an external encoding and Unicode. And the external encoding can be made of single-byte, multi-byte, or double-byte characters. Versatile icon behavior. β¨
Each dynamically-loadable encoding is a text file. The first line, starting with a # symbol, is a comment that gives a human-readable description of the file. Then the next line says what type of encoding file it is. It can be one of these letters:
- S β a single-byte encoding, where one character is always one byte long in the encoding. Example:
iso8859-1, used by many European languages. - D β a double-byte encoding, where one character is always two bytes long in the encoding. Example:
big5, used for Chinese text. - M β a multi-byte encoding, where one character may be either one or two bytes long. Certain bytes are lead bytes, indicating that another byte must follow and that together the two bytes represent one character. Other bytes are not lead bytes and represent themselves. Example:
shiftjis, used by many Japanese computers. - E β an escape-sequence encoding, specifying that certain sequences of bytes do not represent characters, but commands that describe how following bytes should be interpreted.
The rest of the lines in the file depend on the type. π«Ά
Cases [1], [2], and [3] are collectively referred to as table-based encoding files. The lines in a table-based encoding file are in the same format as this example taken from the shiftjis encoding (and btw this is not the complete file, it's just a taste):
# Encoding file: shiftjis, multi-byte
M
003F 0 40
00
0000000100020003000400050006000700080009000A000B000C000D000E000F
0010001100120013001400150016001700180019001A001B001C001D001E001F
0020002100220023002400250026002700280029002A002B002C002D002E002F
0030003100320033003400350036003700380039003A003B003C003D003E003F
0040004100420043004400450046004700480049004A004B004C004D004E004F
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D203E007F
0080000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000FF61FF62FF63FF64FF65FF66FF67FF68FF69FF6AFF6BFF6CFF6DFF6EFF6F
FF70FF71FF72FF73FF74FF75FF76FF77FF78FF79FF7AFF7BFF7CFF7DFF7EFF7F
FF80FF81FF82FF83FF84FF85FF86FF87FF88FF89FF8AFF8BFF8CFF8DFF8EFF8F
FF90FF91FF92FF93FF94FF95FF96FF97FF98FF99FF9AFF9BFF9CFF9DFF9EFF9F
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
81
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
300030013002FF0CFF0E30FBFF1AFF1BFF1FFF01309B309C00B4FF4000A8FF3E
FFE3FF3F30FD30FE309D309E30034EDD30053006300730FC20152010FF0F005C
301C2016FF5C2026202520182019201C201DFF08FF0930143015FF3BFF3DFF5B
FF5D30083009300A300B300C300D300E300F30103011FF0B221200B100D70000
00F7FF1D2260FF1CFF1E22662267221E22342642264000B0203220332103FFE5
FF0400A200A3FF05FF03FF06FF0AFF2000A72606260525CB25CF25CE25C725C6
25A125A025B325B225BD25BC203B301221922190219121933013000000000000
000000000000000000000000000000002208220B2286228722822283222A2229
000000000000000000000000000000002227222800AC21D221D4220022030000
0000000000000000000000000000000000000000222022A52312220222072261
2252226A226B221A223D221D2235222B222C0000000000000000000000000000
212B2030266F266D266A2020202100B6000000000000000025EF000000000000
The third line of the file is three numbers. Let's break them down, because this is where it gets spicy πΆοΈ:
- The first number is the fallback character (in base 16) to use when converting from UTF-8 to this encoding.
- The second number is a
1if this file represents the encoding for a symbol font, or0otherwise. - The last number (in base 10) is how many pages of data follow.
Subsequent lines in the example above are pages that describe how to map from the encoding into 2-byte Unicode. The first line in a page identifies the page number. Following it are 256 double-byte numbers, arranged as 16 rows of 16 numbers. So given a character in the encoding, the high byte of that character is used to select which page, and the low byte of that character is used as an index to select one of the double-byte numbers in that page β and the value you get is the corresponding Unicode character. β¨
By examination of the example above, one can see that the characters 0x7E and 0x8163 in shiftjis map to 203E and 2026 in Unicode, respectively. Look at us, doing forensics ππ
Following the first page will be all the other pages, each in the same format as the first: one number identifying the page followed by 256 double-byte Unicode characters. If a character in the encoding maps to the Unicode character 0000, it means that the character does not actually exist (ghost character, no cap π»). And if all characters on a page would map to 0000, that page can be omitted entirely. Efficiency queen.
π Case [4]: the escape-sequence era π
Case [4] is the escape-sequence encoding file. The lines in an this type of file are in the same format as this example taken from the iso2022-jp encoding:
# Encoding file: iso2022-jp, escape-driven
E
init {}
final {}
iso8859-1 \x1b(B
jis0201 \x1b(J
jis0208 \x1b$@
jis0208 \x1b$B
jis0212 \x1b$(D
gb2312 \x1b$A
ksc5601 \x1b$(C
In the file, the first column represents an option and the second column is the associated value. init is a string to emit or expect before the first character is converted, while final is a string to emit or expect after the last character. All other options are names of table-based encodings; the associated value is the escape-sequence that marks that encoding. π¦
Tcl syntax is used for the values; in the above example, for instance, {} represents the empty string and \x1b represents character 27. iykyk.
π So when does Tcl actually go looking for these files?
When Tcl_GetEncoding encounters an encoding name that has not been loaded, it attempts to load an encoding file called name.enc from the encoding subdirectory of each directory that Tcl searches for its script library. π
Heads up bestie: if the encoding file exists, but is malformed, an error message will be left in interp. So likeβ¦ check your files, okay? π
π KEYWORDS π
utfencodingconvert
And that's a wrap on this whole page, my love π Slay. You just read the Tcl_GetEncoding(3tcl) page from Tcl 8.1 β go forth and encode responsibly, bestie πβ¨π¦π
π¦ where this page comes from
- debian bookworm: tcl8.6-doc 8.6.13+dfsg-2
- debian trixie: tcl8.6-doc 8.6.16+dfsg-1
- ubuntu jammy: tcl8.6-doc 8.6.12+dfsg-1build1
- ubuntu noble: tcl8.6-doc 8.6.14+dfsg-1build1
- ubuntu questing: tcl8.6-doc 8.6.17+dfsg-1
- ubuntu resolute: tcl8.6-doc 8.6.17+dfsg-1build1
- ubuntu stonking: tcl8.6-doc 8.6.18+dfsg-1
- Copyright (c) 1997-1998 Sun Microsystems, Inc
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms