SMS fonts, once and for all, part 3
Right, in the past two articles (first here and second here) we established two set of routines in the same class. In short, what we have finished is as such:
- The ability to check if a font is installed
- The ability to traverse and find the font used by an html element
- The ability to measure text
Now the last point on that list is extremely tricky. While measuring static, one-liner text (read: text that does not break) is indeed working as expected, perfect for captions and other forms of lengths — more complex segments of html measurement is proving to require more work.
To illustrate what we want to do here:
As you can see from this highly scientific diagram i slapped together in paint (the typos are free by the way), by setting the “display” css property to “scroll” (please see css documentation for the display property here), we basically turn the yellow DIV element into a Delphi like TScrollbox. So what we are effectively looking for is clientwidth/clientheight for a TScrollbox which has content more content than is being displayed.
HTML5 is a bit silly regarding this, in that the scrollWidth and scrollHeight properties, which contains the full size of the blue box (the values we want to get) are only there if we have set the display property right — and that the content actually spans beyond the visible display. If not, scrollWidth or scrollHeight may be nil (undefined) depending on what fits within it. This is why i scale the element to 4×4 pixels before measuring – to make sure both values are kosher.
And now for the problem (or feature, depending on how you regard the collected wisdom of the WC3), we have to write to the style for the element, but read back values from the calculated style. Why? Because the world of browsers are not (believe it or not) an exact science. As discussed ad infinitum by now, the browser can assign several styles to an element (hence the “cascading” word in CSS) which results in a final style simply called “the calculated style”. Which the WC3 in their infinite wisdom have decided to store in a completely separate location (document.topframe as opposed to, oh say, element.calculatedStyle).
To make things even more fun, the browser may in fact end up breaking things up to much when we scale down — so truth be told, we may fall back to clientWidth/clientHeight or offsetWidth/offsetHeight instead. Confused yet? Your welcome.
Fixing it
Now, the problem with the old model was simply that by setting the element to 4×4 pixels in size, I was not in fact measuring the actual content – instead I was measuring the largest word in the string. Which gave some pretty funky visual results to be mild.
I have since updated the routine and I am very happy to report that it now works as expected — and finally my casebook demo auto-calculates the height of a new-item perfectly (at least under webkit). I have yet to do battle with Mozilla and Opera, but at least I suspect that Opera will have it’s ducks in a row.
So here is the updated version. I will omit the word “final” because the browser can still throw some quirks at us – but at least now we know what we are dealing with!
Something awesome
QTXLibrary expands the normal TW3CustomControl with attribute storage. I decided to add two methods for measuring text directly on the level of TW3Customcontrol. What it does is grab the font information for that element directly – so you dont have to worry about those parameters, and just provide the text you want to measure. Used the fixed version if you know the width of the target placeholder. Pretty cool!
TQTXTextMetric = Record tmWidth: Integer; tmHeight: Integer; function toString:String; End; TQTXFontInfo = Record fiName: String; fiSize: Integer; function toString:String; End; TQTXFontDetector = Class(TObject) private FBaseFonts: array of string; FtestString: String = "mmmmmmmmmmlli"; FtestSize: String = '72px'; Fh: THandle; Fs: THandle; FdefaultWidth: Variant; FdefaultHeight: Variant; public function Detect(aFont:String):Boolean; function MeasureText(aFontInfo:TQTXFontInfo; aContent:String):TQTXTextMetric;overload; function MeasureText(aFontInfo:TQTXFontInfo; aFixedWidth:Integer; aContent:String):TQTXTextMetric;overload; function MeasureText(aFontName:String;aFontSize:Integer; aContent:String):TQTXTextMetric;overload; function MeasureText(aFontName:String;aFontSize:Integer; aFixedWidth:Integer; aContent:String):TQTXTextMetric;overload; function getFontInfo(const aHandle:THandle):TQTXFontInfo; Constructor Create;virtual; End; //############################################################################ // TQTXFontInfo //############################################################################ function TQTXFontInfo.toString:String; begin result:=Format('%s %dpx',[fiName,fiSize]); end; //############################################################################ // TQTXFontDetector //############################################################################ Constructor TQTXFontDetector.Create; var x: Integer; begin inherited Create; FBaseFonts.add('monospace'); FBaseFonts.add('sans-serif'); FBaseFonts.add('serif'); Fh:=browserApi.document.body; Fs:=browserApi.document.createElement("span"); Fs.style.fontSize:=FtestSize; Fs.innerHTML := FtestString; FDefaultWidth:=TVariant.createObject; FDefaultHeight:=TVariant.createObject; if FBaseFonts.Count>0 then for x:=FBaseFonts.low to FBaseFonts.high do begin Fs.style.fontFamily := FbaseFonts[x]; Fh.appendChild(Fs); FdefaultWidth[FbaseFonts[x]] := Fs.offsetWidth; FdefaultHeight[FbaseFonts[x]] := Fs.offsetHeight; Fh.removeChild(Fs); end; end; function TQTXFontDetector.getFontInfo(const aHandle:THandle):TQTXFontInfo; var mName: String; mSize: Integer; mData: Array of string; x: Integer; Begin result.fiSize:=-1; if aHandle.valid then begin mName:=w3_getStyleAsStr(aHandle,'font-family'); mSize:=w3_getStyleAsInt(aHandle,'font-size'); if length(mName)>0 then begin asm @mData = (@mName).split(","); end; if mData.Length>0 then Begin for x:=mData.low to mData.high do begin if Detect(mData[x]) then begin result.fiName:=mData[x]; result.fiSize:=mSize; break; end; end; end; end; end; end; function TQTXFontDetector.Detect(aFont:String):Boolean; var x: Integer; Begin aFont:=trim(aFont); if aFont.Length>0 then Begin if FBaseFonts.Count>0 then for x:=FBaseFonts.low to FBaseFonts.high do begin Fs.style.fontFamily:=aFont + ',' + FbaseFonts[x]; Fh.appendChild(Fs); result:= (Fs.offsetWidth <> FdefaultWidth[FBaseFonts[x]]) and (Fs.offsetHeight <> FdefaultHeight[FBaseFonts[x]]); Fh.removeChild(Fs); if result then break; end; end; end; function TQTXFontDetector.MeasureText(aFontInfo:TQTXFontInfo; aFixedWidth:Integer; aContent:String):TQTXTextMetric; Begin result:=MeasureText(aFontInfo.fiName,aFontInfo.fiSize,aFixedWidth,aContent); end; function TQTXFontDetector.MeasureText(aFontInfo:TQTXFontInfo; aContent:String):TQTXTextMetric; Begin result:=MeasureText(aFontInfo.fiName,aFontInfo.fiSize,aContent); end; function TQTXFontDetector.MeasureText(aFontName:String;aFontSize:Integer; aContent:String):TQTXTextMetric; var mElement: THandle; Begin if Detect(aFontName) then begin aContent:=trim(aContent); if length(aContent)>0 then begin mElement:=BrowserAPi.document.createElement("p"); if (mElement) then begin mElement.style['font-family']:=aFontName; mElement.style['font-size']:=TInteger.toPxStr(aFontSize); mElement.style['overflow']:='scroll'; mElement.style['display']:='inline-block'; mElement.style['white-space']:='nowrap'; mElement.innerHTML := aContent; Fh.appendChild(mElement); result.tmWidth:=mElement.scrollWidth; result.tmHeight:=mElement.scrollHeight; Fh.removeChild(mElement); end; end; end; end; function TQTXFontDetector.MeasureText(aFontName:String;aFontSize:Integer; aFixedWidth:Integer; aContent:String):TQTXTextMetric; var mElement: THandle; Begin if Detect(aFontName) then begin aContent:=trim(aContent); if length(aContent)>0 then begin mElement:=BrowserAPi.document.createElement("p"); if (mElement) then begin mElement.style['font-family']:=aFontName; mElement.style['font-size']:=TInteger.toPxStr(aFontSize); mElement.style['overflow']:='scroll'; mElement.style.maxWidth:=TInteger.toPxStr(aFixedWidth); mElement.style.width:=TInteger.toPxStr(aFixedWidth); mElement.innerHTML := aContent; Fh.appendChild(mElement); result.tmWidth:=mElement.scrollWidth; result.tmHeight:=mElement.scrollHeight; Fh.removeChild(mElement); end; end; end; end;
You must be logged in to post a comment.