Choosing font size on a text path - html5-canvas

I'm working on a radial menu (for a game) and I'm having some trouble with getting text along paths to behave exactly how I'd like it to. Here's an example of the menu at the moment:
I want all the text to be aligned to the center of the node it's in, and for the fontSize to be sufficiently small for the text to fit into the available space. This is achieved fairly trivially for the straight text by scalingthe font and measuring the width until it fits:
var fontSize = 19;
var titleText = new Kinetic.Text({
text: title,
fontSize: fontSize ,
fontFamily: 'Calibri',
fill: 'white',
rotationDeg: rotation
});
while (titleText.getWidth() > availableSpace)
titleText.setFontSize(--fontSize);
However, this approach can't be applied to the curved text because (as far as I can see) there's no way to measure how long a string is when placed along a path.
How should I achieve centering and scaling of text when it is placed along a path?

Here is a hack in terms of centering, but you get the idea:
http://jsfiddle.net/ysaLp/2/
basically when you have each item in your list, you want to add blank spaces to the beginning, dependent on total space in the arc.
you can do (again, inside a loop):
textPathObjectName.setText(' ' + textPathObjectName.getText()); //dependent on the length of the width of text ( .getTextWidth() )
In terms of resizing you get : http://jsfiddle.net/ysaLp/5/
for (var i = 1; i < 5 && titleText.getTextWidth() > circumference ; i++)
titleText.setFontSize(titleText.getFontSize()-i);
which lowers your font until it "fits" into your wedge's circumference
It also helps to start your font at a lower value, since you are calculating the path data right after, set the font size at 15 first.

Related

Technique for drawing theme element (i.e. how to not stretch draw)?

I'm looking at drawing a custom theme element onto a device content.
For example's sake, i will use the HeaderItem from the Windows XP header/listview:
(18×18 px)
Which we can blow up to see a little easier:
Note: I am not using the Theme API, nor am i asking about using the Theme API.
If i have my bitmap, like the one above, how can i draw it in practice?
Stretch draw ruins the style
The important problem that needs solving is how to maintain the important details. You can see the actual Windows XP Header draws the right-edge vertical line nice and crisp:
But if i were to blindly StretchBlt the image, the details become fuzzy:
The issue also happens with theme elements with crisp horizontal feature when the image is stretched vertically. In this case it also messes up the vertical gradient. But some other element have it even more pronounced.
So what is the technique that can be used to address this?
Should i cut 6 px off the top, left, bottom, and right?:
And then rather than drawing 1 image, i draw nine?:
And draw them with various horizontal or vertical stretch rules depending where it is?:
Unstretched
Horizontally stretched
Unstretched
Vertically stretched
Horizontally and vertically stretched
Vertically stretched
Unstretched
Horizontally stretched
Unstretched
This must be a solved problem already; since Windows already solved it, and who knows how many more Widget libraries that support themes.
Microsoft's solution to this problem can be reverse engineered by looking at the NormalBlue.ini file inside Luna.msstyles. Looking at the entry for Header.HeaderItem:
NormalBlue.ini:
[Header.HeaderItem]
bgtype = imagefile
SizingMargins = 8, 8, 3, 4
ContentMargins = 3, 0, 0, 0
ImageFile = Blue\ListViewHeader.bmp
imageCount=5
imageLayout=vertical
sizingType = tile
transparent=true
transparentcolor=255 0 0
FillColorHint = 250 248 243; Average fill color (light beige)
AccentColorHint = 252 194 71; Rollover hilite color (orange)
First we see it references the \Blue\ListViewHeader.bmp:
ImageFile = Blue\ListViewHeader.bmp
Which is:
And then there's the magic piece:
SizingMargins = 8, 8, 3, 4
This corresponds to TMT_SIZINGMARGINS:
TMT_SIZINGMARGINS: The margins used for sizing a non-true-size image.
where you can see some more hints in TmSchema.h:
//---- rendering MARGIN properties ----
TM_PROP(3601, TMT, SIZINGMARGINS, MARGINS) // margins used for 9-grid sizing
"Margins used for 9-grid sizing". This is a reference to the idea that you split up the image into 3x3 grid, and size the chunks independently as appropriate.
And the final piece is the documentation of the MARGINS type in UxTheme.h:
typedef struct _MARGINS
{
int cxLeftWidth; // width of left border that retains its size
int cxRightWidth; // width of right border that retains its size
int cyTopHeight; // height of top border that retains its size
int cyBottomHeight; // height of bottom border that retains its size
} MARGINS, *PMARGINS;
and its documentation:
cxLeftWidth: int - Width of the left border that retains its size.
cxRightWidth: int - Width of the right border that retains its size.
cyTopHeight: int - Height of the top border that retains its size.
cyBottomHeight: int - Height of the bottom border that retains its size.
Chop and Paint
The Luna theme is telling us that when we draw ListViewHeader.bmp, we need to use the sizing margins:
SizingMargins = 8, 8, 3, 4
And cut the image up into 9 pieces (3x3). But rather than using 6px all around (like i said in my question), we need to use the sizes that the designer of the image intended:
Left: 8
Right: 8
Top: 3
Bottom: 4
So given the 18×18 theme element image created in Photoshop by a designer:
The person who created the image said that my drawing code needs to cut off:
left 8 pixels
right 8 pixels
top 3 pixels
bottom 4 pixels
Meaning i then have to draw each of the nine images:
And then stretch draw some parts in certain directions:
Top-left: draw unstretched
Left: stretch vertically
Bottom-left: draw unstretched
Top: draw stretch horizontally
Middle: draw stretched horizontally and vertically
Bottom: draw stretched horizontally
Top-right: draw unstretched
Right: draw stretched vertically
Bottom-right: draw unstretched

dc.js heatmap - make the top row rects to begin at y="0"

I have a dc.js heatmap working:
But I want to add grid lines to it, like so:
You can see that the lines to not match up with the bottom edges of the rects. Inserting the lines themselves is easy, you just start at zero and add 11 lines based on the height of the rects, which in this case will always be 11 / chart.effectiveHeight().
The reason they do not match up, seems to be that the top rect row does not always start at 0, instead, there seems to be a random(?) y position that the chart starts at, this will change with the height of the chart container, eg this y position starts at 5:
If it was consistent, then I could just start appending lines from that number instead of 0, but it is not. I have tried a couple of hacky work arounds, however I am unsure as to how to get the y position of all the rects after they are available in the DOM.
Interestingly the demo heatmap does not have this issue:
Here is the code for the heatmap:
const heat_map = dc.heatMap('#heatmap');
heat_map
.width(0)
.height(0)
.margins(margins)
.dimension(hm_dim)
.group(hm_group)
.keyAccessor(function(d) { return +d.key[0]; })
.valueAccessor(function(d) { return +d.key[1]; })
.colorAccessor(function(d) { return +d.value; })
.colors(color_scale)
.calculateColorDomain()
.yBorderRadius(0)
.xBorderRadius(0)
heat_map.render();
Is there a way to force the rects to begin at 0? Or get the random y position for the top rows? I did have a look at the source code but got a bit lost. Also I thought about creating a false group that would include each rect in the grid, and the grid lines could then be rect borders, but I thought that was a bit heavy handed.
Outlining the cells using CSS
It's easy to outline the cells using CSS:
rect.heat-box {
stroke-width: 1;
stroke: black;
}
Example fiddle.
However, as you point out, this only works if all the cells have values; crossfilter will not create the empty ones and I agree it would be absurd fill them in using a fake group just for some lines.
So, to answer your original question...
Why is there a gap at the top of the chart?
The heatmap calculates an integer size for the cells, and there may be space left over (since the space doesn't divide perfectly).
It's kind of nasty but the heatmap example avoids having extra space by calculating the width and height for the chart using the count of cells in each dimension:
chart
.width(45 * 20 + 80)
.height(45 * 5 + 40)
The default margins are {top: 10, right: 50, bottom: 30, left: 30} so this allocates 45x45 pixels for each cell and adds on the margins to get the right chart size.
Since the heatmap in this example draws 20 columns by 5 rows, it will calculate the cell width and height as 45.
Alternative Answer for Responsive/Resizable Charts
I am revisiting this question after rewriting my heatmap chart to be responsive - using the "ResizeObserver" method outlined in the dc.js resizing examples and Gordon's answer to this question
While specifying the chart width and height for the heatmap in Gordon's answer still works, it does not combine well with the resizing method because resized charts will have their .width and .height set to 'null'. Which means that this rounding issue will reoccur and the heat boxes will be again be offset by a random integer x or y value of anywhere between 0 and 5 (unless you want to write a custom resizing function for heatmaps).
The alternative answer is relatively simple and can be determined by selecting just one heat-box element in the heatmap.
The vertical offset value for the heat boxes is the remainder value when the heat-box y attribute is divided by the heat-box height attribute.
const heatbox_y = heat_map.select('.heat-box').attr('y);
const heatbox_height = heat_map.select('.heat-box').attr('height')
const vertical_offset = heatbox_y % heatbox_height
The modulus % will return the remainder.
The horizontal offset can be determined in the same way.
Thus you can append lines to the chart at regular intervals determined by the heatbox_height + the vertical_offset values.
This will work if you pick any heat-box in the chart, and so it is suitable for instances like this where you cannot guarantee that there will be a heat-box at each x or y level. And it means that you are free to set your chart height and width to 'null' if needed.

openseadragon svg overlay - clickable area is too large

I am working with an OpenSeaDragon image with an overlay (array of overlays?) that has about 500 clickable svg rect elements, implemented using the Overlay.onclick() function
The bounding rectangle for the clickable area varies but is always much larger than the visible rectangle, and often covers neighboring rectangles as well. I have tried messing with margin, border, and padding to no avail. This image show an example, showing the difference. The actual displayed rectangle is the same dimension as the visible text box, while the clickable area is the entire highlighted rectangle.
There does not seem to be a lot of predictability - the clickable area varies in an apparently random way but is always larger than the correct size, up to about twice in both directions. As shown, it's not always centered - I'm not sure it ever is. The proportions remain the same when the image is zoomed in and out.
I'm fairly new at JS, and this involves so many components including D3 and the OSD suite, that I'm not sure where to start. Any suggestions would be appreciated!
For reference, here is the code where the boxes are generated from an array. This was adapted from a single rectangle example, and I have no idea if this was a good way to do this. (I would have liked to have the inside of the box fully transparent except when mousing over it, but that's a whole other problem...)
var overlay = this.viewer.svgOverlay();
len = nodes.length;
var d3Rect = [];
var url = [];
for (var i = 0; i < len; i++) {
var mynode = nodes[i];
d3Rect[i] = d3.select(overlay.node()).append("rect")
.style('fill', '#ffffff')
.style('fill-opacity', '0.05')
.style('stroke', '#000066')
.style('stroke-width', '0.0005')
.style('stroke-opacity', '0.5')
.attr("x", mynode.x1)
.attr("width", mynode.width)
.attr("y", mynode.y1)
.attr("height", mynode.height)
.attr("title", mynode.title)
.attr("href", mynode.link);
overlay.onClick(d3Rect[i].node(), function() {
window.open(this.element.getAttribute("href"), '_blank');
});
}
I am not 100% sure, but it looks like you are creating 1 overlay element of unknown size, then adding all the rects inside it, and then binding the click event to the top overlay instead of the individual rects.
In any case, if the shapes you need are simple rects, you should try using
viewer.addOverlay( element, location, placement, onDraw )
link to docs
One possibility is that you're running into precision problems. What are the dimensions of your image in viewport coordinates? By default the width would be 1, but this might cause rounding issues when zoomed in on these SVG elements. Try making your image 1000 wide and multiply all of your overlay coordinates by 1000 and see if that helps.

How to get the Position & Dimension of a Shape in Powerpoint?

I'm playing around with OpenXmlSDK to see if it's a viable solution for our Powerpoint needs. One thing that is required is the ability to position shapes in the Powerpoint. I've been searching around for a way to get the position of a Shape, but have only come across is the MSDN "How To" http://msdn.microsoft.com/en-us/library/cc850828.aspx and a Position class (but no way to get it from a Shape) http://msdn.microsoft.com/en-us/library/office/documentformat.openxml.wordprocessing.position%28v=office.14%29.aspx.
How do I do something like:
PresentationDocument presentationDocument = PresentationDocument.Open("C:\\MyDoc.pptx", true);
IdPartPair pp = presentationDocument.PresentationPart.SlideParts.First().Parts.FirstOrDefault();
var shape = pp.OpenXmlPart;
// How do I get the position and dimensions?
You have 2 variables for the dimension of the shape :
- Offset gives the position of the top corner of your shape
- Extents gives the size off your shape
shape.ShapeProperties.Transform2D.Offset.X //gives the x position of top left corner
shape.ShapeProperties.Transform2D.Offset.Y //gives the y position of top left corner
shape.ShapeProperties.Transform2D.Extents.X //gives the x size of the shape : the width
shape.ShapeProperties.Transform2D.Extents.Y //gives the y size of the shape : the height
Go through the XML for the slide in question and look for xfrm elements, which should contain off (offset) and ext (extent) sub-elements. The measurements are in EMUs (see last page of Wouter van Vugt's document).
Sometimes ShapeProperties is not displayed as a Shape property, you must write
var sP = ((DocumentFormat.OpenXml.Presentation.Shape)shape).ShapeProperties;
After you can use Transform2D and find coordinates as Deunz wrote.

How to shift pixels of a pixmap efficient in Qt4

I have implemented a marquee text widget using Qt4. I painted the text content onto a pixmap first. And then paint a portion of this pixmap onto a paint device by calling painter.drawTiledPixmap(offsetX, offsetY, myPixmap)
My Imagination is that, Qt will fill the whole marquee text rectangle with the content from myPixmap.
Is there a ever faster way, to shift all existing content to left by 1px and than fill the newly exposed 1px wide and N-px high area with the content from myPixmap?
Well. This is a trick I used to do with slower hardware back in the old days. Basically, the image buffer is allocated twice as wide as needed with 1 extra line at the beginning. Build the image to the left of the buffer. Then draw the image repeatedly with the buffer advancing 1 pixel at a time in the buffer.
int w = 200;
int h = 100;
int rowBytes = w * sizeof(QRgb) * 2; // line buffer is twice as the width
QByteArray buffer(rowBytes * (h + 1), 0xFF); // 1 more line than the height
uchar * p = (uchar*)buffer.data() + rowBytes; // start drawing the image content at 2nd line
QImage image(p, w, h, rowBytes, QImage::Format_RGB32); // 1st line is used as the padding at the start of scroll
image.fill(qRgb(255, 0, 0)); // well. do something to the image
p = image.bits() - rowBytes / 2; // start scrolling at the middle of the 1st (blank) line
for(int i=0;i<w;++i, p+=sizeof(QRgb)) {
QImage scroll(p, w, h, rowBytes, QImage::Format_RGB32); // scrool 1 pixel at a time
scroll.save(QString("%1.png").arg(i));
}
I am not sure this will be any faster than just change the offset of the image and draw it strait. The hardware today is really powerful which renders a lot of old tricks useless. But it's fun to play obscure tricks. :)
Greetings,
one possibility to achieve this would be to:
Create a QGraphicsScene + View and put the pixmap on that twice (as QGraphicsPixmapItem), so they are right next to each other.
Size the view to fit the size of the (one) pixmap.
Then, instead of repainting the pixmap, you simply reposition the view's viewport, moving from one pixmap to the next.
Jump back at the end to create the loop.
This may or may not be faster (in terms of performance) - I have not tested it. But may be worth a try, if only for the sake of experiment.
Your approach is probably one of the fastest one since you use low level painting methods. You can implement an intermediate approach between low level painting and the QGraphicsScene option : using a scroll area containing a label.
Here is a sample of code that create a new scroll area containing a text label. You may scroll the label automatically using a QTimer to trigger the scrolling effect, that gives you a nice marquee widget.
QScrollArea *scrollArea = new QScrollArea();
// ensure that scroll bars never show
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QLabel *label = new QLabel("your scrolling text");
// resize the scroll area : 50px length and an height equals to its content height.
scrollArea->resize(50, label->size().height());
scrollArea->setWidget(label);
label->show(); // optionnal if the scroll area is not yet visible
The text label inside the scroll area can be moved from left to right by one pixel using the QScrollArea::scrollContentsBy(int dx, int dy) with a dx parameter equals to -1.
Why not just do it on a pixel by pixel basis? Due to the way caches work writing the pixel to the one before it all the way until you get to the end. Then you can fill the final column by reading from your other image.
Its then pretty easy to SIMD optimise it as well; though you start getting into per-platform optimisations at this point.

Resources