Haxe, OpenFL: draw TextField on Bitmap in a loop - mobile

So, this app I am making for mobile phones with Haxe, and OpenFL.
I have a very long text which I load from a text file, and put it into a very tall TextField. However I want to convert this into a Bitmap, due to performance issues.
Again, however, a very tall Bitmap drawn from a text field just shows blank (maybe too much data?), so I decided to split the bitmap data into "pages" bitmaps, which the user can swipe on screen.
When I add the first "page" to display, it does. But rest of the "pages" just show as a blank image.
Here's my code:
images = new Array();
var contentHeight:Float = 560;
field = new TextField();
var fieldFont = Assets.getFont("fonts/Kreon-Regular.ttf");
var format:TextFormat = new TextFormat(fieldFont.fontName, 26 /*currentZoom*/, 0xffffff);// 0x4F4F4F);
format.align = TextFormatAlign.LEFT;
field.defaultTextFormat = format;
var fieldWidth:Float = 410;
field.embedFonts = true;
field.text = fullText;
field.selectable = false;
field.wordWrap = true;
field.border = false;
field.autoSize = TextFieldAutoSize.LEFT;
field.width = fieldWidth;
//field.x = 0;
//addChild(field);
//loop through lines, if line within reach, increase clip height, else make new bd
var clipY:Float = 0;
var clipHeight:Float = 0;
trace(field.numLines);
var h_:Float = field.getLineMetrics(0).height;
var bd:BitmapData;
var mainBd:BitmapData = new BitmapData(Std.int(field.width), Std.int(field.height), true, 0x00000000);
mainBd.draw(field);
for (i in 0... field.numLines)
{
try {
h_ = field.getLineMetrics(i).height+0.2;
} catch (e:Dynamic) {}
if (clipHeight < contentHeight + h_)//line can be accomodated
{
clipHeight += h_;
}
else { //can't be accomodated, do clipping
bd = new BitmapData(Std.int(field.width), Std.int(clipHeight + 5), true, 0x00000000);
trace("clip: clipY:" + clipY + " height:" + clipHeight);
bd.copyPixels(mainBd, new Rectangle(0, clipY, field.width, clipHeight), new Point(0, clipY));
//bd.draw(field, new Matrix(), new ColorTransform(), BlendMode.NORMAL, new Rectangle(0, clipY, field.width, clipHeight), true);
images.push(new Bitmap(bd, PixelSnapping.AUTO, true));
clipY += clipHeight;
clipHeight = 0;
}
}
addChild(images[1]);

You only add one of your images (addChild(images[1]);) to view.
Also, I'd recommend, you to :
Find the exact problem with displaying it as a single TextField, start with the exact amount of text needed for it to break.
Check if there are some weird Unicode characters in your text, they may just plain break the renderer layouting for example(which should be counted as a bug in renderer, but still the way to fix it is to remove them).
If you can't fix that problem, use multiple textfields instead of bitmaps, since with a large text you have a high chance to exceed memory limits(one page of your text currently takes around 1Mb of memory, which is A LOT.
The way you do this currently wouldn't work in any case since you do the same renderer normally does. If you want to render it to partial bitmaps(and there is a real problem with rendering big texts), you need to divide the text in parts and render each part individually. (Basically the same as using multiple textfields with cacheAsBitmap).
NOTE: Bitmaps shouldn't actually be any faster than TextFields, unless you use a VERY fancy font or a lot of filters. In any case, cacheAsBitmap property should do what you want automagically, without writing all this code. But I'm 99% sure thats not the case and you don't need that.

Related

What is the quickest way to get the background color of a single character in a RichTextBox?

Using WinForms, I have a RichTextBox which will hold the contents of a text file whose length will be limited only by what a RichTextBox can manage.
A section within it will be marked using a certain background color. I need to quickly identify where the section begins and ends. I don't trust that the section will be completely unbroken, and the marked area might cover the entire text--so I need to examine the background color of every character.
The obvious way to do this is to select each character in turn and get its SelectionBackColor:
private TextRange? CalcMarkedTextRange() {
var rtb = RichTextBox;
var textLength = rtb.TextLength;
// Store the range of whatever the user currently has selected
var currentSelectionRange = new TextRange( rtb );
rtb.Visible = false; // <--- To prevent slow screen updates
// Find where the first and last color-marked characters are
var markBegins = -1;
var markEnds = -1;
for ( int ix = 0; ix < textLength; ++ix ) {
rtb.Select( ix, 1 );
if ( rtb.SelectionBackColor == SelectedTextBackColor ) {
if ( markBegins == -1 ) {
markBegins = ix;
}
markEnds = ix;
}
}
// Put back user's selection
rtb.Select( currentSelectionRange );
rtb.Visible = true;
// See what we found
if ( markBegins > -1 ) {
// Return a single range encompassing all marked characters
return new TextRange( markBegins, markEnds - markBegins + 1 );
}
return null;
}
In the code above, TextRange is a structure that stores the start and length of a selection, just as you might expect.
If I don't hide the control by messing with its Visible property before scanning the colors, this code is unbelievably slow. You can see it scanning through, even with a relatively small amount of text. My test using about 4000 characters exhibited unacceptably poor performance.
I do hide it, it operates much more quickly, but there is still an ugly flash as the control disappears for a few seconds then comes back with its scroll position slightly off.
There must be a way to know the background color of an individual character without having to select it, though I imagine one would have to call a native Win32 method. But I have had no luck finding one.

WPF PrintPreview Get the PageContent of DocumentPaginator Pages

I want to write my own PrintPreview for larger text using a DocumentViewer to display it later.
I have not found anything usefully for my problem.
At the moment I'm searching for a way to get the content of the individual pages.
I found a way to access the individual pages, but I can't store or get it.
Using the code:
DocumentPaginator dpPages = (DocumentPaginator)((IDocumentPaginatorSource)twhtTemp.BuildTemplateControl(txtHeader, txtContent, pdlgPrint)).DocumentPaginator;
dpPages.ComputePageCount();
var fixedDocument = new FixedDocument();
for (int iPages= 0; iPages < dpPages.PageCount; iPages++)
{
var pageContent = new PageContent();
var fixedPage = new FixedPage();
fixedPage.Width = pdlgPrint.PrintableAreaWidth;
fixedPage.Height = pdlgPrint.PrintableAreaHeight;
pageContent.Child = fixedPage;
fixedDocument.Pages.Add(pageContent);
}
I'm already adding a new page for each existing page, but I can't get the content of the page.
So far I know, I need a UIElement to add to fixedPage.Children.
Or is there some easier way to get a flowdocument to a fixed document with many fixedpages (deppending to the pages count)?
i hate it to answer my own questions.
After searching three days i asked here.
One day later i found a way...
It's been a long time since the question has been answered.
I tried Doo Dah's answer, but the problem was that it doesn't take care of the page paddings of a flowdocument.
Therefore I wrote my own solution (Doo Dah's answer helped me to complete it):
public FixedDocument Get_Fixed_From_FlowDoc(FlowDocument flowDoc, PrintDialog printDlg)
{
var fixedDocument = new FixedDocument();
try
{
if (printDlg != null)
{
pdlgPrint = printDlg;
}
if (pdlgPrint == null)
{
pdlgPrint = new PrintDialog();
}
DocumentPaginator dpPages = (DocumentPaginator)((IDocumentPaginatorSource)flowDoc).DocumentPaginator;
dpPages.ComputePageCount();
PrintCapabilities capabilities = pdlgPrint.PrintQueue.GetPrintCapabilities(pdlgPrint.PrintTicket);
for (int iPages= 0; iPages < dpPages.PageCount; iPages++)
{
var page = dpPages.GetPage(iPages);
var pageContent = new PageContent();
var fixedPage = new FixedPage();
Canvas canvas = new Canvas();
VisualBrush vb = new VisualBrush(page.Visual);
vb.Stretch = Stretch.None;
vb.AlignmentX = AlignmentX.Left;
vb.AlignmentY = AlignmentY.Top;
vb.ViewboxUnits = BrushMappingMode.Absolute;
vb.TileMode = TileMode.None;
vb.Viewbox = new Rect(0, 0, capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
FixedPage.SetLeft(canvas, 0);
FixedPage.SetTop(canvas, 0);
canvas.Width = capabilities.PageImageableArea.ExtentWidth;
canvas.Height = capabilities.PageImageableArea.ExtentHeight;
canvas.Background = vb;
fixedPage.Children.Add(canvas);
fixedPage.Width = pdlgPrint.PrintableAreaWidth;
fixedPage.Height = pdlgPrint.PrintableAreaHeight;
pageContent.Child = fixedPage;
fixedDocument.Pages.Add(pageContent);
}
dv1.ShowPageBorders = true;
}
catch (Exception)
{
throw;
}
return fixedDocument;
}
You had to build a FlowDocument of the content you will show before and pass it
to the Method.
Added the PrintDialog variable to call the Method from my preview window and can pass the current printer settings.
If you call it from your main programm, you either can pass a new PrintDialog() or null, there is no difference, because it will create a new PrintDialog if you are passing null
This worked fine for me with a Flowdocument with different types of text (header, text, font).
It should work with pictures and text mixed, or only pictures, too - it's using the visuals and not something specific from a flowdocument, therefore it should work with pagebreaks, too.
I don't tryed Shahin Dohan'S answer, because it's the same problem as so often.
It's written at MVVM and very hard to understand when another person has written it.
At my opinion it would be better to write a little example programm without mvvm and the people can adept it to mvvm or use only the code.
I understand the opportunities of mvvm, but to show someone how to works something i see only disadvantages (if you will not show a specific mvvm mechanic)
Is it possible in WPF to get content of each DocumentPage by page number?
You can follow this then add each extracted TextRange to whatever you want.

Place an Image scaled at the width available space

I created a code that works, but I'm not sure that it's the best way to place an Image scaled automatically to the available width space. I need to put some content over that image, so I have a LayeredLayout: in the first layer there is the Label created with the following code, on the second layer there is a BorderLayout that has the same size of the Image.
Is the following code fine or is it possible to do better?
Label background = new Label(" ", "NoMarginNoPadding") {
boolean onlyOneTime = false;
#Override
public void paint(Graphics g) {
int labelWidth = this.getWidth();
int labelHeight = labelWidth * bgImage.getHeight() / bgImage.getWidth();
this.setPreferredH(labelHeight);
if (!onlyOneTime) {
onlyOneTime = true;
this.getParent().revalidate();
}
super.paint(g);
}
};
background.getAllStyles().setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
background.getAllStyles().setBgImage(bgImage);
Shorter code:
ScaleImageLabel sl = new ScaleImageLabel(bgImage);
sl.setUIID("Container");
You shouldn't override paint to set the preferred size. You should have overriden calcPreferredSize(). For ScaleImageLabel it's already set to the natural size of the image which should be pretty big.

WPF: Measure/Arrange gets called only once during animation

I'm working on a custom grid-like panel where one element can be zoomed.
First, all elements have the same size. If one should be zoomed, it gets twice its previous size and the rest of the elements fill the remaining space uniformly (so, all elements except the ones in the same row or column with the zoomed element get smaller height and width, the ones in the same row get a smaller width and zoomed height, etc.).
Everything worked fine until I added animations for the zoom.
I'm calculating the size of each element in the measure pass and at first I was measuring each element with its calculated size. The elements itself have NaN as size until they get zoomed. I'm zooming them by setting them to their final size and then start doubleanimations from old to new.
However when I added animations, measure and arrange were only called for the first animation step.
And when I tried to measure the elements with availableSize or an infinite size the animation worked fine but the elements didn't proper resize in the dimensions where they got smaller.
Does anyone know how to solve either one of those behaviors?
Regards
Tobias
Edit: Code for animations:
Size normalSize = GetNormalElementSize(new Size(ActualWidth, ActualHeight));
DoubleAnimation widthAnimation = new DoubleAnimation
{
From = normalSize.Width,
To = normalSize.Width * 2,
Duration = TimeSpan.FromMilliseconds(750),
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut }
};
DoubleAnimation heightAnimation = new DoubleAnimation
{
From = normalSize.Height,
To = normalSize.Height * 2,
Duration = TimeSpan.FromMilliseconds(750),
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut }
};
ZoomedElement.Height = normalSize.Height;
ZoomedElement.Width = normalSize.Width;
Storyboard.SetTargetProperty(widthAnimation, new PropertyPath(FrameworkElement.WidthProperty));
Storyboard.SetTarget(widthAnimation, ZoomedElement);
Storyboard.SetTargetProperty(heightAnimation, new PropertyPath(FrameworkElement.HeightProperty));
Storyboard.SetTarget(heightAnimation, ZoomedElement);
Storyboard zoomStoryboard = new Storyboard();
zoomStoryboard.Children.Add(heightAnimation);
zoomStoryboard.Children.Add(widthAnimation);
zoomStoryboard.CurrentTimeInvalidated += zoomStoryboard_CurrentTimeInvalidated;
zoomStoryboard.Begin();
I guess you are using RenderTransformation, try LayoutTransformation instead.

Custom panel layout doesn't work as expected when animating (WPF)

I've got a custom (and getting complex) TabControl. It's a gathering of many sources, plus my own wanted features. In it is a custom Panel to show the headers of the TabControl. Its features are to compress the size of the TabItems until they reached their minimum, and then activates scrolling features (in the Panel, again). There is also another custom panel to hold a single button, that renders on the right of the TabItems (it's a "new tab" button).
It all works great, until I try to animate the scrolling.
Here are some relevant snippets :
In the CustomTabPanel (C#, overriding Panel and implementing IScrollInfo):
private readonly TranslateTransform _translateTransform = new TranslateTransform();
public void LineLeft()
{
FirstVisibleIndex++;
var offset = HorizontalOffset + _childRects[0].Width;
if (offset < 0 || _viewPort.Width >= _extent.Width)
offset = 0;
else
{
if (offset + _viewPort.Width > _extent.Width)
offset = _extent.Width - _viewPort.Width;
}
_offset.X = offset;
if (_scrollOwner != null)
_scrollOwner.InvalidateScrollInfo();
//Animate the new offset
var aScrollAnimation = new DoubleAnimation(_translateTransform.X, -offset,
new Duration(this.AnimationTimeSpan), FillBehavior.HoldEnd) { AccelerationRatio = 0.5, DecelerationRatio = 0.5 };
aScrollAnimation.Completed += ScrollAnimationCompleted;
_translateTransform.BeginAnimation(TranslateTransform.XProperty, aScrollAnimation , HandoffBehavior.SnapshotAndReplace);
//End of animation
// These lines are the only ones needed if we remove the animation
//_translateTransform.X = -offset;
//InvalidateMeasure();
}
void ScrollAnimationCompleted(object sender, EventArgs e)
{
InvalidateMeasure();
}
the _translateTransform is initialized in the constructor :
base.RenderTransform = _translateTransform;
Again, everything is fine if I remove the animation part and just replace it with the commented out lines at the end.
I must also point out that the problem is NOT with the animation itself. That part works out well. The problem is about when I remove some tab items : all the layout then screws up. The TranslateTransformation seems to hold on some wrong value, or something.
Thanks in advance.
Well. As it's often the case, I kept working on the thing, and... answered myself.
Could still be useful for other people, so here was the catch. In the line :
var aScrollAnimation = new DoubleAnimation(_translateTransform.X, -offset, new Duration(this.AnimationTimeSpan), FillBehavior.HoldEnd)
{ AccelerationRatio = 0.5, DecelerationRatio = 0.5 };
the FillBehavior should have been FillBehavior.Stop.
As easy as that!

Resources