Closed Yardie- closed 1 year ago
Indeed, given future improvements such as rotation and error checking, the class needs to know the size of the framebuffer. This requires changing the API arguments, so I think it's better to change the API alone first for future improvement.
In addition, I will add an argument in the direction of the bit arrangement of the frame buffer to expand the support for the frame buffer format.
In my opinion, the changes below are appropriate, but what about?
void truetypeClass::setFramebuffer(uint16_t _framebufferWidth, uint16_t _framebuffer_bit, uint8_t *_framebuffer)
↓
void truetypeClass::setFramebuffer(uint16_t _framebufferWidth, uint16_t _framebufferHeight, uint16_t _framebuffer_bit, uint8_t _framebufferDirection, uint8_t *_framebuffer)
I'm sorry to bother you to change the API, but I think this is necessary. Or if you have any other solutions, please let me know.
No I totally agree. I thought about this and looked at my old code. and that is basically how I have ended up doing it
It doesn't worry me that you make any changes especially when they will make the whole project so much better.
here's something I just made to explain it.
The calls I make are
struct Image_Canvas canvas;
uint8_t image[EPD_BYTES];
canvas_make(&canvas, image, EPD_BYTES, EPD_WIDTH, EPD_HEIGHT, 0, 0xff);
canvas.font_scale = 0.3;
canvas_rotation_set(&canvas,0);
canvas_draw_paragraph(&canvas, string, start_x , start_y, vertical_padding, 0x00, DRAW_STYLE_CENTRED);
so it's pretty much the same as you are creating. Mine will run on an ATtiny3216 But yours does TTF and that's the clincher.
Wow, that's a great rotation. The feeling of proceeding with the implementation has increased!
As a confirmation, the coordinate transformation of rotation is based on your code below, isn't it? https://github.com/k-omura/truetype_Arduino/issues/6#issue-875863321
Looking at your rotation, I noticed that I need to change the arguments and thinking of the setStringWidth method. Need to set the upper side of the y coordinate as an argument. Also, now the concept of start and end will change to the concept of range. I will proceed with the implementation together with these!
Thanks Greg :)
My pleasure. The code in #6 produced the above image it is the real output of that. It is s screen grab of the output of print_buffer. I think the image is a good way to explain rotation. By the way I prefer rotation as it's more like turning a page rather than turning yourself. You are more than welcome to use it for explanation.
While you are working out the API why not put rotation as a variable in the string method like void string(uint16_t _x, uint16_t _y, const String _string, uint8_t rotation=5); Then in the string method if(rotation==5) use the default rotation. Otherwise use that rotation but don't change the default. then setRotation changes the default
to my mind that is the clearest way to implement it and also explain what is happening. The constructor of the class doesn't need to set it. I would start at zero. Then if it is set by set
also changing the name of string to something like stringDraw might make it clearer.
I don't think you need to change setStringWidth( as it really only needs a length for what it does. Actually It could just be setStringWidth( uint16_t characters_per_line=0xFFFF, bool line_break_on_word=false ) then running off the page is dealt with in addPixel
or simply add those two variables to string then it doesn't need to be implemented at all. after all it is only relevant to that particular string not the buffer.
for line_break_on word have a simple routine that checks does a look back and look ahead line_break_on_word is really a wish list thing and though and could easily be implemented later
The cool thing about using the rotation in addPixel ALL the other code doesn't need to deal with.
All other code just references the top left corner of the character/word/sentence/paragraph relative to the current rotation. Not the actual co-ordinates on the frame buffer. addPixel does that.
I hope that helps. It took me a long time to get my head around this in a real way.
By implementing my ideas in your code it means that all the work I have done on my code is not wasted. I have been working to a point where I can help you. I just didn't know it at the time. The end result is I actually get what I wanted in the first place. multiple scalable variable width TTF fonts on an epaper with a minimal library.
Now that! is so cool.
I just re-read your README implementation of Align text to the right. Do you mean writing right to left for Japanese Arabic Chinese siht ekil I think that could be implemented with the same API as would be required by centring. just a flag. So you could mix fonts and r-l l-r sentences onto the same buffer.
When you go back to work I will start working on my fork and you can see where I get. But for now have fun. It will always be your code and your project.
After thinking about the right to left I think direction is actually better than rotation. Thus there are 16 possibilities with centring as the 3rd bit this can be delt with like int with the high bit as the direction flag then the first two as rotation
0 left to right = 0b00000000 0 right to left = 0b10000000 90 left to right = 0b000000001 90 right to left = 0b10000001 180 left to right = 0b00000010 180 right to left = 0b10000010 270 left to right = 0b00000011 270 right to left = 0b10000011 or direction & 0b00000000 == 0 rotation direction & 0b00000010 == 90 degrees rotation direction & 0b00000001 == 180 degrees rotation direction & 0b00000011 == 270 degrees rotation direction & 0b00000100 == centred direction & 0b10000000 == right to left
that is really clean
string advances or reduces x on the basis of direction & 0b10000000
addPixel rotates on the basis of direction & 0b00000000 == 0 rotation direction & 0b00000010 == 90 degrees rotation direction & 0b00000001 == 180 degrees rotation direction & 0b00000011 == 270 degrees rotation
rotateText (new_rotation) mask = direction & 0b11111100; new_rotation &= 0b00000011; direction = mask | new_rotation
haven't tested that but it should work
on further reflection an API change I would prefer is
string becomes textDraw setStringWidth becomes textWidthMaximum or textWidthMax setStringColor becomes setTextColor
also add add
setTextDirection
setTextLineSpacing
// this is an int to allow negatives and defines the amount the carriage return moves down each new line
// the character height is included then this is added/subtracted from this
this would be even clearer in the use of the class
text can be either a character a sentence or paragraph and it is actually what we are dealing with here string is really a programmers concept so people new to coding may have trouble with it. https://en.wiktionary.org/wiki/text
Sorry for changing my advise but I think this will lead to a cleaner and clearer robust code base. Easier adoption and less support problems. As with all libraries change is inevitable. I don't think anyone else is using this at the moment so now is the time. Anyone who returns should be able to adapt your old code base to this fairly easily.
If we get it clean now you wont be bothered by confused users in the future and it can stay a fun project for both of us. Simple clean API = 0 support requirements
here is the complete working code for my c file function ( I can send the whole code if you like but this is the relevant part ) the scaling and rotation are handled here I think it is fairly self explanatory.
enum _Rotations
{
Rotate_0 = 0,
Rotate_90,
Rotate_180,
Rotate_270,
};
struct Image_Canvas
{
uint8_t * image;
uint16_t width_in_pixels;
uint16_t height_in_pixels;
uint32_t bytes; // max image size
uint16_t rotation;
uint8_t bit_type;
double font_scale;
uint8_t bg_colour;
};
bool canvas_pixel_set( struct Image_Canvas * canvas, int16_t page_x, int16_t page_y, bool colour_code)
{
// translate x and y
page_x = int16_t((double) page_x * canvas->font_scale);
page_y = int16_t((double) page_y * canvas->font_scale);
int16_t temp = page_x;
int16_t canvas_x, canvas_y;
if( canvas->rotation == Rotate_90 )
{
canvas_x = canvas->width_in_pixels -1 - page_y ;
canvas_y = temp;
}
else if( canvas->rotation == Rotate_180 )
{
canvas_x = canvas->width_in_pixels -1 - page_x;
canvas_y = canvas->height_in_pixels -1 - page_y;
}
else if( canvas->rotation == Rotate_270 )
{
canvas_x = page_y ;
canvas_y = canvas->height_in_pixels -1 - temp ;
}
else
{
canvas_y = page_y;
canvas_x = page_x;
}
// now all is rotated test out of bounds
// force to range to bit wise 0-7 or 0 to boundary number - 1
if (canvas_x < 0 || (uint16_t)canvas_x >= canvas->width_in_pixels || canvas_y < 0 || (uint16_t) canvas_y >= canvas->height_in_pixels)
return false;
// the code below is for 1 bit per pixel this is the only part that needs to change for the other resolutions 4 and 8
// as I now have a lillygo and an inkscape6 and it will help the ttf lib I will work on that in the next few days
uint32_t byte_to_change = (canvas_x + canvas_y * canvas->width_in_pixels) / 8;
uint16_t col = canvas_x / 8;
uint8_t bit_mask = 1 << (7 - (canvas_x % 8)); // draw pixel left to right
if(colour_code)
canvas->image[byte_to_change] &= ~bit_mask;//0x00;//unset the bit
else
canvas->image[byte_to_change] |= bit_mask ;// = 0xFF;//&= ~ bit_mask;
return true;
}
Thank you for a lot of information! I haven't caught up with everything yet, but it looks very useful.
I just re-read your README implementation of Align text to the right. Do you mean writing right to left for Japanese Arabic Chinese siht ekil
Regarding text alignment, I did not intend to arrange the characters from the right, but I intended to align them to the right. (In Japanese, it is rare to write from the right in modern times) The source of the idea comes from CSS. https://www.w3schools.com/cssref/pr_text_text-align.ASP
Arranging letters from the right (typically Arabic) isn't a high priority for me, but if it's easy to see your idea, you might try implementing it!
Thank you as always!
Yes it is clear I need to find my cpp hat and start coding cpp. If you spend the rest of your holiday coding the parts you enjoy. Then when you have to go back to work I will pick up the ball. I am semi retired and have a fair bit of available time to code. Also I have some specific requirements for my real project which this will become a part of. I finally have a better understanding of git and so I will keep my git up to date. I have had some major variation ideas. I would like to make this compatible in esp-idf and pi so no String references. also I have a small simple graphics lib circles squares buttons I would like to hook in to the framework I have had a very oop idea which I will attempt to implement. ie cpp template uint8_t_image which frame_buffer implements ttfClass extends frame_buffer then either ttfClasArduino extends ttfClass adding String related code functions or ttfClass has #ifdef Arduino guards so the code will only be available in Arduino
the second option is probably the easier and less confusing way to go
simple_images extends frame_buffer etc. then output_des simply includes the ttfClass and or simple_image
the API will basically stay the same but each part deals specifically with it's own individual issues only easier to debug
I will play with the right to left when I finally get my head around the core.
Have fun Greg
I have added rotation to my repo and modified setTextWidth to setTextBoundary I haven't had a chance to test it yet but it compiles. It's late now so I'm going to bed.
I ported your rotation code and tried it. It worked perfectly and was impressed. I also understood the idea of the coordinate system when rotating. I could clearly understand why I didn't have to add arguments to setTextBoundary.
Only one thing to share, You commented out the if statement for this->framebufferDirection inside setFramebuffer and addPixel, but here it was intended to set the framebuffer format type. (Readme framebuffer format section) It does not deny your ideas about text rotation, alignment, and direction. It's just my intended use of variables. It's confusing because there are several directions of meaning.
good night!
Yes it is surprisingly simple. Once you get your head around what is actually happening.
I'm glad you like it and it still works. I still haven't had a chance to test it. I generated a pull request which you can ignore or implement. That's up to you. I tried to stay within your coding style.
The reason I just commented them out was to show that they are not required for this functionality. Basically leaving the setTextBoundary as it is means that right until the addPixel we are thinking in one direction. It simplfies the code and concepts. The next step was to use the textBoundaries as a sort of window for you text in the same rotation as the text. Centring and word wrap then work within that window.
Here's an image to show what I mean. The blue line (boundary of "0") is the same boundary as the green one (boundary of "90"). the textDraw then uses that window to describe it's world. if the text goes outside the window it returns to the start of the window right or left depending on the textDirecton Maintaining your idea of a window rather than just a length means that the draw text draw is also vertically restricted with in that window. This would allow scrolling if someone wanted to go that far. The code could simply truncate the string that was printed and the user could compare what was sent to what came back. and send that part later like the token in strtok
ALL the rest of the code can pretty much ignore rotation as they reference off 0x and 0y which is then translated in the rotation section In the image rotation 0x and 0y are moved to the appropriate corner. This part took me a long time to get my head around. I kept talking myself out of the obvious answer. It can't be that easy. That is where the serial print version of the code really helped.
I have added a branch to my repo called minor mods. It contains minor changes that I think are worth adding I will try and keep it close to your code. With simple suggestions of working code. I know I changed a lot over the last day or two. Each commit will have a simple reason.
Hi K If you have a moment could you please answer a question.
in the lines below
result.advanceWidth = (result.advanceWidth * this->characterSize) / (this->yMax - this->yMin);
result.leftSideBearing = (result.leftSideBearing * this->characterSize) / (this->yMax - this->yMin);
what is the point of the / (this->yMax - this->yMin) I am clearly miss understanding something here.
I have been working on getStringWidth with a model that should make it easy to break on word or break on \n or \r This would not change the api but may allow an additional method paragraphDraw(String str, bool breakOnWord) where you feed a string and it does carriage returns when appropriate without affecting your code or API. I realise that this is not a priority for you and as such I thought it would be a good job for me as it is something I would like. without affecting your code.
Thanks Greg
When drawing the glyph information read from the TTF in the frame buffer, it means scaling to match the size of the character specified by the user.
Scaling is the following equation.
this->charactersize / (this->ymax - this->ymin)
In the calculation, in many cases (this->characterSize) <(this->yMax - this->yMin)
, it will be caused by 0(int operation) to result.advanceWidth or result.leftSideBearing, Explicitly, the denominer will increase result.advanceWidth, result.leftSideBearing and this->characterSize ahead.
Anyway, this calculation means scaling from the TTF to the character size specified by the user.
I am very grateful to work on improving GetStringWidth. Certainly, now this is not so high priority for me, and I'm thinking of working on 'Compound Glyphs'.
Speaking of which, it is very interesting to be called by one alphabet. It seems to appear in 007 :) It is not a genius like Q
Compound glyphs would be very very cool.
I actually think you were heading in an interesting direction with the first string width idea. I have re-implemented your struct with a couple of variations. I will get something going in the next few days and put it up on the yardie thread.
the glyph information read from the TTF I understand now these values are read from the ttf itself not the buffer or the window. the this-> confused me.
Hi K Here is what I am working towards
void truetypeClass::paragraphDraw(int16_t _x, int16_t _y, int16_t _v_padding, uint8_t _justification, bool _break_on_nl, const wchar_t _character[]){
/* concept
* this takes a string and finds how many words fit in the boundary
* it then justifies that line according to _justification
* 0: left
* 1: centre
* 2: right
* and writes that to the buffer.
* it moves down the font height + v_padding ( which can be negative )
* and repeats the process until either the end of the string
* or the bottom of the boundary is reached.
*
* if the string is incomplete it returns the position in the string
* where the last character drawn occurred. So your code could
* show the rest of the text when you send it again from that point.
* This opens many possibilities.
*
* Line breaks.
* if _break_on_nl is true.
* The line will break and increment.
* The escape characters \v or \f are treated as paragraph breaks
* and as such will always break the line and move down.
* These are historic characters that are rarely used but still valid
* and as such are available to format your text easily.
* Simply insert them at the appropriate points in your text.
*
*/
}
Speaking of which, it is very interesting to be called by one alphabet. It seems to appear in 007 :) It is not a genius like Q
Or like K in the movie "Men in Black" Maybe you haven't seen it.
also a modification to
typedef struct { uint16_t width; // width of string up to break point uint16_t lastCharPoint; // last string position on break allows break string on width overrun and carriage return uint16_t lastSpacePoint; // last string position on space character allows break string on word boundary } ttStringWidth_t ;
ttStringWidth_t truetypeClass::getStringWidth(const wchar_t _character[], int16_t _current_x, bool _break_on_return=false, bool _break_on_word=false ){
the paragraph stuff could be simply in an example rather than the core to keep the core tight.
But it is something I want and so as long as the core handles it I am happy.
I think it's good. I thought about some of the structures of the structure, but couldn't think of a good one. With this, it seems that you can measure the width of the string without doing complicated processing! thanks!
Ok after quite a bit of testing I have realised that lastCharPoint and lastSpacePoint are problematic as the type of string we are dealing with varies. So these issues like centering and right justification are actually outside the scope of the class. I will revert back to your version where getStringWidth returns a uint16_t equal to the width in pixels the string would use, and that is all it does. I will write an example of how to centre or right justify the output. It will keep your code tighter and simpler to maintain.
We simply need to have two regions. framebufferBoundary width_in_pixels or bytes, height_in_pixels and bits_per_byte ---- This is relative to the array and textBoundary start_x start_y width_in_pixels and height_in_pixels ----- This is relative to the current rotation.
if draw accepts negatives and a start_x start_y as it now does true centering is possible.
if addPixel only draws anything inside the framebufferBoundary and textBoundary it stays clean and doesn't break.
This keeps it simple and flexible.
Hi K
At your leisure have a look at my "altenate" branch.
I have made a few changes without really changing your code a lot.
Hopefully it will be more esp-idf and pi compatible but I haven't tested that yet.
The input strings should be more compatible and robust.
I have added one public variable breakLine.
Which defaults to work as you have planned however allows printing text on one line without line breaks.
It relies on the addPixel just not drawing the out of bounds characters.
Over the next few weeks I will work on that branch.
I have added an example of justify.
Centre or right align the text simply using only the class.
Also I will test it in esp-idf and add an example for that.
I now have something I can work with and can move on with my stuff.
Also you can get back to casually improving the core.
Thanks heaps for your help.
Hi Greg
The challenge of the width of the string is very difficult. I have not had a definitive idea yet.
When I think in implementing various ideas, what I think is not dependent on the framebuffer format as much as possible. In other words, it is the simplest way that the frame buffer operation is integrated only in the addPixel
Unfortunately, my holiday ended. I have fewer time I can touch this code. I am grateful for your various activities and I am confirmed as much as possible.
Thank you.
That is fine. I now have fully functional code and will modify my "alternate" branch so that when you have holidays again next year you can come back and have a look. Thank you K.
Enjoy. :) Greg ps. the results so far an e-paper clock that looks like a post it note
Sorry I just noticed something that could be a major problem. The clas does not actually know how large the frame buffer is. I was trying to hack out a rotation algo and without a height or frame buffer size it's not possible.
Also the code could easily run off the end. But with it most of the buffer checks can easily be done in the addPixel method. Rotation is also easy. Also if you do implement rotation I would suggest you call it either characterRotation stringRotation or pageRotation so it is clear to the user that the buffer itself is not rotating just the stuff inside it.
Sorry to interrupt your holiday even more.