TCPDF: Clip text to Cell width - width

I am generating PDF report using TCPDF's Cell method extensively. Text printed with Cell method spills beyond width specified in method. I want to print only as much part of the text that fits in the specified width but not to spill beyond or wrap to next line. I do not want font stretch strategy.
I searched a lot but could not find a solution. Is there any other method/way to handle this?
(I used setfillcolor(255) to achieve the visual effect. But the text is still there, invisible; gets revealed when you try to select.)
here is my part of code.
$pdf->SetFillColor(255); // only visual effect
$pdf->Cell(36, 0, "A very big text in the first column, getting printed in 3.6cm width", 0, 0, 'L', true);
$pdf->Cell(20, 0, "Data 1", 0, 0, 'L', true);
$pdf->Cell(20, 0, "Data 2", 0, 0, 'L', true);
Thanks a lot.

I have found an answer here by Nicola Asuni, who is the main TCPDF author. The following code, provided by user fenstra, is working for me:
// Start clipping.
$pdf->StartTransform();
// Draw clipping rectangle to match html cell.
$pdf->Rect($x, $y, $w, $h, 'CNZ');
// Output html.
$pdf->writeHTMLCell($w, $h, $x, $y, $html);
// Stop clipping.
$pdf->StopTransform();
As far as I can tell, the clipping rectangle won't consider any padding on the displayed text, so you apply the proper math to Rect's width and height if you need to mimic the behaviour of a MultiCell on this particular.

For this one I created it with cell and make function on it to call it globally
* clip Text
*
* #param float $w width
* #param float $h height
* #param float $x x-axis
* #param float $y y-axis
* #param string $str text
*
* #return void
*/
private function clipText($w, $h, $x, $y, $str)
{
// Start clipping.
$this->Pdf->StartTransform();
// Draw clipping rectangle to match cell.
$this->Pdf->Rect($x, $y, $w, $h, 'CNZ');
// Output text.
$this->Pdf->SetXY($x, $y);
$this->Pdf->Cell($w, $h, $str, 0, 0, 'L', 0, 0);
// Stop clipping.
$this->Pdf->StopTransform();
}

Related

Constructing a Line in the Vertex Shader - Removing Perspective Scaling

I'm trying to implement a line renderer that expands the line vertices in the vertex shader, so that they expand in screen space, so that all segments of lines are exactly the same size, regardless of how far they are from the camera, each other, or the origin.
I first tried implementing my own version, but could not seem to cancel out the perspective scaling that seems to happen automatically in the graphics pipeline. I then adapted some components from this website: https://mattdesl.svbtle.com/drawing-lines-is-hard (see Screen-Space Projected Lines section). See their full vertex shader here: https://github.com/mattdesl/webgl-lines/blob/master/projected/vert.glsl
In my version, the c++ code uploads two vertices for each end of the line, along with a direction vector pointing from line point A to B, and a scalar sign (-1 or +1) used to expand the line in opposite perpendicular directions, to give it thickness. The vertex shader then constructs two screen space coordinates, generates a screen space direction, then generates a perpendicular direction (using the signed scalar) from that.
In the website's code, they upload 3 positions (prev, cur, next) - I believe so that they can generate joints. But in my case, I just want a simple segment, so I upload the current position, along with a world-space direction to the next position (all vertices of a segment get the same world space line direction). Then in my vertex shader, I construct the "next world position" by adding the world line direction to the current world/vertex position, then transform both into screen space. I probably could have just transformed the world space direction into screen space, but I'm currently trying to rule out all sources of unknowns.
Here is the code I have so far. I believe I've transformed and scaled my vectors just as they have, but my lines are still scaling as they change depths. I'm not sure if I've missed something from the web-page, or if this is the result they were after. But since they are dividing their projected xy coordinates by their projected w coordinate, it sure seems like they were trying to cancel out the scaling.
The closest I've came to achieving the result I want (constant thickness) was to override the w component of all projected positions with the Scene.ViewProj[3][3] component. It almost seemed to work that way, but there was still some strange scaling when the view was rotated. Anyway, here is the code trying to emulate the logic from the website. Any advice on how to make this work would be very much appreciated:
struct sxattrScene
{
float4x4 Eye; // world space transform of the camera
float4x4 View; // view transform - inverted camera
float4x4 Proj; // projection transform for camera perspective/FOV
float4x4 ViewProj; // view * projection transform for camera
float4x4 Screen; // screen projection transform for 2D blitting
float2 Display; // size of display
float Aspect; // aspect ratio of display sizes
float TimeStep; // time that advanced from last frame to this one, in milliseconds
};
ConstantBuffer<sxattrScene> Scene; // constant buffer scene
// input vertex
struct vinBake
{
// mesh inputs
float4 Position : ATTRIB0; // world position of the center of the line (2 verts at each end)
float4 Color : ATTRIB1; // color channels
float3 TexCoord : ATTRIB2; // x=sign, y=thickness, z=feather
// enhanced logic
float4 Prop : ATTRIB3; // xyz contains direction of line (from end points A -> B)
float4 Attr : ATTRIB4; // not used here
};
// 3D line drawing interpolator
struct lerpLine3D
{
float4 ClipPos : SV_POSITION; // projected clip-space screen position of vertex
float4 Diffuse : COLOR0; // diffuse color
float3 ScrPos : TEXCOORD0; // screen-space position of this point
float Factor : TEXCOORD1; // factor value of this position (0->1)
float Feather : TEXCOORD2; // falloff of line
};
// vertex shader
lerpLine3D vs(vinBake vin)
{
// prepare output
lerpLine3D lerp;
// float ww = Scene.ViewProj[3][3];
// generate projected screen position
lerp.ClipPos = mul( Scene.ViewProj, float4( vin.Position.xyz, 1.0) );
// generate a fake "next position" using the line direction, then transform into screen space
float4 next_proj = mul( Scene.ViewProj, float4( vin.Position.xyz + vin.Prop.xyz, 1.0) );
// remove perspect from both positions
float2 curr_screen = lerp.ClipPos.xy / lerp.ClipPos.w;
float2 next_screen = next_proj.xy / next_proj.w;
// correct for aspect ratio
curr_screen.x *= Scene.Aspect;
next_screen.x *= Scene.Aspect;
// generate a direction between these two screen positions
float2 dir = normalize( next_screen - curr_screen );
// extract sign direction .. -1 (neg side) to +1 (pos side)
float sign = vin.TexCoord.x;
// extract line size
float thickness = vin.TexCoord.y;
// extract alpha falloff (used in pixel shader)
lerp.Feather = vin.TexCoord.z;
// remap sign (-1 to +1) into line factor (0 to 1) - used in ps
lerp.Factor = ( sign + 1.0 ) * 0.5;
// compute our expanse, defining how far to push our line vertices out from the starting center point
float expanse = thickness * sign;
// compute our offset vector
float4 offset = float4( -dir.y * expanse / Scene.Aspect, dir.x * expanse, 0.0, 1.0 );
// push our projected position by this offset
lerp.ClipPos += offset;
// copy diffuse color
lerp.Diffuse = vin.Color;
// return lerp data
return lerp;
}
// compute a slope for the alpha falloff of a line draw
float ComputeLineAlpha(float t,float feather)
{
// slope feather to make it more useful
float ft = 1.0 - feather;
float ft4 = ft*ft*ft*ft;
// compute slope
return min( 1.0, t * 40.0 * ( 1.0 - t ) * ( 0.1 + ft4 ) );
}
// pixel shader
float4 ps(lerpLine3D lerp) : SV_TARGET
{
// compute line slope alpha
float alpha = ComputeLineAlpha( lerp.Factor, lerp.Feather );
// return the finished color while scaling the curve with alpha
return float4( lerp.Diffuse.rgb, lerp.Diffuse.a * alpha );
}
Edit:
I think I'm really close to figuring this out. I have things setup so that the lines are scaled correctly as long as all parts of a visible line are in front of the camera. Here is the updated vertex shader code, which is simpler than before:
lerpLine3D main(vinBake vin)
{
// prepare output
lerpLine3D lerp;
// generate projected screen position
lerp.ClipPos = mul( Scene.ViewProj, float4( vin.Position.xyz, 1.0 ) );
// generate fake clip-space point in the direction of the line
// + vin.Prop.xyz contains the world space direction of the line itself (A->B)
float4 next_proj = mul( Scene.ViewProj, float4( vin.Position.xyz + vin.Prop.xyz, 1.0 ) );
// generate a directiion between these two screen positions
float2 dir = normalize( next_proj.xy - lerp.ClipPos.xy );
// extract sign direction .. -1 (neg side) to +1 (pos side)
float sign = vin.TexCoord.x;
// extract line size from input
float thickness = vin.TexCoord.y;
// extract alpha falloff from input
lerp.Feather = vin.TexCoord.z;
// remap sign (-1 to +1) into line factor (0 to 1)
lerp.Factor = ( sign + 1.0 ) * 0.5;
// compute our expanse, defining how far to push our line vertices out from the starting center point
float expanse = thickness * sign;
// compute our offset vector
float2 offset = float2( -dir.y * expanse, dir.x * expanse * Scene.Aspect );
lerp.ClipPos.xy += offset * abs( lerp.ClipPos.w * 0.001 ); // <----- important part
// copy diffuse color
lerp.Diffuse = vin.Color;
// return lerp data
return lerp;
}
However, there is one serious problem I could use some help with, if anyone knows how to pull it off. Notice the updated code above that has the "important part" comment. The reason I placed an abs() here is because sometimes the end-points of a single line segment can cross through the camera/screen plane. In fact, this is pretty common, when drawing long lines, such as for a grid.
Also notice the 0.001 on that same line, which is an arbitrary number that I plugged in to make the scale similar to pixel scaling. But I'm pretty sure there is an exact way to calculate this scaling that will take things into account, such as lines crossing the screen plane.
The updated code above seems to work really well as long as both ends of the line segment are in front of the camera. But when one end is behind the camera, the line is expanded incorrectly. My understanding of the w component and perspective scaling is very limited, beyond knowing that things that are further away are smaller. The w component seems to be heavily derived from the 'z'/depth component after transforming into clip space, but I'm not sure what its min/max range would be under normal 3D circumstances. I'm wondering if just having the correct scaler in that line of code might fix the problem - something like this:
lerp.ClipPos.xy += offset * ((lerp.ClipPos.w-MIN_W_VALUE)/ENTIRE_W_RANGE);
But I'm honestly not familiar with these concepts enough to figure this out. Would anyone be able to point me in the right direction?
Edit: Well, in my engine at least, the w component seems to literally just be world-space depth, relative to the camera. So if something is 100 units in front of the camera, its w value will be 100. And if -100 units behind the camera, then it will be -100. Unfortunately, that seems like it would then have no range to lock it into. So its possible I'm going about this the wrong way. Anyway, would really appreciate any advice.

Explaining coordinate's calculation with map()

Could someone explain the meaning of this value/position: 300-400/2+10. I know it makes that the red circle won't go out of the square but I don't really understand the calculation..? Is there a page where I can read how it works because I personally would do it like this
float redCircle = map(mouseX,0,width,116,485);
circle(redCircle,map(mouseY,0,height,114,485),20);
with one number positions and not a calculation like in the code. I tried to understand it but I don't. I would really appreciate it if someone could explain the proceed.
void setup() {
size(600, 600);
surface.setTitle("Mapping");
surface.setLocation(CENTER, CENTER);
}
void draw() {
background(0);
stroke(255);
fill(255, 255, 255);//weißer Kreis
circle(mouseX, mouseY, 20);
mouseMoved();
text("Maus X/Y:"+mouseX+"/"+mouseY, 250, 300); //Text für weiße Position
fill(255, 0, 0); //Roter Kreis
float posFromMouseX = map(mouseX, 0, width, 300-400/2+10, 300-400/2+400-10);
float posFromMouseY = map(mouseY, 0, height, 300-400/2+10, 300-400/2+400-10);
ellipse(posFromMouseX, posFromMouseY, 20, 20);
text("map to: "+posFromMouseX+" / "+posFromMouseY, 255, 320); //Text für rote Position
// Transparentes Rechteck in der Mitte
noFill();
rect(300-400/2, 300-400/2, 400, 400);
}
map() will adjust the scale of a number accordingly to a range.
For an example, if you have these values:
MouseX: 200
width: 1000
You can easily calculate that, if the screen had a width of 2000 your mouse X position would need to be 400 to be proportional.
But this is an easy example. In the code you pasted here, the same thing is happening, but the coordinates compared are:
The whole window
The white rectangle
The map() function takes 5 args:
map(value, start1, stop1, start2, stop2)
value: float: the incoming value to be converted
start1: float: lower bound of the value's current range
stop1: float: upper bound of the value's current range
start2: float: lower bound of the value's target range
stop2: float: upper bound of the value's target range
So... you can totally write this line without the calculations:
float posFromMouseX = map(mouseX, 0, width, 110, 300-400/2+400-10);
// is the same thing than:
float posFromMouseX = map(mouseX, 0, width, 110, 490);
The probables reasons to write it that way are:
The author may not have wanted to do the simple math
The author may want to know where these numbers come from later (seeing how they were calculated would help on this front)
The author may want to change the hardcoded numbers for variables and make his white rectangle's size dynamic later
Hope it makes sense to you. Have fun!

HTML5 Canvas: Rendering aliased text

When you render text on an HTML5 canvas (using the fillText command, for example), the text will render anti-aliased, meaning the text looks smoother. The downside is that it becomes very noticable when trying to render small text or specifically non-aliased fonts (such as Terminal). Because of this, what I want to do is render text aliased, rather than anti-aliased.
Is there any way to do so?
Unfortunately, there is no native way to turn off anti-aliasing for text.
The solution is to use the old-school approach of bitmap fonts, that is, in the case of HTML5 canvas a sprite-sheet where you copy each bitmap letter to the canvas. By using a sprite-sheet with transparent background you can easily change the color/gradient etc. of it as well.
An example of such bitmap:
For it to work you need to know what characters it contains ("map"), the width and height of each character, and the width of the font bitmap.
Note: In most cases you'll probably end up with a mono-spaced font where all cells have the same size. You can use a proportional font but in that case you need to be aware of that you need to map each character with an absolute position and include the width and height as well for its cell.
An example with comments:
const ctx = c.getContext("2d"), font = new Image;
font.onload = () => {
// define some meta-data
const charWidth = 12; // character cell, in pixels
const charHeight = 16;
const sheetWidth = (font.width / charWidth)|0; // width, in characters, of the image itself
// map so we can use index of a char. to calc. position in bitmap
const charMap = " !\"#$% '()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~§";
// Draw some demo text
const timeStart = performance.now();
fillBitmapText(font, "Demo text using bitmap font!", 20, 20);
fillBitmapText(font, "This is line 2...", 20, 45);
const timeEnd = performance.now();
console.log("Text above rendered in", timeEnd - timeStart, "ms");
// main example function
function fillBitmapText(font, text, x, y) {
// always make sure x and y are integer positions
x = x|0;
y = y|0;
// current x position
let cx = x;
// now, iterate over text per char.
for(let char of text) {
// get index in map:
const i = charMap.indexOf(char);
if (i >= 0) { // valid char
// Use index to calculate position in bitmap:
const bx = (i % sheetWidth) * charWidth;
const by = ((i / sheetWidth)|0) * charHeight;
// draw in character on canvas
ctx.drawImage(font,
// position and size from font bitmap
bx, by, charWidth, charHeight,
// position on canvas, same size
cx, y, charWidth, charHeight);
}
cx += charWidth; // increment current canvas x position
}
}
}
font.src = "//i.stack.imgur.com/GeawH.png";
body {background:#fff}
<canvas id=c width=640></canvas>
This should produce an output similar to this:
You can modify this to suit your needs. Notice that the bitmap used here is not transparent - I'll leave that to OP.

DrawText doesn't work, but Graphics::DrawString is ok

I am creating a bitmap in the memory which combine with an image and text. My code is:
HDC hdcWindow = GetDC();
HDC hdcMemDC = CreateCompatibleDC(hdcWindow);
HBITMAP hbmDrag = NULL;
if (!hdcMemDC) {
ReleaseDC(hdcWindow);
return NULL;
}
RECT clientRect = {0};
GetClientRect(&clientRect);
hbmDrag = CreateCompatibleBitmap(hdcWindow, 256, 256);
if(hbmDrag) {
SelectObject(hdcMemDC, hbmDrag);
FillRect(hdcMemDC, &clientRect, mSelectedBkgndBrush);
Graphics graphics(hdcMemDC);
// Draw the icon
graphics.DrawImage(mImage, 100, 100, 50, 50);
#if 1
CRect desktopLabelRect(0, y, clientRect.right, y);
HFONT desktopFont = mNameLabel.GetFont();
HGDIOBJ oldFont = SelectObject(hdcMemDC, desktopFont);
SetTextColor(hdcMemDC, RGB(255,0,0));
DrawText(hdcMemDC, mName, -1, desktopLabelRect, DT_CENTER | DT_END_ELLIPSIS | DT_CALCRECT);
#else
// Set font
Font font(hdcMemDC, mNameLabel.GetFont());
// Set RECT
int y = DEFAULT_ICON_HEIGHT + mMargin;
RectF layoutRect(0, y, clientRect.right, y);
// Set display format
StringFormat format;
format.SetAlignment(StringAlignmentCenter);
// Set brush
SolidBrush blackBrush(Color(255, 0, 0, 0));
// Draw the label
int labelWide = DEFAULT_ICON_WIDTH + mMargin;
CString labelName = GetLayOutLabelName(hdcMemDC, labelWide, mName);
graphics.DrawString(labelName, -1, &font, layoutRect, &format, &blackBrush);
#endif
}
DeleteDC(hdcMemDC);
ReleaseDC(hdcWindow);
return hbmDrag;
The image can be outputted to the bitmap success.
For the text, if I use "DrawText", it can't be shown in the bitmap although the return value is correct;
But Graphics::DrawString can output the text success.
I don't know the reason. Anybody can pls tell me?
Thanks a lot.
You are passing the DT_CALCRECT flag to DrawText(). This flag is documented as (emphasis mine):
Determines the width and height of the rectangle. If there are
multiple lines of text, DrawText uses the width of the rectangle
pointed to by the lpRect parameter and extends the base of the
rectangle to bound the last line of text. If the largest word is wider
than the rectangle, the width is expanded. If the text is less than
the width of the rectangle, the width is reduced. If there is only one
line of text, DrawText modifies the right side of the rectangle so
that it bounds the last character in the line. In either case,
DrawText returns the height of the formatted text but does not draw
the text.

GDI: How to fill RoundRect with color?

While the question title seems dumb, that's not exactly what I need. To fill whole area with color, one needs to select appropriate brush - that's trivial. But I want to fill upper half of it with different color, and bottom half of it with the different one. If it was the normal (not round) rectangle, I could draw two rectangles (with different brushes). But with RoundRect I don't have any ideas how to do it.
Here is what I need it for: I draw each node in my graph visualization with RoundRect, and those nodes should have several compartments (cells) that should be filled with different colors.
I hope you get the idea what I mean :)
If you have to use legacy GDI instead of GDI+, here I wrote you a function to draw such a (cell) as you needed I hope it is what you have expected !
The basic idea is to create upper and lower regions (which they were both full overlapping rounded rectangles, then each has one of its halves cut off)
I have prepared the above illustration to show how the cell could be produced. It's for the upper side only, but you should have got the idea of creating the lower one.
Here is a wrapping function to create the cell you need:
void DrawCell(HDC& hdc, const RECT& rcTarget,const HBRUSH& hbrUpper, const HBRUSH& hbrLower)
{
HRGN hRgnUpper = CreateRoundRectRgn(rcTarget.left, rcTarget.top, rcTarget.right, rcTarget.bottom, 42, 38);
HRGN hRgnLower = CreateRoundRectRgn(rcTarget.left, rcTarget.top, rcTarget.right, rcTarget.bottom, 42, 38);
HRGN hRgnCutFromUpper = CreateRectRgn(rcTarget.left, rcTarget.top + ((rcTarget.bottom - rcTarget.top) / 2), rcTarget.right, rcTarget.bottom);
HRGN hRgnCutFromLower = CreateRectRgn(rcTarget.left, rcTarget.top , rcTarget.right, rcTarget.bottom - ((rcTarget.bottom - rcTarget.top) / 2));
CombineRgn(hRgnUpper, hRgnUpper,hRgnCutFromUpper, RGN_DIFF);
CombineRgn(hRgnLower, hRgnLower,hRgnCutFromLower, RGN_DIFF);
FillRgn( hdc, hRgnUpper, hbrUpper);
FillRgn( hdc, hRgnLower, hbrLower);
DeleteObject(hRgnCutFromLower);
DeleteObject(hRgnCutFromUpper);
DeleteObject(hRgnLower);
DeleteObject(hRgnUpper);
}
call this function from within your WM_PAINT handler:
RECT rcTarget;
rcTarget.left = 20;
rcTarget.top = 20;
rcTarget.right = 275;
rcTarget.bottom = 188;
HBRUSH hRed = CreateSolidBrush( RGB(255, 0, 0) );
HBRUSH hGreen = CreateSolidBrush( RGB(0, 255, 0) );
DrawCell(hdc, rcTarget, hRed, hGreen);

Resources