[printing] wrong page size calculation in printing-sample? Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
MoonKid
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 543
Joined: Wed Apr 05, 2006 9:39 am
Contact:

[printing] wrong page size calculation in printing-sample?

Post by MoonKid » Tue Jan 22, 2008 4:44 pm

I am still working on printing. Now I minimized the printing-sample code you can see below.

This is the result:
Image
Very cool is that now the result is the same on each DC (preview, pdf-printer, real-printer)! And the point size of the strings are correct on all DCs. I checked it with a ruler, too. ;)

But you can see that the bottom-right-corner is wrong calculated. (it is the same on each DC)

In the code you see that I draw a rectangle (with margins 5) around the page and draw a "X" in the bottom-right-corner. But you can not see it in the result.

Code: Select all

MyPrintout::MyPrintout(wxPageSetupDialogData* pPageSetupDialogData)
          : wxPrintout("My Printout"),
            pPageSetupDialogData_(pPageSetupDialogData)
{
    fontDefault_.SetFaceName(_T("Arial"));
    fontDefault_.SetFamily(wxFONTFAMILY_SWISS);
    fontDefault_.SetPointSize(12);
    fontDefault_.SetStyle(wxFONTSTYLE_NORMAL);
    fontDefault_.SetWeight(wxFONTWEIGHT_NORMAL);
}

bool MyPrintout::OnPrintPage(int page)
{
    DrawPageTwo();

    // Draw page numbers at top left corner of printable area, sized so that
    // screen size of text matches paper size.
    MapScreenSizeToPage();
    wxChar buf[200];
    wxSprintf(buf, wxT("PAGE %d"), page);
    GetDC()->DrawText(buf, 0, 0);

    return true;
}

bool MyPrintout::OnBeginDocument(int startPage, int endPage)
{
    if (!wxPrintout::OnBeginDocument(startPage, endPage))
        return false;

    return true;
}

void MyPrintout::GetPageInfo(int *minPage, int *maxPage, int *selPageFrom, int *selPageTo)
{
    *minPage = 1;
    *maxPage = 1;
    *selPageFrom = 1;
    *selPageTo = 1;
}

bool MyPrintout::HasPage(int pageNum)
{
    return (pageNum == 1);
}

void MyPrintout::DrawPageTwo()
{
    wxDC *dc = GetDC();

    // Get the logical pixels per inch of screen and printer
    int ppiScreenX, ppiScreenY;
    GetPPIScreen(&ppiScreenX, &ppiScreenY);
    int ppiPrinterX, ppiPrinterY;
    GetPPIPrinter(&ppiPrinterX, &ppiPrinterY);

    // This scales the DC so that the printout roughly represents the the screen
    // scaling. The text point size _should_ be the right size but in fact is
    // too small for some reason. This is a detail that will need to be
    // addressed at some point but can be fudged for the moment.
    float scale = (float)((float)ppiPrinterX/(float)ppiScreenX);

    // Now we have to check in case our real page size is reduced (e.g. because
    // we're drawing to a print preview memory DC)
    int pageWidth, pageHeight;
    int w, h;
    dc->GetSize(&w, &h);
    GetPageSizePixels(&pageWidth, &pageHeight);

    // If printer pageWidth == current DC width, then this doesn't change. But w
    // might be the preview bitmap width, so scale down.
    float overallScale = scale * (float)(w/(float)pageWidth);
    dc->SetUserScale(overallScale, overallScale);

    // draw a rectangle around the page with margins of 5
    dc->DrawRectangle(5, 5, pageWidth-5, pageHeight-5);

    // draw a "X" in the bottom-right corner (without margin)
    dc->GetTextExtent("X", &w, &h);
    dc->DrawText("X", pageWidth-w, pageHeight-h);

    //
    wxString words[7] = {_T("This "), _T("is "), _T("GetTextExtent "), _T("testing "), _T("string. "), _T("Enjoy "), _T("it!")};
    long x = 200, y= 250;
    wxFont fnt(15, wxSWISS, wxNORMAL, wxNORMAL);

    dc->SetFont(fnt);

    for (int i = 0; i < 7; i++)
    {
        wxString word = words[i];
        word.Remove( word.Len()-1, 1 );
        dc->GetTextExtent(word, &w, &h);
        //dc->DrawRectangle(x, y, w, h);
        dc->GetTextExtent(words[i], &w, &h);
        dc->DrawText(words[i], x, y);
        x += w;
    }

    dc->SetFont(fontDefault_);

    dc->DrawText(_T("Some test text"), 200, 300 );
}

tan
Moderator
Moderator
Posts: 1471
Joined: Tue Nov 14, 2006 7:58 am
Location: Saint-Petersburg, Russia

Post by tan » Wed Jan 23, 2008 7:28 am

Hi,
just replace pageWidth|Hight on pageWidth|Hight/scale in the DrawRectangle:

Code: Select all

dc->DrawRectangle(5, 5, pageWidth/scale-5, pageHeight/scale-5);
OS: Windows XP Pro
Compiler: MSVC++ 7.1
wxWidgets: 2.8.10

MoonKid
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 543
Joined: Wed Apr 05, 2006 9:39 am
Contact:

Post by MoonKid » Wed Jan 23, 2008 11:08 pm

tan wrote:just replace ...
Hm... Work, but not 100% correct.

"pageWidth/scale-5" just fit exactly to the corner. There is no margin of 5 pixel.

dc->DrawText("X", pageWidth/scale-w, pageHeight/scale-h);
This doesn't work correct, too. In the preview it is definitly in the corner.
In the printout it is in the corner of the printable area not in the page corner.

Ok maybe I can work with this. Thank you for your help very much!

MoonKid
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 543
Joined: Wed Apr 05, 2006 9:39 am
Contact:

Post by MoonKid » Thu Jan 24, 2008 11:38 pm

Of course it should be this

Code: Select all

dc->DrawRectangle(5, 5, pageWidth/scale-10, pageHeight/scale-10);

Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria » Fri Jan 25, 2008 12:32 am

I don't think you should calculate stuff in pixels... you will need to decide for a margin size in inches or centimeters, and then retrieve the sizing from the DC (i think) and convert the cm/inches into pixels. Depending on the resolution of your printer, 5 pixels can be very small

MoonKid
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 543
Joined: Wed Apr 05, 2006 9:39 am
Contact:

Post by MoonKid » Sat Jan 26, 2008 12:11 am

Auria wrote:I don't think you should calculate stuff in pixels...
A good framework would do this for me. But what do I say...

Ok I should use the scale-factor for the margins or store the marings calculates with scalefactors.

jmason1182
Earned some good credits
Earned some good credits
Posts: 149
Joined: Fri Dec 14, 2007 3:40 pm
Location: Midland, TX
Contact:

Post by jmason1182 » Tue Jan 29, 2008 10:26 pm

I had a lot of trouble with the same issue you are dealing with. "To Be or not to BE" in pixels.... or cm... or POINTS... etc. etc.


See, you have to pick one, then scale AFTER you do all drawing. if you don't then you will have a difficult time doing your scaling everywhere. I learned this the hard way.

Most people will do their scaling based on what they've drawn, then map the canvas to the page. I use something like GetSizeMM to get a "known" dimensional size. GetSize will be scaled... so to use it you have to use scaling for everything...

All that to say: if you want to do it the way you are doing it, then use the page setup data to get the margins, get the paper size, etc. Then do your scaling and adjust a local "pageHeight" etc. value. If you want to have a .5" margin and put something in the lower-right hand corner, then compute the page size and margins using the page setup data, apply your scaling etc, then set pageHeight = pageHeight / scale and pageMarginLeft = pageMarginLeft / scale, etc. etc. etc.

THEN, you can use your pageHeight, pagemargins, etc. in the manner you are used to because the pageHeight (again... etc.) are all based on this "artibtrary" unit you've created. the "page pixel" as you might call it.

After three weeks of working over 3,000 lines of code to generate an XML based report-writer... I started over. JUST so I could separate my scaling from my drawing. Then, I applied my paper size and mapping mode, then got the scale to convert "my" page margins into the actual page margins, then did more calculations to convert every other variable I would use in the drawing process to my new arbitrary units. Then, I just drew. no scale. nada. It was done. Then printing worked great.

Man... am I wordy or what!? Good luck. Sorry i don't have any examples right-away.
John A. Mason
Midland, TX

MoonKid
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 543
Joined: Wed Apr 05, 2006 9:39 am
Contact:

Post by MoonKid » Wed Jan 30, 2008 9:18 am

jmason1182 wrote:See, you have to pick one, then scale AFTER you do all drawing.
Hm... Interessting. I don't do this. I scale on each OnPrintPage()-call before any drawings. My code work, but I don't understand the code. ;)
So I don't want to change something on it. But I will remember you If I write a printing routine on another project.

Just for interesst here my working (but maybe very ugly) code.

Code: Select all

/*virtual*/ void WCPrintout::OnPreparePrinting()
{
    //
    float scale = ScaleDC();

    // margins in milimeter
    iMarginLeft_    = 12;
    iMarginRight_   = 10;
    iMarginTop_     = 10;
    iMarginBottom_  = 10;

    // patient height in milimeter
    iPatientHeight_ = 14;

    //
    GetPageSizePixels(&iPageWidth_, &iPageHeight_);
    iPageWidth_ = (int)((float)iPageWidth_ / scale);
    iPageHeight_ = (int)((float)iPageHeight_ / scale);
    iMarginLeft_    = (int)((float)iMarginLeft_    / scale);
    iMarginRight_   = (int)((float)iMarginRight_   / scale);
    iMarginTop_     = (int)((float)iMarginTop_     / scale);
    iMarginBottom_  = (int)((float)iMarginBottom_  / scale);
    iPatientHeight_ = (int)((float)iPatientHeight_ / scale);

    /* convert margins in pixel related to the PageSize
       they are correct scaled later in ScaleDC() */
    int wMM, hMM, wPIX, hPIX;
    GetPageSizePixels(&wPIX, &hPIX);
    GetPageSizeMM(&wMM, &hMM);
    float fScaleMMtoPIXw = (float)wPIX / (float)wMM;
    float fScaleMMtoPIXh = (float)hPIX / (float)hMM;
    iMarginLeft_    = (int)(iMarginLeft_    * fScaleMMtoPIXw);
    iMarginRight_   = (int)(iMarginRight_   * fScaleMMtoPIXw);
    iMarginTop_     = (int)(iMarginTop_     * fScaleMMtoPIXh);
    iMarginBottom_  = (int)(iMarginBottom_  * fScaleMMtoPIXh);
    iPatientHeight_ = (int)(iPatientHeight_ * fScaleMMtoPIXh);

    pPageSetupDialogData_->SetMarginTopLeft(wxPoint(iMarginLeft_, iMarginTop_));
    pPageSetupDialogData_->SetMarginBottomRight(wxPoint(iMarginRight_, iMarginBottom_));

    // fonts
    fontDefault_.SetFaceName(_T("Arial"));
    fontDefault_.SetFamily(wxFONTFAMILY_SWISS);
    fontDefault_.SetPointSize(12);
    fontDefault_.SetStyle(wxFONTSTYLE_NORMAL);
    fontDefault_.SetWeight(wxFONTWEIGHT_NORMAL);
    fontName_ = fontDiagnosis_ = fontHeadline_ = fontDefault_;
    fontName_.SetWeight(wxFONTWEIGHT_BOLD);
    fontDiagnosis_.SetStyle(wxFONTSTYLE_ITALIC);
    fontDiagnosis_.SetPointSize(11);
    fontHeadline_.SetWeight(wxFONTWEIGHT_BOLD);
    fontHeadline_.SetPointSize(15);

    // font height
    int iFontWidth;
    GetDC()->SetFont(fontDefault_);
    GetDC()->GetTextExtent(_T("W"), &iFontWidth, &iFontHeightDefault_);
    GetDC()->SetFont(fontName_);
    GetDC()->GetTextExtent(_T("W"), &iFontWidth, &iFontHeightName_);
    GetDC()->SetFont(fontDiagnosis_);
    GetDC()->GetTextExtent(_T("W"), &iFontWidth, &iFontHeightDiagnosis_);

    // pen
    penDefault_.SetColour(*wxBLACK);
    penDefault_.SetStyle(wxSOLID);
    penDefault_.SetWidth(2);

    // number of pages
    iMinPage_ = 1;
    iMaxPage_ = 2;
}

float WCPrintout::ScaleDC()
{
    // Get the logical pixels per inch of screen and printer
    int ppiScreenX, ppiScreenY;
    GetPPIScreen(&ppiScreenX, &ppiScreenY);
    int ppiPrinterX, ppiPrinterY;
    GetPPIPrinter(&ppiPrinterX, &ppiPrinterY);

    // This scales the DC so that the printout roughly represents the the screen
    // scaling. The text point size _should_ be the right size but in fact is
    // too small for some reason. This is a detail that will need to be
    // addressed at some point but can be fudged for the moment.
    float scale = (float)((float)ppiPrinterX/(float)ppiScreenX);

    /* scale our PageSize and Margins to the "screen" */
    int pageWidth, pageHeight;
    int w, h;
    GetDC()->GetSize(&w, &h);
    GetPageSizePixels(&pageWidth, &pageHeight);


    // If printer pageWidth == current DC width, then this doesn't change. But w
    // might be the preview bitmap width, so scale down.
    float overallScale = scale * (float)(w/(float)pageWidth);
    GetDC()->SetUserScale(overallScale, overallScale);

    return scale;
}

bool WCPrintout::OnPrintPage(int page)
{
    ScaleDC();

    // [do a lot of drawing]

    return true;
}

jmason1182
Earned some good credits
Earned some good credits
Posts: 149
Joined: Fri Dec 14, 2007 3:40 pm
Location: Midland, TX
Contact:

Post by jmason1182 » Wed Jan 30, 2008 1:18 pm

Actually, what you have should work for scaling. Later on today i'll post a few comments. I have to make time to go through your code a little more.

In the meantime, what parts of the code are most confusing?
John A. Mason
Midland, TX

jmason1182
Earned some good credits
Earned some good credits
Posts: 149
Joined: Fri Dec 14, 2007 3:40 pm
Location: Midland, TX
Contact:

Post by jmason1182 » Wed Jan 30, 2008 1:47 pm

Some quick things I noticed is what I had a problem with before.

First off, IF you have multiple pages, you probably want to be able to compute how many pages you need. Since you are scaling before you draw, you'll have to apply the scale before computing page numbers too. Here's how I did it:
(My class name is XMLReportPrintout... a derived class from wxPrintout)

Code: Select all

/*----------------------------------------------------
 * OnPreparePrinting
 * - This is the first stop when printing or previewing. MUST get pages setup here since this is where we are supposed to count pages, etc.
 *----------------------------------------------------*/
void XMLReportPrintout::OnPreparePrinting()
{
	//Reset scale and pagination since
	//this function indicates potential re-zooming, etc.
	wxDC *dc = GetDC();
	if (dc) {
		this->Scaled = ComputeScale(*dc);
		this->Paginated = Paginate(*dc);
		wxPrintout::OnPreparePrinting();
	} else {
        wxLogError("Couldn't prepare the document. Could not get canvas.");
	}
}

/*----------------------------------------------------
 * OnBeginDocument
 * - This is the second stop when printing/previewing.
 * - Here we can do more scaling if necessary
 *----------------------------------------------------*/
bool XMLReportPrintout::OnBeginDocument(int startPage, int endPage)
{
	wxDC *dc = GetDC();
	if (dc) {
		if(!this->Scaled) this->Scaled = ComputeScale(*dc);
		if(!this->Paginated) 		this->Paginated = Paginate(*dc);
    	if (!wxPrintout::OnBeginDocument(startPage, endPage)) return false;
    	return true;
	} else {
        wxLogError("Couldn't begin the document. Could not get canvas.");
		return false;
	}
}


/*----------------------------------------------------
 * OnPrintPage
 * - This is the last stop to print/preview.
 * - All drawing should be here. SCALING AND PAGINATION SHOULD BE COMPLETED ALREADY
 *----------------------------------------------------*/
bool XMLReportPrintout::OnPrintPage(int page)
{
    wxDC *dc = GetDC();
    if (dc && this->Scaled && this->Paginated) {		
	    return DrawReportPage(*dc,page);
    } else {
		wxLogError("Couldn't produce the page. %s",(this->Scaled?(this->Paginated?wxT("Couldn't get canvas."):wxT("Not Paginated.")):wxT("Not Scaled Yet.")));
		return false;
	}
}

So a quick stop before I post my ComputeScale and my Paginate functions. In my class, I have a simple boolean variable called Scaled and Paginated that keep track of whether the scaling has occurred or not. I don't remember why i do the sanity check of testing the scaling in OnBeginDocument... but I do remember all heck broke loose if I didn't.

Secondly, I'm not gonna post my 1000 line DrawReportPage function... instead I'll just mention that it just reads an XML file that has attributes in each tag specifying "Draw a line from point (100,40) to (200,80)" and then the function draws that line. Earlier, i parse the XML file for page margins, data record placeholders, etc. so I can make a useful report from an application and use the XML file as a formatter. But that's not relevant here, so i digress. Just note that DrawReportPage does no scaling at all... just draws as-is.

And now that I think about it, my Paginate just uses the XML defined heights listed for each object and decides if it will fit on what page, if a new page needs to be created, etc. Really simple. So I guess it doesn't need to be posted either. Most of it is mathematical. Pseudocode example:

Code: Select all

If HeightOfCurrentObject  + HeightOfObjectsAlreadyOnPage > TotalPrintablePageHeight, Then {
    Create a new page,
    Increment the page counter,
    Put the CurrentObject on the next page
}
REPEAT FOR ALL OBJECTS
So that just leaves ComputeScale.... So here it is:

Code: Select all

/*----------------------------------------------------
 * ComputeScale
 * - Computes all scaling and size changes to document.
 * - Note the use of pageSetupData (local wxPageSetupDialogData member to XMLReportPrintout passed during constructor from a wxPageSetupDialog)
 *----------------------------------------------------*/
bool XMLReportPrintout::ComputeScale(wxDC &dc){

	//Maximum page size (in MM... why is it in MM? I don't know. I just figured out it is.)
	//Note: this is PRINTABLE page size... so LIMITS of physical paper
	float maxX = pageSetupData->GetPaperSize().x;
	float maxY = pageSetupData->GetPaperSize().y;

	//Margins size (in MM... again why MM? <shrug>)
	float marginL = pageSetupData->GetMarginTopLeft().x;
	float marginT = pageSetupData->GetMarginTopLeft().y;
	float marginR = pageSetupData->GetMarginBottomRight().x;
	float marginB = pageSetupData->GetMarginBottomRight().y;

	//Now we want to set the origin of our drawing to the most upper-left corner....
	//Think of this as "applying" our margins
	dc.SetDeviceOrigin( dc.LogicalToDeviceX((long)marginL), dc.LogicalToDeviceY((long)marginT) );

	//Get the printable area (again in MM)
	//Now we are looking at OUR printable area.... so basically the whole canvas WITHOUT margins (since we don't print in the margins)
	float printableW = maxX - (marginL+marginR);
	float printableH = maxY - (marginT+marginB);

	//The FormatUnits is taken from the XML document... it is simply one of the following values:
	//  wxMM_TWIPS, wxMM_POINTS, wxMM_METRIC, or wxMM_LOMETRIC
	dc.SetMapMode(this->FormatUnits);

	//If we stop now and return, our page units will be exactly as specified. So drawing a line from (100,40) to (200,80) would go 100mm,40mm to 200mm,80mm IF we were using wxMM_METRIC. Or it would be points on the physical paper if we used wxMM_POINTS.


	//BUT we want to test a custom scale too.... and you could use your getPPI type functions to generate this. 
	//I use a parameter passed from the XML document... so the report defines the custom scale
	//FormatScale is a boolean... when parsed we say "Is there a custom scale?"
	if(this->FormatScale) {

		// A custom scale exists... so let's apply it.
		int deviceW, deviceH;
		//since our page setup is MM, I use GetSizeMM... then scaling WORKS
		dc.GetSizeMM(&deviceW, &deviceH);

		float scaleX=(float)(deviceW/printableW);
		float scaleY=(float)(deviceH/printableH);

		//Do we keep the aspect ratio? Or allow scaling to be different between X and Y axis.
		if(this->FormatKeepAspect){
		        //We do keep aspect ratio, so whatever is the smallest scaling factor... we use that.
			scaleX = wxMin(scaleX,scaleY);
			scaleY = scaleX;
		}

		//Set the scale. NOW things are in whatever arbitrary scale you wanted. 
		//So for instance, if you specified a 2.5:1 ratio, then that line (100,40) to (200,80) will be the equivalent of (250,100) to (500,200).
		dc.SetUserScale(scaleX,scaleY);
	}

	return true;
}
SO... when I scale before I draw, I have to do it before counting my pages. I also have to do it before I can do ANYTHING... then I have to make sure it is applied.

Secondly, I use MM in all my calculations because the page setup info is already in MM. If I do pixels or something else... then my margins won't be what I expect and so on.
John A. Mason
Midland, TX

MoonKid
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 543
Joined: Wed Apr 05, 2006 9:39 am
Contact:

Post by MoonKid » Thu Jan 31, 2008 12:35 am

Thank your very much for the very well code! It make some things clearer. I will check my own code against yours in the next weeks.

btw: In my case there is a fix number of pages. So I don't need to calculate then. But in the futur I will work on it. ;)
jmason1182 wrote:

Code: Select all

// [..]
bool XMLReportPrintout::ComputeScale(wxDC &dc){

// [..]
	dc.SetDeviceOrigin( dc.LogicalToDeviceX((long)marginL), dc.LogicalToDeviceY((long)marginT) );
Do I understand this line right: It means that if I DrawSomething(0, 0) it doesn't draw it on the physical-top-left corner of the page. It will draw it on the printable-top-left-corner of the page including the marings?

jmason1182
Earned some good credits
Earned some good credits
Posts: 149
Joined: Fri Dec 14, 2007 3:40 pm
Location: Midland, TX
Contact:

Post by jmason1182 » Thu Jan 31, 2008 1:16 pm

That is correct IF and ONLY IF you use the SetDeviceOrigin like I did:

Code: Select all

dc.SetDeviceOrigin( dc.LogicalToDeviceX((long)marginL), dc.LogicalToDeviceY((long)marginT) );
This will help you to make easier decisions on your coordinates... for instance, instead of drawing to some x-coordinate, "x plus the left margin", you'll just use the x-coordinate, "x".

Deciphering the difference between logical units, device units, and the mapped units (and the equivalences between each) is the hard part.

Good luck.
John A. Mason
Midland, TX

MoonKid
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 543
Joined: Wed Apr 05, 2006 9:39 am
Contact:

Post by MoonKid » Mon Feb 04, 2008 12:42 am

jmason1182 wrote:Secondly, I use MM in all my calculations because the page setup info is already in MM. If I do pixels or something else... then my margins won't be what I expect and so on.
You use milimeters to draw.

Ok but I need pixelx because I need to calculate with the values GetTextExtent() retuns. So is it enough If I set the MapMode to wxMM_POINTS?

On your OnPrintPage() how do you know the max-drawable-x and max-drawable-y?

jmason1182
Earned some good credits
Earned some good credits
Posts: 149
Joined: Fri Dec 14, 2007 3:40 pm
Location: Midland, TX
Contact:

Post by jmason1182 » Mon Feb 04, 2008 3:21 pm

First off, a point is 1/72nd of an inch. The TWIP is a huge 1/20 of a point, or 1/1440 of an inch. See... a PIXEL is arbitrary when talking about printing. You see you screen has pixels... so we always think in pixels. But no screen has the same pixels... different resolutions changes what a pixel is too. Plus, think about how the manual describe GetTextExtent....
wxDC::GetTextExtent
void GetTextExtent(const wxString& string, wxCoord *w, wxCoord *h,
wxCoord *descent = NULL, wxCoord *externalLeading = NULL, wxFont *font = NULL) const

wxSize GetTextExtent(const wxString& string) const

Gets the dimensions of the string using the currently selected font. string is the text string to measure, descent is the dimension from the baseline of the font to the bottom of the descender, and externalLeading is any extra vertical space added to the font by the font designer (usually is zero).

The text extent is returned in w and h pointers (first form) or as a wxSize object (second form).

If the optional parameter font is specified and valid, then it is used for the text extent calculation. Otherwise the currently selected font is.

Note that this function only works with single-line strings.

See also

wxFont, wxDC::SetFont, wxDC::GetPartialTextExtents, wxDC::GetMultiLineTextExtent

wxPython note: The following methods are implemented in wxPython:


GetTextExtent(string) Returns a 2-tuple, (width, height)
GetFullTextExtent(string, font=NULL) Returns a 4-tuple, (width, height, descent, externalLeading)

wxPerl note: In wxPerl this method is implemented as GetTextExtent( string, font = undef ) returning a four element array ( width, height, descent, externalLeading )
No pixels are mentioned. That's because the GetTextExtent (which is not 100% exact... but extremely close for many fonts) returns a width/height IN THE CURRENT SCALE.

So if you set the map mode to points, then GetTextExtent will return the width/height in terms of points. If you set the scale to a custom value (x,y) it will return the width/height in terms of those scaled units.

And lastly, just store the printableW and printableH that are calculated during calculateScale and you have the printableW and printableH.
John A. Mason
Midland, TX

MoonKid
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 543
Joined: Wed Apr 05, 2006 9:39 am
Contact:

Post by MoonKid » Tue Feb 05, 2008 6:02 pm

Ok, now I try to use your code in my application.
But I did something wrong.
Look on this result:

Image
This is just Arial 12 points.
You can see it in the code. The most of it is your own. ;)

Code: Select all

WCPrintout::WCPrintout (wxPageSetupDialogData* pPageSetupDialogData)
          : pPageSetupDialogData_(pPageSetupDialogData),
            pNextRoom_(NULL),
            bScaled_(false),
            bPaginated_(false)

{
}


/*virtual*/ WCPrintout::~WCPrintout ()
{
}

/*----------------------------------------------------
 * OnPreparePrinting
 * - This is the first stop when printing or previewing. MUST get pages setup here since this is where we are supposed to count pages, etc.
 *----------------------------------------------------*/
/*virtual*/ void WCPrintout::OnPreparePrinting()
{
    ComputeScale();
    Paginate();

    wxPrintout::OnPreparePrinting();
}

/*----------------------------------------------------
 * OnBeginDocument
 * - This is the second stop when printing/previewing.
 * - Here we can do more scaling if necessary
 *----------------------------------------------------*/
bool WCPrintout::OnBeginDocument(int startPage, int endPage)
{
    if (!bScaled_)
        ComputeScale();

    if (!bPaginated_)
        Paginate();

    // fonts
    fontDefault_.SetFaceName(_T("Arial"));
    fontDefault_.SetFamily(wxFONTFAMILY_SWISS);
    fontDefault_.SetPointSize(12);
    fontDefault_.SetStyle(wxFONTSTYLE_NORMAL);
    fontDefault_.SetWeight(wxFONTWEIGHT_NORMAL);

    // pen
    penDefault_.SetColour(*wxBLACK);
    penDefault_.SetStyle(wxSOLID);
    penDefault_.SetWidth(2);

    return wxPrintout::OnBeginDocument(startPage, endPage);
}

/*----------------------------------------------------
 * ComputeScale
 * - Computes all scaling and size changes to document.
 * - Note the use of pageSetupData (local wxPageSetupDialogData member to XMLReportPrintout passed during constructor from a wxPageSetupDialog)
 *----------------------------------------------------*/
void WCPrintout::ComputeScale()
{
    //Maximum page size (in MM... why is it in MM? I don't know. I just figured out it is.)
    //Note: this is PRINTABLE page size... so LIMITS of physical paper
    float maxX = pPageSetupDialogData_->GetPaperSize().x;
    float maxY = pPageSetupDialogData_->GetPaperSize().y;

    //Margins size (in MM... again why MM? <shrug>)
    float marginL = pPageSetupDialogData_->GetMarginTopLeft().x;
    float marginT = pPageSetupDialogData_->GetMarginTopLeft().y;
    float marginR = pPageSetupDialogData_->GetMarginBottomRight().x;
    float marginB = pPageSetupDialogData_->GetMarginBottomRight().y;

    //Now we want to set the origin of our drawing to the most upper-left corner....
    //Think of this as "applying" our margins
    GetDC()->SetDeviceOrigin( GetDC()->LogicalToDeviceX((long)marginL), GetDC()->LogicalToDeviceY((long)marginT) );

    //Get the printable area (again in MM)
    //Now we are looking at OUR printable area.... so basically the whole canvas WITHOUT margins (since we don't print in the margins)
    fPrintableW_ = maxX - (marginL+marginR);
    fPrintableH_ = maxY - (marginT+marginB);

    //The FormatUnits is taken from the XML document... it is simply one of the following values:
    //  wxMM_TWIPS, wxMM_POINTS, wxMM_METRIC, or wxMM_LOMETRIC
    GetDC()->SetMapMode(wxMM_POINTS);

    //If we stop now and return, our page units will be exactly as specified. So drawing a line from (100,40) to (200,80) would go 100mm,40mm to 200mm,80mm IF we were using wxMM_METRIC. Or it would be points on the physical paper if we used wxMM_POINTS.


    //BUT we want to test a custom scale too.... and you could use your getPPI type functions to generate this.
    //I use a parameter passed from the XML document... so the report defines the custom scale
    //FormatScale is a boolean... when parsed we say "Is there a custom scale?"

    // A custom scale exists... so let's apply it.
    int deviceW, deviceH;
    //since our page setup is MM, I use GetSizeMM... then scaling WORKS
    GetDC()->GetSizeMM(&deviceW, &deviceH);

    float scaleX=(float)(deviceW/fPrintableW_);
    float scaleY=(float)(deviceH/fPrintableH_);

    //We do keep aspect ratio, so whatever is the smallest scaling factor... we use that.
    scaleX = wxMin(scaleX,scaleY);
    scaleY = scaleX;

    //Set the scale. NOW things are in whatever arbitrary scale you wanted.
    //So for instance, if you specified a 2.5:1 ratio, then that line (100,40) to (200,80) will be the equivalent of (250,100) to (500,200).
    GetDC()->SetUserScale(scaleX,scaleY);

    bScaled_ = true;
}

void WCPrintout::Paginate ()
{
    // patient height in milimeter
    iPatientHeight_ = 14;

    // number of pages
    iMinPage_ = 1;
    iMaxPage_ = 2;

    bPaginated_ = true;
}


/*virtual*/ void WCPrintout::GetPageInfo(int* minPage,
                                         int* maxPage,
                                         int* pageFrom,
                                         int* pageTo)
{
    (*minPage)  = iMinPage_;
    (*maxPage)  = iMaxPage_;
    (*pageFrom) = iMinPage_;
    (*pageTo)   = iMaxPage_;
}


bool WCPrintout::OnPrintPage(int page)
{
    //ScaleDC();
    GetDC()->SetFont(fontDefault_);
    GetDC()->DrawText("XYZ", 0, 0);
    GetDC()->DrawLine(0, 0, (int)fPrintableW_, (int)fPrintableH_);
    return true;
}

/*virtual*/ bool WCPrintout::HasPage(int iPageNum)
{
    if( (iPageNum >= iMinPage_) && (iPageNum <= iMaxPage_) )
        return true;

    return false;
}
This is the state of the local variables while the ComputeScale()-call.

Image

Maybe there is something wrong on the page-setup-date? But I don't think so.

Code: Select all

void WCMainFrame::OnButton_Print (wxCommandEvent& rEvent)
{
    wxPrintData* pPrintData = new wxPrintData();
    pPrintData->SetPaperId(wxPAPER_A4);

    wxPageSetupDialogData* pPageSetupDialogData = new wxPageSetupDialogData();
    (*pPageSetupDialogData) = (*pPrintData);

    wxPrinter printer;
    WCPrintout printout(pPageSetupDialogData);
    printer.Print(this, &printout, true);
}

Post Reply