I am implementing an on-chain font in Solidity. It works great, but I am not able to eliminate unnessary copies of bytes from code space (bytes constants) to the storage.
Here is the current implementation.
contract LondrinaSolid is IFontProvider {
uint constant fixed_height = 1024;
uint constant fixed_baseline = 805;
bytes constant font_0 = "\x4d\x60\xd1\x74\x05\x43\x8a\x56\x1f\x29\x56\x0b\xd2\x55\x21\x63\x50\x00\x00\x45\x62\x2e\x45\x4a\xc5\x45\xf4\x19\x45\xf5\x4b\x45\xf3\xb0\x45\xff\x65\x45\xfe\xeb\x55\x39\x2e\x56\x3b\x44\x55\x8b\x73\x65\x0c\x4c\x05\x73\xc0\x45\x02\xc5\x35\x97\x63\x50\x05\x95\x44\xfe\x21\x44\xb7\xcd\x03\x5a\x6d\x40\x75\x54\x06\x63\xfc\x54\x83\xe6\x54\x99\xc7\x54\x9a\xe0\x54\x01\xe3\x44\xf5\xd2\x44\xc9\xef\x44\xd5\xef\x44\x66\xef\x44\x66\x0b\x45\x72\x0b\x45\x3e\x40\x45\x40\x73\x50\x3d\x73\x55\x39\xf7\x05\x5a";
bytes constant font_1 = "\x4d\x50\xbc\x27\x06\x73\xf7\x54\xf4\xf5\x64\x27\xfe\x54\xc6\xfe\x54\xc6\x63\x50\x00\x00\x55\x4e\x0d\x55\x7c\x0c\x05\x73\x4e\x45\xfb\x4f\x45\xea\x05\x35\x8a\x08\x35\x61\x04\x35\xbd\xfe\x34\xaf\x63\x50\x00\x00\x45\xa1\xfa\x44\x7e\xfb\x04\x73\x9d\x54\x02\x99\x54\x01\x63\x50\x00\x00\x45\xbd\x64\x45\xac\x80\x05\x53\x09\x55\xfb\x0a\x65\x0d\x63\x50\x00\x00\x55\x1b\x14\x55\x4b\x1f\x05\x73\x46\x55\x0f\x4b\x55\x0f\x18\x45\xf9\x1c\x45\xed\x5a\x00";
bytes constant font_2 = "\x4d\x50\xd3\x57\x06\x73\x0a\x45\xa6\x18\x45\x93\x39\x45\xd6\x4b\x55\x0d\xfe\x54\x66\xcc\x54\x81\xb4\x54\x33\x7f\x54\x53\xb6\x54\x3b\xb2\x54\x41\xe8\x54\x24\xe3\x54\x4f\xf6\x54\xbb\xf5\x54\xc3\x63\x50\x00\x00\x55\x0f\x04\x55\x1e\x05\x05\x73\xbd\x45\xfb\xd4\x45\xfb\xee\x45\xfd\xf6\x45\xfd\x08\x45\xfa\x08\x45\xf7\xfd\x44\xb7\xff\x44\xab\xfd\x44\xcc\xf6\x44\xbc\x63\x50\x00\x00\x45\x04\x06\x35\xe9\x02\x55\x00\x00\x55\x23\xb5\x54\x48\x9f\x04\x73\x99\x45\xb4\xb9\x45\x90\x31\x45\xc5\x31\x45\x7f\xfc\x44\x56\xa4\x44\x14\xf2\x43\xe0\xc0\x53\x02\x53\x40\xf6\xbb\x55\x15\x58\x06\x63\x00\x55\x00\x39\x55\x15\xbe\x45\xff\x5a\x00";
...
bytes constant font_X = "\x4d\x50\x09\x35\x05\x73\x40\x55\x93\x4f\x55\xc7\x2f\x55\x94\x2e\x55\xad\xce\x54\xaa\xc8\x54\xbe\xcd\x54\xa5\xc9\x54\xad\x63\x50\x00\x00\x55\x28\x10\x55\x60\x0e\x05\x73\x50\x45\xfd\x50\x45\xfd\x63\x50\x00\x00\x55\x1e\xaf\x54\x25\x98\x04\x73\x1f\x45\x96\x1f\x45\x96\x63\x50\x00\x00\x55\x1c\x6a\x55\x20\x7a\x05\x73\x13\x55\x48\x19\x55\x51\x63\x50\x00\x00\x55\x30\x0e\x55\x5d\x09\x05\x73\x52\x55\x01\x57\x55\x02\x63\x50\x00\x00\x45\xe5\x9f\x44\xd8\x6e\x04\x73\xc5\x44\x3a\xc2\x44\x1d\x63\x50\x00\x00\x55\x34\x52\x54\x41\x29\x04\x73\x34\x45\x51\x34\x45\x51\x63\x50\x00\x00\x45\xb3\x01\x45\xa2\x01\x05\x73\xa1\x54\x03\x99\x54\x04\x63\x50\x00\x00\x45\xca\xc9\x45\xc5\xd0\x55\x00\x00\x45\xe3\xa5\x44\xe0\x93\x04\x73\xe8\x44\xa0\xe8\x44\xa0\x63\x50\x00\x00\x45\x76\xff\x44\x3b\x05\x05\x5a";
bytes constant font_Y = "\x4d\x50\x17\x30\x05\x73\x19\x55\x59\x30\x55\x8d\x57\x55\xdc\x57\x55\xe8\x63\x50\x00\x00\x45\xc5\x9e\x45\xbd\xb1\x05\x53\x12\x85\x1d\x10\x85\x22\x63\x50\x00\x00\x55\x42\x04\x55\x64\x03\x05\x73\x4a\x45\xfd\x58\x55\x01\x63\x50\x00\x00\x55\x3f\x56\x54\x55\x2e\x04\x53\xb6\x66\x15\xcb\x56\xe0\x73\x50\x39\x5f\x54\x3a\x51\x04\x63\x00\x55\x00\xb2\x44\xf2\x4c\x54\x07\x00\x55\x00\xbb\x54\xc7\xb9\x54\xcc\x00\x55\x00\xbe\x44\x48\xbc\x44\x34\x00\x55\x00\xeb\x44\xee\xab\x44\xf3\x73\x40\xc0\x08\x45\xa5\x07\x05\x5a";
bytes constant font_Z = "\x4d\x50\x0b\x33\x05\x73\x0a\x55\x33\x09\x55\x4f\x03\x55\x4b\x0d\x55\x54\x63\x50\x00\x00\x55\xeb\x06\x55\xfc\x0c\x55\x00\x00\x45\x8a\xa5\x45\x7a\xb9\x05\x53\x26\x75\x4a\x19\x75\x73\x63\x50\x00\x00\x55\x01\x44\x55\x01\x5c\x05\x73\x04\x55\x4e\x0c\x55\x58\x63\x50\x00\x00\x55\xab\xf8\x54\xcf\xf9\x04\x73\xdb\x55\x03\x06\x56\x07\x63\x50\x00\x00\x55\x0e\x6b\x54\x02\x46\x54\x00\x00\x45\x0c\xff\x34\xf6\x08\x55\x00\x00\x55\x92\x2d\x54\xa4\x14\x04\x73\x6a\x45\x64\x6d\x45\x59\x01\x45\xad\x02\x45\x9d\xff\x44\xb5\xfc\x44\xb0\x63\x50\x00\x00\x45\x35\xfd\x44\x12\xfc\x04\x73\x3f\x54\x01\xf7\x53\x09\x5a\x00";
mapping(uint => bytes) fontData;
mapping(uint => uint) widths;
constructor() {
registerAll();
}
function _register(string memory _char, bytes memory _bytecode, uint _width) internal {
uint key = uint(uint8(bytes(_char)[0]));
if (_bytecode.length > 0) {
fontData[key] = _bytecode;
}
widths[key] = _width;
}
function registerAll() external onlyOwner {
_register("0", font_0, 554);
_register("1", font_1, 414);
_register("2", font_2, 540);
...
_register("X", font_X, 531);
_register("Y", font_Y, 531);
_register("Z", font_Z, 531);
}
function height() external pure override returns(uint) {
return fixed_height;
}
function baseline() external pure override returns(uint) {
return fixed_baseline;
}
function widthOf(string memory _char) external view override returns(uint) {
uint key = uint(uint8(bytes(_char)[0]));
return widths[key];
}
function pathOf(string memory _char) external view override returns(bytes memory) {
uint key = uint(uint8(bytes(_char)[0]));
return fontData[key];
}
}
The problem is in the _register method, which stores the mapping between the charactor and the vector data in the "fontData". Even thought the font data itself is already in this smart contract, it copies it to the "memory" space when calling this method and copies it into the "storage", which is quite expensive (12M units of gas).
One possible work around is to eliminate the _register method, and implement a binary search in pathOf method to directly return font_X, but that code is ugly and not scalable.
Ideally, I just want to store the references to those font data in "fontData" and return them in pathOf method.
I'd appreciate any suggestions, including writing some assembly solution (which I am comfortable with).