<body><div id="__MailbirdStyleContent" style="font-size: 10pt;font-family: Arial;color: #000000;text-align: left" dir="ltr">
                                        Also that platform-UI-scaling issue on macOS and Windows (32-bit, default.manifest). My addition to the Info.plist [1] (macOS) did not do the trick. It's always magnified on Retina displays.<div><br></div><div>Best,</div><div>Marcel</div><div><br></div><div>[1] https://github.com/squeak-smalltalk/squeak-app/blob/squeak-trunk/templates/macos/Squeak.app/Contents/Info.plist</div><div class="mb_sig"></div><blockquote class='history_container' type='cite' style='border-left-style:solid;border-width:1px; margin-top:20px; margin-left:0px;padding-left:10px;'>
                        <p style='color: #AAAAAA; margin-top: 10px;'>Am 08.03.2022 02:58:10 schrieb Eliot Miranda <eliot.miranda@gmail.com>:</p><div style='font-family:Arial,Helvetica,sans-serif'>Hi Marcel,<div><br></div><div>    I’m happy with the latest commit.  It and recent commits are the result of use with Virtend and solving a few stability issues found there.  The only issue that I know needs to be addressed before release is the bitblt scroll up crash on arm64.  I’m hoping that Bruce can give me a test case that doesn’t involve the UI.<br><br><div dir="ltr"><span style="background-color: rgba(255, 255, 255, 0);">_,,,^..^,,,_ (phone)</span></div><div dir="ltr"><br><blockquote type="cite">On Mar 7, 2022, at 9:00 AM, Marcel Taeumel <marcel.taeumel@hpi.de> wrote:<br><br></blockquote></div><blockquote type="cite"><div dir="ltr"><div id="__MailbirdStyleContent" style="font-size: 10pt;font-family: Arial;color: #000000;text-align: left" dir="ltr">
                                        Thanks! We will hopefully have the next release candidate of the OSVM soon. :-)<div><br></div><div>Best,</div><div>Marcel</div><div class="mb_sig"></div><blockquote class="history_container" type="cite" style="border-left-style:solid;border-width:1px; margin-top:20px; margin-left:0px;padding-left:10px;">
                        <p style="color: #AAAAAA; margin-top: 10px;">Am 06.03.2022 14:59:18 schrieb Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com>:</p><div style="font-family:Arial,Helvetica,sans-serif">
<div dir="ltr"><div>Sorry, it displayed the many diffs from Graphics-nice.442 rather than the few from Graphics-mt.491...</div><div>That's only 3 new composition rules.<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Le dim. 6 mars 2022 à 14:55, <<a href="mailto:commits@source.squeak.org">commits@source.squeak.org</a>> a écrit :<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Nicolas Cellier uploaded a new version of Graphics to project The Trunk:<br>
<a href="http://source.squeak.org/trunk/Graphics-nice.492.mcz" rel="noreferrer" target="_blank">http://source.squeak.org/trunk/Graphics-nice.492.mcz</a><br>
<br>
==================== Summary ====================<br>
<br>
Name: Graphics-nice.492<br>
Author: nice<br>
Time: 6 March 2022, 2:55:01.566363 pm<br>
UUID: 9b341ab2-f970-4afc-a140-d1c255a5ca41<br>
Ancestors: Graphics-nice.442, Graphics-mt.491<br>
<br>
Merge: Graphics-nice.442, Graphics-mt.491<br>
<br>
Note: the new BitBlt rules have been corrected in VMMaker.oscog-nice.3170/eem.3171, that is in VM generated from 2022 February 24 and on.<br>
<br>
=============== Diff against Graphics-nice.442 ===============<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont class>>defaultFallbackTextStyle (in category 'fallback') -----<br>
+ defaultFallbackTextStyle<br>
+       "Answer a text style that has fonts that support a lot of codepoints."<br>
+       <br>
+       ^ TextConstants at: #DefaultFallbackTextStyle ifAbsent: [TextStyle default]!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont class>>localeChanged (in category 'fallback') -----<br>
+ localeChanged<br>
+       self setupDefaultFallbackTextStyle.!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont class>>setupDefaultFallbackFontTo: (in category 'fallback') -----<br>
+ setupDefaultFallbackFontTo: aFont<br>
+ <br>
+       self setupDefaultFallbackTextStyleTo: (TextStyle fontArray: {aFont}).!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont class>>setupDefaultFallbackTextStyle (in category 'fallback') -----<br>
+ setupDefaultFallbackTextStyle<br>
+ <br>
+       | defaultStyle |<br>
+       defaultStyle := self defaultFallbackTextStyle.<br>
+       TextStyle knownTextStylesWithoutDefault do: [:styleName |<br>
+               (TextStyle named: styleName) fontArray do: [:each |<br>
+                       each setupDefaultFallbackTextStyleTo: defaultStyle]].!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont class>>setupDefaultFallbackTextStyleTo: (in category 'fallback') -----<br>
+ setupDefaultFallbackTextStyleTo: aTextStyle<br>
+ <br>
+       TextConstants at: #DefaultFallbackTextStyle put: aTextStyle.<br>
+ !<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>asNewTextStyle (in category 'converting') -----<br>
+ asNewTextStyle<br>
+       "Answer a new text style where the receiver is the default font. Try to lookup the an existing #textStyle so that TextFontChange can be used in views."<br>
+       <br>
+       | newTextStyle |<br>
+       newTextStyle := self textStyleOrNil<br>
+               ifNil: [TextStyle fontArray: {self}]<br>
+               ifNotNil: [:style | style copy].<br>
+       newTextStyle defaultFontIndex: (newTextStyle fontIndexOfPointSize: self pointSize).     <br>
+       ^ newTextStyle!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>asPointSize: (in category 'converting') -----<br>
+ asPointSize: differentPointSize<br>
+       "Convert the receiver into a different point size. Compared to #pointSize:, this operation does not modify the receiver but tries to lookup another font object or create one on-the-fly."<br>
+ <br>
+       self pointSize = differentPointSize ifTrue: [^ self].<br>
+ <br>
+       ^ self class<br>
+               familyName: self familyName<br>
+               pointSize: differentPointSize<br>
+               emphasized: self emphasis!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>asRegular (in category 'converting') -----<br>
+ asRegular<br>
+       "Try to lookup the receiver with normal emphasis. If the receiver itself looks bold face, this might be okay. Rely on what is registered on the font family's text style. Not that this is different from #emphasized: with 0, which does nothing."<br>
+       <br>
+       self emphasis = 0 ifTrue: [^ self].<br>
+       ^ self textStyleOrNil<br>
+               ifNil: [self]<br>
+               ifNotNil: [:style | style fontOfPointSize: self pointSize]!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>emphasis (in category 'accessing') -----<br>
+ emphasis<br>
+ <br>
+       ^ 0!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>fallbackFont (in category 'accessing') -----<br>
+ fallbackFont<br>
+       "Answers the fallbackFont for the receiver. The fallback font must be some derivative of the receiver since it will not be asked to install itself properly on the target BitBlt so rendering a completely different font here is simply not possible. The default implementation uses a synthetic font that maps all characters to question marks."<br>
+ <br>
+       ^ FixedFaceFont new errorFont baseFont: self!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>fallbackFont: (in category 'initialize-release') -----<br>
+ fallbackFont: aFont<br>
+       "Reset fallback font. Ignore. See #fallbackFont."!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>hasFixedWidth (in category 'testing') -----<br>
+ hasFixedWidth<br>
+       "Answer whether the receiver is a monospaced/fixed-width/non-proportional font. See TextStyle class >> #defaultFixed."<br>
+ <br>
+       ^ (self widthOf: $.) = (self widthOf: $w)!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>isSynthetic (in category 'testing') -----<br>
+ isSynthetic<br>
+       ^ false!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>larger (in category 'converting') -----<br>
+ larger<br>
+       "Answer a font that is about 20% larger than the receiver but has the same font family and emphasis. Round to 0.5 points to not yield so many different font instances."<br>
+       <br>
+       ^ self asPointSize: ((self pointSize asFloat * 1.2) roundTo: 0.5)!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>lineGap (in category 'accessing') -----<br>
+ lineGap<br>
+ <br>
+       ^ 2 "pre-rendered legacy fonts"!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>lineGapSlice (in category 'accessing') -----<br>
+ lineGapSlice<br>
+ <br>
+       ^ 1 "pre-rendered legacy fonts"!<br>
<br>
Item was changed:<br>
  ----- Method: AbstractFont>>lineGrid (in category 'accessing') -----<br>
  lineGrid<br>
        "Answer the relative space between lines"<br>
<br>
+       ^ self height + self lineGap!<br>
-       ^self subclassResponsibility!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>maxAscii (in category 'accessing') -----<br>
+ maxAscii<br>
+ <br>
+       self flag: #deprecated.<br>
+       ^ self maxCodePoint!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>maxCodePoint (in category 'accessing') -----<br>
+ maxCodePoint<br>
+       "Answer the largest code point that the receiver can translate into glyphs. Use the range from #minCodePoint to #maxCodePoint to configure a list of #fallbackFont's. Note that subclasses may insert 'holes' via #hasGlyphOf: test such as StrikeFont's internal xTable."<br>
+       <br>
+       ^ 16r10FFFF "Unicode uses 21-bit but 16r110000 to 16r1FFFFF are not valid code points. See <a href="https://www.unicode.org/versions/stats/" rel="noreferrer" target="_blank">https://www.unicode.org/versions/stats/</a>."!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>minAscii (in category 'accessing') -----<br>
+ minAscii<br>
+ <br>
+       self flag: #deprecated.<br>
+       ^ self minCodePoint!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>minCodePoint (in category 'accessing') -----<br>
+ minCodePoint<br>
+       "Answer the smallest code point that the receiver can translate into glyphs. Use the range from #minCodePoint to #maxCodePoint to configure a list of #fallbackFont's. Note that subclasses may insert 'holes' via #hasGlyphOf: test such as StrikeFont's internal xTable."<br>
+       <br>
+       ^ 0!<br>
<br>
Item was changed:<br>
  ----- Method: AbstractFont>>pixelSize (in category 'accessing') -----<br>
  pixelSize<br>
        "Make sure that we don't return a Fraction"<br>
+       ^ (TextStyle pointsToPixels: self pointSize) rounded!<br>
-       ^ TextStyle pointsToPixels: self pointSize!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>pixelsPerInch (in category 'accessing') -----<br>
+ pixelsPerInch<br>
+       "Answers the PPI reference for the pre-rendered receiver."<br>
+       <br>
+       ^ (self height * 72 / self pointSize asFloat) rounded!<br>
<br>
Item was changed:<br>
  ----- Method: AbstractFont>>printShortDescriptionOn: (in category 'printing') -----<br>
  printShortDescriptionOn: aStream<br>
+       aStream nextPutAll: self familyName!<br>
-       aStream space; nextPutAll: self familyName!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>reset (in category 'initialize-release') -----<br>
+ reset<br>
+       "Clear all caches."!<br>
<br>
Item was changed:<br>
  ----- Method: AbstractFont>>sampleText (in category 'example') -----<br>
  sampleText<br>
<br>
        | text |<br>
+       text := self isSymbolFont<br>
+               ifTrue: [self symbolSample asText]<br>
-       text := (self isSymbolFont or: [(self basicHasGlyphOf: $a) not])<br>
-               ifTrue: [Text symbolSample]<br>
                ifFalse: [Text textSample].<br>
        text addAttribute: (TextFontReference toFont: self).<br>
        ^ text!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>setupDefaultFallbackTextStyleTo: (in category 'initialize-release') -----<br>
+ setupDefaultFallbackTextStyleTo: aTextStyle<br>
+ <br>
+ "     | fonts f |<br>
+       fonts := aTextStyle fontArray.<br>
+       f := fonts first.<br>
+       f familyName = self familyName ifTrue: [^ self].<br>
+       1 to: fonts size do: [:i |<br>
+               self height > (fonts at: i) height ifTrue: [f := fonts at: i].<br>
+       ]."<br>
+ <br>
+       self derivativeFonts do: [:each |<br>
+               each setupDefaultFallbackTextStyleTo: aTextStyle].<br>
+ <br>
+       self flag: #todo. "mt: Figure out a way to lookup useful fallback fonts."<br>
+ <br>
+       self fallbackFont: nil.<br>
+       self reset.!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>smaller (in category 'converting') -----<br>
+ smaller<br>
+       "Answer a font that is about 20% smaller than the receiver but has the same font family and emphasis. Round to 0.5 points to not yield so many different font instances."<br>
+ <br>
+       ^ self asPointSize: ((self pointSize asFloat * 0.8) roundTo: 0.5)!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>symbolSample (in category 'example') -----<br>
+ symbolSample<br>
+       "Variation of Text class >> #symbolSample, which uses the receiver's available code points. This is important for fonts such as Wingdings."<br>
+       <br>
+       ^ String streamContents: [:stream | | start lineLength character |<br>
+               lineLength := 0.<br>
+               (start := self minCodePoint max: 33) to: (self maxCodePoint min: start + 200) do: [:codePoint |<br>
+                       (self hasGlyphOf: (character := Character value: codePoint))<br>
+                               ifTrue: [stream nextPut: character.<br>
+                                       ((lineLength := lineLength + 1) > 30) ifTrue: [<br>
+                                               lineLength := 0.<br>
+                                               stream cr]]]]!<br>
<br>
Item was changed:<br>
  ----- Method: AbstractFont>>textStyle (in category 'accessing') -----<br>
  textStyle<br>
+       "Answer an instance of TextStyle that (most likely) includes the receiver. Note that if the receiver is used in more than one style, only answer the most prominent one."<br>
+ <br>
+       ^ self textStyleOrNil ifNil: [TextStyle fontArray: {self}]!<br>
-       ^ TextStyle actualTextStyles detect:<br>
-               [:aStyle | aStyle fontArray includes: self] ifNone: [ TextStyle fontArray: { self } ]!<br>
<br>
Item was added:<br>
+ ----- Method: AbstractFont>>textStyleOrNil (in category 'accessing') -----<br>
+ textStyleOrNil<br>
+       "Like #textStyle but avoid creating a new style for orphaned fonts."<br>
+ <br>
+       ^ TextStyle named: self textStyleName!<br>
<br>
Item was changed:<br>
  ----- Method: BMPReadWriter class>>readAllFrom: (in category 'testing') -----<br>
  readAllFrom: fd<br>
        "MessageTally spyOn:[BMPReadWriter readAllFrom: FileDirectory default]"<br>
        fd fileNames do:[:fName|<br>
                (fName endsWith: '.bmp') ifTrue:[<br>
+                       [Form fromBinaryStream: (fd readOnlyFileNamed: fName)] ifError: [].<br>
-                       [Form fromBinaryStream: (fd readOnlyFileNamed: fName)] on: Error do:[:nix].<br>
                ].<br>
        ].<br>
        fd directoryNames do:[:fdName|<br>
                self readAllFrom: (fd directoryNamed: fdName)<br>
        ].!<br>
<br>
Item was changed:<br>
  ----- Method: BMPReadWriter>>nextPutImage: (in category 'writing') -----<br>
  nextPutImage: aForm<br>
        | bhSize rowBytes rgb data colorValues depth image ppw scanLineLen pixline |<br>
        depth := aForm depth.<br>
        depth := #(1 4 8 32 ) detect: [ :each | each >= depth].<br>
        image := aForm asFormOfDepth: depth.<br>
        image unhibernate.<br>
        bhSize := 14.  "# bytes in file header"<br>
        biSize := 40.  "info header size in bytes"<br>
        biWidth := image width.<br>
        biHeight := image height.<br>
        biClrUsed := depth = 32 ifTrue: [0] ifFalse:[1 << depth].  "No. color table entries"<br>
        bfOffBits := biSize + bhSize + (4*biClrUsed).<br>
        rowBytes := ((depth min: 24) * biWidth + 31 // 32) * 4.<br>
        biSizeImage := biHeight * rowBytes.<br>
<br>
        "Write the file header"<br>
        stream position: 0.<br>
        stream nextLittleEndianNumber: 2 put: 19778.  "bfType = BM"<br>
        stream nextLittleEndianNumber: 4 put: bfOffBits + biSizeImage.  "Entire file size in bytes"<br>
        stream nextLittleEndianNumber: 4 put: 0.  "bfReserved"<br>
        stream nextLittleEndianNumber: 4 put: bfOffBits.  "Offset of bitmap data from start of hdr (and file)"<br>
<br>
        "Write the bitmap info header"<br>
        stream position: bhSize.<br>
        stream nextLittleEndianNumber: 4 put: biSize.  "info header size in bytes"<br>
        stream nextLittleEndianNumber: 4 put: image width.  "biWidth"<br>
        stream nextLittleEndianNumber: 4 put: image height.  "biHeight"<br>
        stream nextLittleEndianNumber: 2 put: 1.  "biPlanes"<br>
        stream nextLittleEndianNumber: 2 put: (depth min: 24).  "biBitCount"<br>
        stream nextLittleEndianNumber: 4 put: 0.  "biCompression"<br>
        stream nextLittleEndianNumber: 4 put: biSizeImage.  "size of image section in bytes"<br>
        stream nextLittleEndianNumber: 4 put: 2800.  "biXPelsPerMeter"<br>
        stream nextLittleEndianNumber: 4 put: 2800.  "biYPelsPerMeter"<br>
        stream nextLittleEndianNumber: 4 put: biClrUsed.<br>
        stream nextLittleEndianNumber: 4 put: 0.  "biClrImportant"<br>
        biClrUsed > 0 ifTrue: [<br>
                "write color map; this works for ColorForms, too"<br>
                colorValues := image colormapIfNeededForDepth: 32.<br>
                1 to: biClrUsed do: [:i |<br>
                        rgb := colorValues at: i.<br>
                        0 to: 24 by: 8 do: [:j | stream nextPut: (rgb >> j bitAnd: 16rFF)]]].<br>
<br>
        depth < 32 ifTrue: [<br>
                "depth = 1, 4 or 8."<br>
                data := image bits asByteArray.<br>
                ppw := 32 // depth.<br>
                scanLineLen := biWidth + ppw - 1 // ppw * 4.  "# of bytes in line"<br>
                1 to: biHeight do: [:i |<br>
                        stream next: scanLineLen putAll: data startingAt: (biHeight-i)*scanLineLen+1.<br>
                ].<br>
        ] ifFalse: [<br>
                data := image bits.<br>
                pixline := ByteArray new: (((biWidth * 3 + 3) // 4) * 4).<br>
                1 to: biHeight do:[:i |<br>
                        self store24BitBmpLine: pixline from: data startingAt: (biHeight-i)*biWidth+1 width: biWidth.<br>
                        stream nextPutAll: pixline.<br>
                ].<br>
        ].<br>
+       stream position = (bfOffBits + biSizeImage) ifFalse: [self error: 'Write failure' translated].<br>
-       stream position = (bfOffBits + biSizeImage) ifFalse: [self error:'Write failure'].<br>
        stream close.!<br>
<br>
Item was changed:<br>
  ----- Method: CharacterScanner>>primScanCharactersFrom:to:in:rightX:stopConditions:kern: (in category 'scanning') -----<br>
  primScanCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta<br>
+       "Primitive. This is the inner loop of text display--but see #scanCharactersFrom:to:in:rightX: which would get the string, stopConditions and displaying from the instance. March through sourceString from startIndex to stopIndex. If any character is flagged with a non-nil entry in stops, then return the corresponding value. Determine width of each character from xTable, indexed by map. If destX would exceed rightX, then return stops at: 258. Advance destX by the width of the character. If stopIndex has been reached, then return stops at: 257. <br>
+       <br>
+       Optional. See Object documentation whatIsAPrimitive.<br>
+       <br>
+       NOTE THAT this primitive does only work for our legacy StrikeFont because #setActualFont: needs #xTable and #characterToGlyphMap, which are both not available for TTCFont this way and thus we end up calling #widthOf: manually on the font."<br>
+       <br>
-       "Primitive. This is the inner loop of text display--but see <br>
-       scanCharactersFrom: to:rightX: which would get the string, <br>
-       stopConditions and displaying from the instance. March through source <br>
-       String from startIndex to stopIndex. If any character is flagged with a <br>
-       non-nil entry in stops, then return the corresponding value. Determine <br>
-       width of each character from xTable, indexed by map. <br>
-       If dextX would exceed rightX, then return stops at: 258. <br>
-       Advance destX by the width of the character. If stopIndex has been<br>
-       reached, then return stops at: 257. Optional. <br>
-       See Object documentation whatIsAPrimitive.<br>
-       Historical note: this primitive has been unusable since about Squeak 2.8 when the shape of the CharracterScanner class changed. It is left here as a reminder that the actual primitive still needs supporting in the VM to keep old images such as Scratch1.4 alive - tpr"<br>
        <primitive: 103><br>
+       ^self basicScanByteCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX <br>
+       <br>
+ "Here are some sketchy benchmarks to illustrate the performance issue regarding the use of TrueType fonts:<br>
+ <br>
+ 1) TTCFont only; with primitive fail<br>
+  '1,160 per second. 865 microseconds per run. 0.44 % GC time.' <br>
+ <br>
+ 2) TTCFont only; without primitive fail<br>
+  '5,730 per second. 175 microseconds per run. 6.12 % GC time.' <br>
+ <br>
+ 3) StrikeFont only; using primitive 103<br>
+  '29,700 per second. 33.6 microseconds per run. 1.11978 % GC time.' <br>
+ <br>
+ 4) StrikeFont only; without primitive 103<br>
+  '13,900 per second. 71.7 microseconds per run. 1.17976 % GC time.' <br>
+ <br>
+ "!<br>
-       ^self basicScanByteCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX !<br>
<br>
Item was changed:<br>
  ----- Method: Color>>negated (in category 'transformations') -----<br>
  negated<br>
        "Return an RGB inverted color"<br>
        ^Color<br>
                r: 1.0 - self red<br>
                g: 1.0 - self green<br>
+               b: 1.0 - self blue<br>
+               alpha: self alpha!<br>
-               b: 1.0 - self blue!<br>
<br>
Item was added:<br>
+ ----- Method: Color>>veryMuchDarker (in category 'transformations') -----<br>
+ veryMuchDarker<br>
+ <br>
+       ^ self alphaMixed: 0.25 with: Color black<br>
+ !<br>
<br>
Item was changed:<br>
  CharacterScanner subclass: #CompositionScanner<br>
+       instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineGap lineGapSlice topMargin bottomMargin lineHeightAtSpace baselineAtSpace lastBreakIsNotASpace nextIndexAfterLineBreak'<br>
-       instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineHeightAtSpace baselineAtSpace lastBreakIsNotASpace nextIndexAfterLineBreak'<br>
        classVariableNames: ''<br>
        poolDictionaries: ''<br>
        category: 'Graphics-Text'!<br>
<br>
  !CompositionScanner commentStamp: 'nice 10/6/2013 23:24' prior: 0!<br>
  A CompositionScanner measures text and determines where line breaks.<br>
  Given a rectangular zone on input, it is used to split text in horizontal lines, and produce information about those lines on output (at which index a line starts/stops, which vertical space does the line require, which horizontal space if left for adjusting inter-word spacing, etc...)<br>
<br>
  Instance Variables<br>
        baseline:               <Number><br>
        baselineAtSpace:                <Number><br>
        lastBreakIsNotASpace:           <Boolean><br>
        lineHeight:             <Number><br>
        lineHeightAtSpace:              <Number><br>
        nextIndexAfterLineBreak:                <Integer><br>
        spaceIndex:             <Integer><br>
        spaceX:         <Number><br>
<br>
  baseline<br>
        - the distance between top of line and the base line (that is the bottom of latin characters abcdehiklmnorstuvwx in most fonts)<br>
<br>
  baselineAtSpace<br>
        - memorize the baseline at last encountered space or other breakable character.<br>
        This is necessary because the CompositionScanner wants to break line at a breakable character.<br>
        If a word layout overflows the right margin, the scanner has to roll back and restore the line state to last encountered breakable character.<br>
<br>
  lastBreakIsNotASpace<br>
        - indicates that the last breakable character was not a space.<br>
        This is necessary because handling a line break at a space differs from non space.<br>
        If line break occurs on space, the space won't be displayed in next line.<br>
        If it's another breakable character, it has to be displayed on next line.<br>
<br>
  lineHeight<br>
        - the total line height from top to bottom, including inter-line spacing.<br>
<br>
  lineHeightAtSpace<br>
        - the line height at last encountered space or other breakable character.<br>
        See baselineAtSpace for explanation.<br>
<br>
  nextIndexAfterLineBreak<br>
        - the index of character after the last line break that was encountered.<br>
<br>
  spaceIndex<br>
        - the index of last space or other breakable character that was encountered<br>
<br>
  spaceX<br>
        - the distance from left of composition zone to left of last encountered space or other breakable character <br>
        See baselineAtSpace for explanation.<br>
<br>
  Note: if a line breaks on a space, a linefeed or a carriage return, then the space, linefeed or carriage return is integrated in the line.<br>
  If there is a carriage return - linefeed pair, the pair is integrated to the line as if it were a single line break for compatibility with legacy software.!<br>
<br>
Item was changed:<br>
  ----- Method: CompositionScanner>>composeFrom:inRectangle:firstLine:leftSide:rightSide: (in category 'scanning') -----<br>
  composeFrom: startIndex inRectangle: lineRectangle<br>
        firstLine: firstLine leftSide: leftSide rightSide: rightSide<br>
        "Answer an instance of TextLineInterval that represents the next line in the paragraph."<br>
+       | runLength stopCondition lineSpacing |<br>
-       | runLength stopCondition |<br>
        "Set up margins"<br>
        leftMargin := lineRectangle left.<br>
        leftSide ifTrue: [leftMargin := leftMargin +<br>
                                                (firstLine ifTrue: [textStyle firstIndent]<br>
                                                                ifFalse: [textStyle restIndent])].<br>
        destX := spaceX := leftMargin.<br>
        rightMargin := lineRectangle right.<br>
        rightSide ifTrue: [rightMargin := rightMargin - textStyle rightIndent].<br>
        lastIndex := startIndex.        "scanning sets last index"<br>
        destY := lineRectangle top.<br>
+       lineHeight := baseline := 0. "Will be increased by setFont"<br>
+       lineGap := lineGapSlice := -9999. "Will be increased by setFont; allow negative to show all effects of a custom #extraGap value. See TTFontDescription."<br>
+       topMargin := bottomMargin := 0.<br>
-       lineHeight := baseline := 0.  "Will be increased by setFont"<br>
        line := (TextLine start: lastIndex stop: 0 internalSpaces: 0 paddingWidth: 0)<br>
                                rectangle: lineRectangle.<br>
        self setStopConditions. "also sets font"<br>
        runLength := text runLengthFor: startIndex.<br>
        runStopIndex := (lastIndex := startIndex) + (runLength - 1).<br>
        nextIndexAfterLineBreak := spaceCount := 0.<br>
        lastBreakIsNotASpace := false.<br>
        self handleIndentation.<br>
        leftMargin := destX.<br>
        line leftMargin: leftMargin.<br>
<br>
        [stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex<br>
                in: text string rightX: rightMargin.<br>
        "See setStopConditions for stopping conditions for composing."<br>
        self perform: stopCondition] whileFalse.<br>
<br>
+       lineHeight := lineHeight + lineGap.<br>
+       baseline := baseline + lineGapSlice.<br>
+       <br>
+       "TODO: Allow special characters or text attributes to accumulate extra top or bottom margin."<br>
+       (lineSpacing := textStyle lineSpacing) = 0.0<br>
+               ifFalse: [bottomMargin := bottomMargin + (lineSpacing * lineHeight) truncated].<br>
+ <br>
+       line lineHeight: lineHeight baseline: baseline.<br>
+       line topMargin: topMargin bottomMargin: bottomMargin.<br>
+       ^ line!<br>
-       ^ line<br>
-               lineHeight: lineHeight + textStyle leading<br>
-               baseline: baseline + textStyle leading!<br>
<br>
Item was changed:<br>
  ----- Method: CompositionScanner>>computeDefaultLineHeight (in category 'scanning') -----<br>
  computeDefaultLineHeight<br>
        "Compute the default line height for a potentially empty text"<br>
        rightMargin notNil<br>
                ifTrue: [lastIndex := 1.<br>
                        self setFont.<br>
+                       ^ lineHeight + lineGap]<br>
-                       ^ lineHeight + textStyle leading]<br>
                ifFalse: [^textStyle lineGrid]!<br>
<br>
Item was changed:<br>
  ----- Method: CompositionScanner>>setActualFont: (in category 'text attributes') -----<br>
  setActualFont: aFont<br>
        "Keep track of max height and ascent for auto lineheight"<br>
        | descent |<br>
        super setActualFont: aFont.<br>
        lineHeight == nil<br>
                ifTrue: [descent := font descent.<br>
                                baseline := font ascent.<br>
+                               lineHeight := baseline + descent.<br>
+                               lineGap := aFont lineGap.<br>
+                               lineGapSlice := aFont lineGapSlice]<br>
-                               lineHeight := baseline + descent]<br>
                ifFalse: [descent := lineHeight - baseline max: font descent.<br>
                                baseline := baseline max: font ascent.<br>
+                               lineHeight := lineHeight max: baseline + descent.<br>
+                               lineGap := lineGap max: aFont lineGap.<br>
+                               lineGapSlice := lineGapSlice max: aFont lineGapSlice]!<br>
-                               lineHeight := lineHeight max: baseline + descent]!<br>
<br>
Item was changed:<br>
  ----- Method: Cursor class>>currentCursor: (in category 'current cursor') -----<br>
  currentCursor: aCursor <br>
        "Make the instance of cursor, aCursor, be the current cursor. Display it. <br>
        Create an error if the argument is not a Cursor."<br>
<br>
        (aCursor isKindOf: self)<br>
+               ifTrue: [ | platformCursor |<br>
+                       CurrentCursor := aCursor. "unscaled"<br>
+                       platformCursor := aCursor enlargedBy: RealEstateAgent scaleFactor.<br>
+                       self useBiggerCursors<br>
+                               ifTrue: [platformCursor := platformCursor enlargedBy: 2].<br>
+                       platformCursor beCursor]<br>
-               ifTrue: [CurrentCursor := aCursor.<br>
-                               self useBiggerCursors<br>
-                                       ifTrue: [[^ aCursor asBigCursor beCursor]<br>
-                                               on: Error do: ["fall through"]].<br>
-                               aCursor beCursor]<br>
                ifFalse: [self error: 'The new cursor must be an instance of class Cursor']!<br>
<br>
Item was changed:<br>
  ----- Method: Cursor class>>useBiggerCursors (in category 'preferences') -----<br>
  useBiggerCursors<br>
<br>
        <preference: 'Use bigger mouse cursors'<br>
+               categoryList: #(mouse Accessibility)<br>
-               category: 'mouse'<br>
                description: 'If true, mouse cursors are scaled up'<br>
                type: #Boolean><br>
        ^ UseBiggerCursors ifNil: [false]!<br>
<br>
Item was changed:<br>
  ----- Method: Cursor class>>useBiggerCursors: (in category 'preferences') -----<br>
  useBiggerCursors: aBool<br>
<br>
+       UseBiggerCursors := aBool.<br>
+       Cursor currentCursor: Cursor currentCursor.!<br>
-       UseBiggerCursors := aBool!<br>
<br>
Item was changed:<br>
  Form subclass: #DisplayScreen<br>
        instanceVariableNames: 'clippingBox extraRegions'<br>
+       classVariableNames: 'DeferringUpdates DisplayChangeSignature DisplayIsFullScreen PlatformScaleFactor RelativeScaleFactorEnabled'<br>
-       classVariableNames: 'DeferringUpdates DisplayChangeSignature DisplayIsFullScreen'<br>
        poolDictionaries: ''<br>
        category: 'Graphics-Display Objects'!<br>
<br>
  !DisplayScreen commentStamp: '<historical>' prior: 0!<br>
  There is only one instance of me, Display. It is a global and is used to handle general user requests to deal with the whole display screen. <br>
        Although I offer no protocol, my name provides a way to distinguish this special instance from all other Forms. This is useful, for example, in dealing with saving and restoring the system.<br>
        To change the depth of your Display...<br>
                Display newDepth: 16.<br>
                Display newDepth: 8.<br>
                Display newDepth: 1.<br>
  Valid display depths are 1, 2, 4, 8, 16 and 32.  It is suggested that you run with your monitors setting the same, for better speed and color fidelity.  Note that this can add up to 4Mb for the Display form.  Finally, note that newDepth: ends by executing a 'ControlManager restore' which currently terminates the active process, so nothing that follows in the doit will get executed.<br>
<br>
  Depths 1, 2, 4 and 8 bits go through a color map to put color on the screen, but 16 and 32-bit color use the pixel values directly for RGB color (5 and 8 bits per, respectivlely).  The color choice an be observed by executing Color fromUser in whatever depth you are using.<br>
  !<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen class>>actualScreenScaleFactor (in category 'snapshots') -----<br>
+ actualScreenScaleFactor<br>
+       "<primitive: #primitiveScreenScaleFactor>"<br>
+       "Once the primitive is ready, you can simply uncomment it. The system is prepared for changes from 'nil' to actual factors and back."<br>
+ <br>
+       ^ nil "unknown -- do not default to 1.0 here"!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen class>>checkForNewScreenScaleFactor (in category 'display box access') -----<br>
+ checkForNewScreenScaleFactor<br>
+       "Check whether the platform's scale factor has changed and if so take appropriate actions"<br>
+ <br>
+       Display platformScaleFactor: DisplayScreen actualScreenScaleFactor.!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen class>>relativeScaleFactor (in category 'preferences') -----<br>
+ relativeScaleFactor<br>
+       <preference: 'Scale Factor'<br>
+               categoryList: #(Morphic Tools visuals Accessibility)<br>
+               description: 'Set the size of fonts and tools according to the pixels-per-inch of your display. Can be used to zoom in even further.'<br>
+               type: #String><br>
+ <br>
+       ^ Display relativeUiScaleFactor!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen class>>relativeScaleFactor: (in category 'preferences') -----<br>
+ relativeScaleFactor: aFloatString<br>
+ <br>
+       Display relativeUiScaleFactor: (aFloatString ifNotNil: [:s | s asNumber] ifNil: [1.0]).!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen class>>relativeScaleFactorEnabled (in category 'preferences') -----<br>
+ relativeScaleFactorEnabled<br>
+       <preference: 'Show Relative Scale Factor'<br>
+               categoryList: #(Morphic Tools visuals)<br>
+               description: 'When true, 100% means as-the-platform/monitor-demands, which is typical for macOS. When false, 100% means pixel-perfect, which is typical for Windows. Only works if #platformScaleFactorKnown.'<br>
+               type: #Boolean><br>
+ <br>
+       "Note that we set the default to 'false' because pixels are very prominent in the Morphic programming model. So it makes sense to communcate this kind of pixel scaling openly and thus not hide the #platformScaleFactor from the user."<br>
+       ^ RelativeScaleFactorEnabled ifNil: [false].!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen class>>relativeScaleFactorEnabled: (in category 'preferences') -----<br>
+ relativeScaleFactorEnabled: aBoolean<br>
+ <br>
+       RelativeScaleFactorEnabled := Display platformScaleFactorKnown and: [aBoolean ifNil: [false]].!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen>>currentScaleError (in category 'scale factor') -----<br>
+ currentScaleError<br>
+       "Documentation and debugging only. The scale error originates in rounding errors while rendering the standard TTCFont font into pixels or using a pre-rendered StrikeFont in the first place.<br>
+       <br>
+       Display currentScaleError<br>
+       "<br>
+       <br>
+       ^ RealEstateAgent scaleFactor - self uiScaleFactor<br>
+       <br>
+ "<br>
+ | errors current |<br>
+ errors := OrderedDictionary new.<br>
+ current := Display uiScaleFactor.<br>
+ 1.0 to: 3.0 by: 0.25 do: [:s |<br>
+       Display uiScaleFactor: s.<br>
+       errors at: s put: Display currentScaleError].<br>
+ Display uiScaleFactor: current.<br>
+ errors explore.<br>
+ "!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen>>platformScaleFactor (in category 'scale factor') -----<br>
+ platformScaleFactor<br>
+       "Answers the platform's (and thus monitor's) current scale factor as last reported via VM primitive. See #checkForNewScreenScaleFactor."<br>
+ <br>
+       ^ PlatformScaleFactor ifNil: [1.0 "Primitive not ready."]!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen>>platformScaleFactor: (in category 'scale factor') -----<br>
+ platformScaleFactor: aFloatOrNil<br>
+       "Report a new scale factor from the platform to the image. This can happen if you move Squeak's window between monitors with different pixels-per-inch. On some platforms, you can also set a scale factor independent of monitor PPI. Note that the user might have scaled the image regardless of the previous platform scale factor."<br>
+ <br>
+       | old new |<br>
+       aFloatOrNil ifNil: [<br>
+               "Ignore. Primitive not ready (anymore)."<br>
+               PlatformScaleFactor := nil.<br>
+               ^ self].<br>
+       PlatformScaleFactor ifNil: [<br>
+               "First time. Ignore. Assume that the user scaled manually until now."<br>
+               PlatformScaleFactor := aFloatOrNil.<br>
+               ^ self].<br>
+       <br>
+       (old := self platformScaleFactor) = (new := aFloatOrNil) ifTrue: [^ self].<br>
+       PlatformScaleFactor := new.<br>
+       self uiScaleFactor: self uiScaleFactor * (new/old).!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen>>platformScaleFactorKnown (in category 'scale factor') -----<br>
+ platformScaleFactorKnown<br>
+       "Tools can help users understand whether Squeak will adjust the scale factor automatically or whether they have to scale manually. See class-side's #actualScreenScaleFactor and also #relativeUiScaleFactor."<br>
+ <br>
+       ^ PlatformScaleFactor notNil!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen>>relativeUiScaleFactor (in category 'scale factor') -----<br>
+ relativeUiScaleFactor<br>
+       "Answers the scale factor as perceived by the user. Note that this concept might be platform-dependent. On Windows, for example, 100% means pixel-perfect while on macOS 100% means as-the-monitor-demands. So, Retina displays are effectively scaled even if the user sees 100% in a Choose-Your-Scale dialog."<br>
+       <br>
+       ^ self class relativeScaleFactorEnabled<br>
+               ifTrue: [ "macOS" self uiScaleFactor / self platformScaleFactor ]<br>
+               ifFalse: [ "Windows" self uiScaleFactor ]!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen>>relativeUiScaleFactor: (in category 'scale factor') -----<br>
+ relativeUiScaleFactor: aFloat<br>
+       <br>
+       ^ self class relativeScaleFactorEnabled<br>
+               ifTrue: [ "macOS" self uiScaleFactor: aFloat * self platformScaleFactor ]<br>
+               ifFalse: [ "Windows" self uiScaleFactor: aFloat ]!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen>>uiScaleFactor (in category 'scale factor') -----<br>
+ uiScaleFactor<br>
+       "Answers the current scale factor used to configure all widgets, tools, or windows to be prepared for the current rendering system, i.e., BitBlt. Note that 1.0 means 'pixel perfect'."<br>
+       <br>
+       ^ UserInterfaceTheme current isTTCBased<br>
+               ifTrue: [TextStyle pixelsPerInch / 96.0 "Hide rounding errors in TTCFont >> #height."]<br>
+               ifFalse: [RealEstateAgent scaleFactor roundTo: 0.25 "Force 25% steps. See, e.g., #doScale150."].!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayScreen>>uiScaleFactor: (in category 'scale factor') -----<br>
+ uiScaleFactor: aFloat<br>
+       "Sets the effective scale factor for the user interface, i.e., all widgets, tools, and windows. The user can override the CurrentScaleFactor recommended by the platform."<br>
+ <br>
+       | oldFactor newFactor |<br>
+       (UserInterfaceTheme current canFakeScaleFactor: aFloat) ifTrue: [<br>
+               self flag: #isTTCBased.<br>
+               ^ UserInterfaceTheme current applyScaled: aFloat].<br>
+ <br>
+       aFloat = 0.75 ifTrue: [(Project uiManager confirm: ((('You are currently using <b>TrueType fonts</b>. Your requested scale factor of <b>{1}%</b> looks better using pre-rendered <b>pixel fonts</b>.\\Do you want to switch to pixel fonts now?' translated withCRs format: {(aFloat * 100) rounded}) withNoLineLongerThan: 60) copyReplaceAll: String cr with: '<br>') asTextFromHtml title: 'Blurry Fonts Detected' translated) == true<br>
+               ifTrue: [UserInterfaceTheme cleanUpAndReset. ^ self uiScaleFactor: aFloat]].<br>
+       <br>
+       oldFactor := RealEstateAgent scaleFactor. "Use effective, pixel-based factor to account for rounding errors. See #isTTCBased and #uiScaleFactor."<br>
+       newFactor := aFloat max: 0.75.<br>
+       newFactor = oldFactor ifTrue: [^ self].<br>
+       <br>
+       TextStyle pixelsPerInch: 96.0 * aFloat.<br>
+       newFactor := RealEstateAgent resetScaleFactor; scaleFactor. "Again, account for rounding errors."<br>
+       Project current ifNotNil: [:p | p displayScaleChangedFrom: oldFactor to: newFactor].!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayText>>asString (in category 'converting') -----<br>
+ asString<br>
+       "See String >> #asDisplayText."<br>
+       <br>
+       ^ self string!<br>
<br>
Item was added:<br>
+ ----- Method: DisplayText>>asText (in category 'converting') -----<br>
+ asText<br>
+       "See Text >> #asDisplayText."<br>
+ <br>
+       ^ self text!<br>
<br>
Item was changed:<br>
  ----- Method: FixedFaceFont>>baseFont: (in category 'accessing') -----<br>
  baseFont: aFont<br>
+       baseFont := aFont.<br>
+       self fixSubstitutionCharacter.!<br>
-       baseFont := aFont!<br>
<br>
Item was changed:<br>
+ ----- Method: FixedFaceFont>>characterFormAt: (in category 'character shapes') -----<br>
- ----- Method: FixedFaceFont>>characterFormAt: (in category 'accessing') -----<br>
  characterFormAt: character <br>
        ^ baseFont characterFormAt: substitutionCharacter!<br>
<br>
Item was added:<br>
+ ----- Method: FixedFaceFont>>errorCharacter (in category 'accessing') -----<br>
+ errorCharacter<br>
+       ^$?!<br>
<br>
Item was changed:<br>
  ----- Method: FixedFaceFont>>errorFont (in category 'initialize-release') -----<br>
  errorFont<br>
        displaySelector := #displayErrorOn:length:at:kern:baselineY:.<br>
+       substitutionCharacter := self errorCharacter.<br>
+       self fixSubstitutionCharacter.!<br>
-       substitutionCharacter := $?.!<br>
<br>
Item was added:<br>
+ ----- Method: FixedFaceFont>>fixSubstitutionCharacter (in category 'private') -----<br>
+ fixSubstitutionCharacter<br>
+       <br>
+       substitutionCharacter ifNil: [^ self].<br>
+       baseFont ifNil: [^ self].<br>
+       (baseFont hasGlyphOf: substitutionCharacter) ifTrue: [^ self].<br>
+ <br>
+       ((baseFont minCodePoint max: 33) to: baseFont maxCodePoint)<br>
+               detect: [:codePoint | baseFont hasGlyphOf: (Character value: codePoint)]<br>
+               ifFound: [:codePoint | substitutionCharacter := Character value: codePoint]<br>
+               ifNone: [<br>
+                       baseFont := TextStyle defaultFont.<br>
+                       substitutionCharacter := $?].   <br>
+       self<br>
+               assert: [baseFont hasGlyphOf: substitutionCharacter]<br>
+               description: 'Could not find a possible substitution character and font!!'.!<br>
<br>
Item was added:<br>
+ ----- Method: FixedFaceFont>>fontPointSize: (in category 'accessing') -----<br>
+ fontPointSize: aNumber <br>
+       self baseFont: (StrikeFont familyName: baseFont familyName pointSize: aNumber) copy!<br>
<br>
Item was added:<br>
+ ----- Method: FixedFaceFont>>formOf: (in category 'private') -----<br>
+ formOf: aCharacter<br>
+       "No need to check #hasGlyphOf:."<br>
+ <br>
+       ^ self characterFormAt: aCharacter!<br>
<br>
Item was changed:<br>
  ----- Method: FixedFaceFont>>initialize (in category 'initialize-release') -----<br>
  initialize<br>
+ <br>
+       baseFont := TextStyle defaultFont.<br>
+       self passwordFont.!<br>
-       "This used to be the default textstyle, but it needs to be a StrikeFont and not a TTCFont and sometimes the default textstyle is a TTCFont.  So, we use a typical StrikeFont as the default fallback font."<br>
-       baseFont := StrikeFont defaultSized: 12.<br>
-       self passwordFont!<br>
<br>
Item was removed:<br>
- ----- Method: FixedFaceFont>>maxAscii (in category 'accessing') -----<br>
- maxAscii<br>
-       ^ SmallInteger maxVal!<br>
<br>
Item was added:<br>
+ ----- Method: FixedFaceFont>>maxCodePoint (in category 'accessing') -----<br>
+ maxCodePoint<br>
+       "Overwritten for robustness. The receiver MUST BE a reliable source of glyphs if all else fails. Font rendering must never stop."<br>
+       <br>
+       ^ SmallInteger maxVal!<br>
<br>
Item was added:<br>
+ ----- Method: FixedFaceFont>>minSubstitutionCharacter (in category 'private') -----<br>
+ minSubstitutionCharacter<br>
+ <br>
+       ^ Character value: (baseFont ifNil: [0] ifNotNil: [baseFont minCodePoint])!<br>
<br>
Item was changed:<br>
  ----- Method: FixedFaceFont>>passwordFont (in category 'initialize-release') -----<br>
  passwordFont<br>
        displaySelector := #displayPasswordOn:length:at:kern:baselineY:.<br>
+       substitutionCharacter := self passwordCharacter.<br>
+       self fixSubstitutionCharacter.!<br>
-       substitutionCharacter := $*!<br>
<br>
Item was changed:<br>
  ----- Method: FontSet class>>acceptsLoggingOfCompilation (in category 'compiling') -----<br>
  acceptsLoggingOfCompilation<br>
        "Dont log sources for my subclasses, so as not to waste time<br>
        and space storing printString versions of the string literals."<br>
<br>
+       ^super acceptsLoggingOfCompilation<br>
+               and:<br>
+                       [self == FontSet]!<br>
-       ^ self == FontSet!<br>
<br>
Item was changed:<br>
  DisplayMedium subclass: #Form<br>
        instanceVariableNames: 'bits width height depth offset'<br>
+       classVariableNames: 'CompressOnSnapshot'<br>
-       classVariableNames: ''<br>
        poolDictionaries: ''<br>
        category: 'Graphics-Display Objects'!<br>
<br>
  !Form commentStamp: 'cbc 5/5/2017 10:07' prior: 0!<br>
  A rectangular array of pixels, used for holding images.  All pictures, including character images are Forms.  The depth of a Form is how many bits are used to specify the color at each pixel.  The actual bits are held in a Bitmap, whose internal structure is different at each depth.  Class Color allows you to deal with colors without knowing how they are actually encoded inside a Bitmap.<br>
          The supported depths (in bits) are 1, 2, 4, 8, 16, and 32.  The number of actual colors at these depths are: 2, 4, 16, 256, 32768, and 16 million.<br>
        Forms are indexed starting at 0 instead of 1; thus, the top-left pixel of a Form has coordinates 0@0.<br>
        Forms are combined using BitBlt.  See the comment in class BitBlt.  Forms that repeat many times to fill a large destination are InfiniteForms.<br>
<br>
        colorAt: x@y            Returns the abstract Color at this location<br>
        displayAt: x@y          shows this form on the screen<br>
        displayOn: aMedium at: x@y      shows this form in a Window, a Form, or other DisplayMedium<br>
        fillColor: aColor               Set all the pixels to the color.<br>
        edit            launch an editor to change the bits of this form.<br>
        pixelValueAt: x@y       The encoded color.  The encoding depends on the depth.<br>
<br>
  Note: If you want to hook up other external forms/displayScreens, please look at the (successful) Graphics-External package in <a href="http://www.squeaksource.com/Balloon3D" rel="noreferrer" target="_blank">http://www.squeaksource.com/Balloon3D</a>.!<br>
<br>
Item was added:<br>
+ ----- Method: Form class>>compressOnSnapshot (in category 'preferences') -----<br>
+ compressOnSnapshot<br>
+       <preference: 'Always compress graphics data on snapshot'<br>
+               categoryList: #(performance Graphics)<br>
+               description: 'When enabled, graphics data such as all instances of Form are compressed during image snapshots -- even when the image will not quit after the snapshot. This includes cache clean-up such as the TrueType glyph cache. Disable to avoid render lags after snapshots at the cost of a bigger footprint of your .image file. Note that snapshot-and-quit always compresses graphics data.'<br>
+               type: #Boolean><br>
+ <br>
+       ^ CompressOnSnapshot ifNil: [true]!<br>
<br>
Item was added:<br>
+ ----- Method: Form class>>compressOnSnapshot: (in category 'preferences') -----<br>
+ compressOnSnapshot: aBoolean<br>
+ <br>
+       CompressOnSnapshot := aBoolean.!<br>
<br>
Item was removed:<br>
- ----- Method: Form class>>shutDown (in category 'shut down') -----<br>
- shutDown  "Form shutDown"<br>
-       "Compress all instances in the system.  Will decompress on demand..."<br>
-       Form allInstancesDo: [:f | f hibernate].<br>
-       ColorForm allInstancesDo: [:f | f hibernate].!<br>
<br>
Item was added:<br>
+ ----- Method: Form class>>shutDown: (in category 'shut down') -----<br>
+ shutDown: quitting<br>
+       "When quitting, compress all instances in the system.  Will decompress on demand after start-up. Note that #compressOnShapshot can avoid hibernating forms during no-quit snapshotting to keep the system as responsive as possible directly after."<br>
+ <br>
+       "Form shutDown: true"<br>
+       (quitting or: [self compressOnSnapshot]) ifTrue: [<br>
+               Form allInstancesDo: [:f | f hibernate].<br>
+               ColorForm allInstancesDo: [:f | f hibernate]].!<br>
<br>
Item was changed:<br>
  ----- Method: Form>>bitsSize (in category 'accessing') -----<br>
  bitsSize<br>
+       | pixelsPerWord |<br>
+       depth ifNil: [depth := 1].<br>
+       pixelsPerWord := 32 // self depth.<br>
+       ^width + pixelsPerWord - 1 // pixelsPerWord * height!<br>
-       | pixPerWord |<br>
-       depth == nil ifTrue: [depth := 1].<br>
-       pixPerWord := 32 // self depth.<br>
-       ^ width + pixPerWord - 1 // pixPerWord * height!<br>
<br>
Item was added:<br>
+ ----- Method: Form>>isVirtualScreen (in category 'testing') -----<br>
+ isVirtualScreen<br>
+       "Am I a virtual display screen?"<br>
+       ^false!<br>
<br>
Item was changed:<br>
  ----- Method: Form>>setExtent:depth:bits: (in category 'private') -----<br>
  setExtent: extent depth: bitsPerPixel bits: bitmap<br>
        "Create a virtual bit map with the given extent and bitsPerPixel."<br>
+       | bitsClass |<br>
+       (width := extent x asInteger) < 0 ifTrue: [width := 0].<br>
+       (height := extent y asInteger) < 0 ifTrue: [height := 0].<br>
- <br>
-       width := extent x asInteger.<br>
-       width < 0 ifTrue: [width := 0].<br>
-       height := extent y asInteger.<br>
-       height < 0 ifTrue: [height := 0].<br>
        depth := bitsPerPixel.<br>
-       depth := bitsPerPixel.<br>
        (bits isNil<br>
+        or: [(bitsClass := bits class) isBits<br>
+               and: [self bitsSize * 4 "bytes per pixel" = (bitmap size * bitsClass elementSize)]]) ifFalse:<br>
+               [^self error: 'Bad dimensions and/or bitmap kind'].<br>
-               or:[(bitmap class isWords and: [self bitsSize = bitmap size])<br>
-               or: [bitmap class isBytes and: [self bitsSize * 4 = bitmap size]]])<br>
-                       ifFalse:[^self error:'Bad dimensions'].<br>
        bits := bitmap!<br>
<br>
Item was changed:<br>
  StrikeFont subclass: #FormSetFont<br>
        instanceVariableNames: 'tintable combinationRule'<br>
        classVariableNames: ''<br>
        poolDictionaries: ''<br>
        category: 'Graphics-Fonts'!<br>
<br>
+ !FormSetFont commentStamp: 'ct 2/14/2020 16:49' prior: 0!<br>
+ FormSetFonts are designed to capture individual images as character forms for imbedding in normal text.  While most often used to insert an isolated glyph in some text, the code is actually designed to support an entire user-defined font.  The TextAttribute subclass TextFontReference is specifically designed for such in-line insertion of exceptional fonts in normal text.!<br>
- !FormSetFont commentStamp: '<historical>' prior: 0!<br>
- FormSetFonts are designed to capture individual images as character forms for imbedding in normal text.  While most often used to insert an isolated glyph in some text, the code is actually desinged to support an entire user-defined font.  The TextAttribute subclass TextFontReference is specifically designed for such in-line insertion of exceptional fonts in normal text.!<br>
<br>
Item was changed:<br>
  ----- Method: GIFReadWriter class>>grabScreenAndSaveOnDisk (in category 'examples') -----<br>
  grabScreenAndSaveOnDisk<br>
        "GIFReadWriter grabScreenAndSaveOnDisk"<br>
<br>
        | form fileName |<br>
        form := Form fromUser.<br>
        form bits size = 0 ifTrue: [^Beeper beep].<br>
        fileName := FileDirectory default nextNameFor: 'Squeak' extension: 'gif'.<br>
+       Project uiManager<br>
+               informUser: ('Writing {1}' translated format: {fileName})<br>
+               during: [GIFReadWriter putForm: form onFileNamed: fileName].!<br>
-       UIManager default informUser: 'Writing ' , fileName<br>
-               during: [GIFReadWriter putForm: form onFileNamed: fileName]!<br>
<br>
Item was changed:<br>
  ----- Method: GIFReadWriter>>nextImage (in category 'accessing') -----<br>
  nextImage<br>
        "Read in the next GIF image from the stream."<br>
<br>
        | f thisImageColorTable |<br>
<br>
        localColorTable := nil.<br>
        self readHeader.<br>
        f := self readBody.<br>
        self close.<br>
+       f == nil ifTrue: [^ self error: 'corrupt GIF file' translated].<br>
-       f == nil ifTrue: [^ self error: 'corrupt GIF file'].<br>
<br>
        thisImageColorTable := localColorTable ifNil: [colorPalette].<br>
        transparentIndex ifNotNil: [<br>
                transparentIndex + 1 > thisImageColorTable size ifTrue: [<br>
                        thisImageColorTable := thisImageColorTable <br>
                                forceTo: transparentIndex + 1 <br>
                                paddingWith: Color white<br>
                ].<br>
                thisImageColorTable at: transparentIndex + 1 put: Color transparent<br>
        ].<br>
        f colors: thisImageColorTable.<br>
        ^ f<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: GIFReadWriter>>readBitData (in category 'private-decoding') -----<br>
  readBitData<br>
        "using modified Lempel-Ziv Welch algorithm."<br>
<br>
        | outCodes outCount bitMask initCodeSize code curCode oldCode inCode finChar i bytes f c packedBits hasLocalColor localColorSize maxOutCodes |<br>
<br>
        maxOutCodes := 4096.<br>
        offset := self readWord@self readWord. "Image Left@Image Top"<br>
        width := self readWord.<br>
        height := self readWord.<br>
<br>
        "---<br>
        Local Color Table Flag        1 Bit<br>
        Interlace Flag                1 Bit<br>
        Sort Flag                     1 Bit<br>
        Reserved                      2 Bits<br>
        Size of Local Color Table     3 Bits<br>
        ----"<br>
        packedBits := self next.<br>
        interlace := (packedBits bitAnd: 16r40) ~= 0.<br>
        hasLocalColor := (packedBits bitAnd: 16r80) ~= 0.<br>
        localColorSize := 1 bitShift: ((packedBits bitAnd: 16r7) + 1).<br>
        hasLocalColor ifTrue: [localColorTable := self readColorTable: localColorSize].<br>
<br>
        pass := 0.<br>
        xpos := 0.<br>
        ypos := 0.<br>
        rowByteSize := ((width + 3) // 4) * 4.<br>
        remainBitCount := 0.<br>
        bufByte := 0.<br>
        bufStream := ReadStream on: ByteArray new.<br>
<br>
        outCodes := ByteArray new: maxOutCodes + 1.<br>
        outCount := 0.<br>
        bitMask := (1 bitShift: bitsPerPixel) - 1.<br>
        prefixTable := Array new: 4096.<br>
        suffixTable := Array new: 4096.<br>
<br>
        initCodeSize := self next.<br>
<br>
        self setParameters: initCodeSize.<br>
+       bitsPerPixel > 8 ifTrue: [^self error: ('never heard of a GIF that deep (depth = {1})' translated format: {bitsPerPixel})].<br>
-       bitsPerPixel > 8 ifTrue: [^self error: 'never heard of a GIF that deep'].<br>
        bytes := ByteArray new: rowByteSize * height.<br>
        [(code := self readCode) = eoiCode] whileFalse:<br>
                [code = clearCode<br>
                        ifTrue:<br>
                                [self setParameters: initCodeSize.<br>
                                curCode := oldCode := code := self readCode.<br>
                                finChar := curCode bitAnd: bitMask.<br>
                                "Horrible hack to avoid running off the end of the bitmap.  Seems to cure problem reading some gifs!!? tk 6/24/97 20:16"<br>
                                xpos = 0 ifTrue: [<br>
                                                ypos < height ifTrue: [<br>
                                                        bytes at: (ypos * rowByteSize) + xpos + 1 put: finChar]]<br>
                                        ifFalse: [bytes at: (ypos * rowByteSize) + xpos + 1 put: finChar].<br>
                                self updatePixelPosition]<br>
                        ifFalse:<br>
                                [curCode := inCode := code.<br>
                                curCode >= freeCode ifTrue:<br>
                                        [curCode := oldCode.<br>
                                        outCodes at: (outCount := outCount + 1) put: finChar].<br>
                                [curCode > bitMask] whileTrue:<br>
                                        [outCount > maxOutCodes<br>
+                                               ifTrue: [^self error: ('corrupt GIF file ({1})' translated format: {'OutCount'})].<br>
-                                               ifTrue: [^self error: 'corrupt GIF file (OutCount)'].<br>
                                        outCodes at: (outCount := outCount + 1)<br>
                                                put: (suffixTable at: curCode + 1).<br>
                                        curCode := prefixTable at: curCode + 1].<br>
                                finChar := curCode bitAnd: bitMask.<br>
                                outCodes at: (outCount := outCount + 1) put: finChar.<br>
                                i := outCount.<br>
                                [i > 0] whileTrue:<br>
                                        ["self writePixel: (outCodes at: i) to: bits"<br>
                                        bytes at: (ypos * rowByteSize) + xpos + 1 put: (outCodes at: i).<br>
                                        self updatePixelPosition.<br>
                                        i := i - 1].<br>
                                outCount := 0.<br>
                                prefixTable at: freeCode + 1 put: oldCode.<br>
                                suffixTable at: freeCode + 1 put: finChar.<br>
                                oldCode := inCode.<br>
                                freeCode := freeCode + 1.<br>
                                self checkCodeSize]].<br>
        prefixTable := suffixTable := nil.<br>
<br>
        f := ColorForm extent: width@height depth: 8.<br>
        f bits copyFromByteArray: bytes.<br>
        "Squeak can handle depths 1, 2, 4, and 8"<br>
        bitsPerPixel > 4 ifTrue: [^ f].<br>
        "reduce depth to save space"<br>
        c := ColorForm extent: width@height<br>
                depth: (bitsPerPixel = 3 ifTrue: [4] ifFalse: [bitsPerPixel]).<br>
        f displayOn: c.<br>
+       ^ c!<br>
-       ^ c<br>
- !<br>
<br>
Item was changed:<br>
  ----- Method: GIFReadWriter>>readHeader (in category 'private-decoding') -----<br>
  readHeader<br>
        | is89 byte hasColorMap |<br>
        (self hasMagicNumber: 'GIF87a' asByteArray)<br>
                ifTrue: [is89 := false]<br>
                ifFalse: [(self hasMagicNumber: 'GIF89a' asByteArray)<br>
                        ifTrue: [is89 := true]<br>
+                       ifFalse: [^ self error: 'This does not appear to be a GIF file' translated]].<br>
-                       ifFalse: [^ self error: 'This does not appear to be a GIF file']].<br>
        self readWord.  "skip Screen Width"<br>
        self readWord.  "skip Screen Height"<br>
        byte := self next.<br>
        hasColorMap := (byte bitAnd: 16r80) ~= 0.<br>
        bitsPerPixel := (byte bitAnd: 7) + 1.<br>
        byte := self next.      "skip background color."<br>
        self next ~= 0<br>
                ifTrue: [is89<br>
+                       ifFalse: [^self error: ('corrupt GIF file ({1})' translated format: {'screen descriptor'})]].<br>
-                       ifFalse: [^self error: 'corrupt GIF file (screen descriptor)']].<br>
        hasColorMap<br>
                ifTrue:<br>
                        [colorPalette := self readColorTable: (1 bitShift: bitsPerPixel)]<br>
                ifFalse:<br>
                        ["Transcript cr; show: 'GIF file does not have a color map.'."<br>
                        colorPalette := nil "Palette monochromeDefault"].!<br>
<br>
Item was changed:<br>
  ----- Method: GIFReadWriter>>updatePixelPosition (in category 'private') -----<br>
  updatePixelPosition<br>
        (xpos := xpos + 1) >= width ifFalse: [^self].<br>
        xpos := 0.<br>
        interlace<br>
                ifFalse: [ypos := ypos + 1. ^self].<br>
        pass = 0 ifTrue:<br>
                [(ypos := ypos + 8) >= height<br>
                        ifTrue:<br>
                                [pass := pass + 1.<br>
                                ypos := 4].<br>
                ^self].<br>
        pass = 1 ifTrue:<br>
                [(ypos := ypos + 8) >= height<br>
                        ifTrue:<br>
                                [pass := pass + 1.<br>
                                ypos := 2].<br>
                ^self].<br>
        pass = 2 ifTrue:<br>
                [(ypos := ypos + 4) >= height<br>
                        ifTrue:<br>
                                [pass := pass + 1.<br>
                                ypos := 1].<br>
                ^self].<br>
        pass = 3 ifTrue:<br>
                [ypos := ypos + 2.<br>
                ^self].<br>
<br>
+       ^pass caseError!<br>
-       ^self error: 'can''t happen'!<br>
<br>
Item was changed:<br>
  ----- Method: ImageReadWriter class>>formFromStream: (in category 'image reading/writing') -----<br>
  formFromStream: aBinaryStream<br>
        "Answer a ColorForm stored on the given stream.  closes the stream"<br>
        | reader readerClass form  |<br>
<br>
        readerClass := self withAllSubclasses<br>
                detect: [:subclass |<br>
                        aBinaryStream reset.<br>
                        subclass understandsImageFormat: aBinaryStream]<br>
                ifNone: [<br>
                        aBinaryStream close.<br>
+                       ^self error: 'image format not recognized' translated].<br>
-                       ^self error: 'image format not recognized'].<br>
        aBinaryStream reset.<br>
        reader := readerClass new on: aBinaryStream.<br>
        Cursor read showWhile: [<br>
                form := reader nextImage.<br>
                reader close].<br>
        ^ form<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: ImageReadWriter>>changePadOfBits:width:height:depth:from:to: (in category 'private') -----<br>
  changePadOfBits: bits width: width height: height depth: depth from: oldPad<br>
  to: newPad<br>
        "Change padding size of bits."<br>
<br>
        | srcRowByteSize dstRowByteSize newBits srcRowBase rowEndOffset |<br>
        (#(8 16 32) includes: oldPad)<br>
+               ifFalse: [^self error: ('Invalid pad: {1}' translated format: {oldPad})].<br>
-               ifFalse: [^self error: 'Invalid pad: ', oldPad printString].<br>
        (#(8 16 32) includes: newPad)<br>
+               ifFalse: [^self error: ('Invalid pad: {1}' translated format: {newPad})].<br>
-               ifFalse: [^self error: 'Invalid pad: ', newPad printString].<br>
        srcRowByteSize := width * depth + oldPad - 1 // oldPad * (oldPad / 8).<br>
        srcRowByteSize * height = bits size<br>
+               ifFalse: [^self error: 'Incorrect bitmap array size.' translated].<br>
-               ifFalse: [^self error: 'Incorrect bitmap array size.'].<br>
        dstRowByteSize := width * depth + newPad - 1 // newPad * (newPad / 8).<br>
        newBits := ByteArray new: dstRowByteSize * height.<br>
        srcRowBase := 1.<br>
        rowEndOffset := dstRowByteSize - 1.<br>
        1 to: newBits size by: dstRowByteSize do:<br>
                [:dstRowBase |<br>
                newBits replaceFrom: dstRowBase<br>
                        to: dstRowBase + rowEndOffset<br>
                        with: bits<br>
                        startingAt: srcRowBase.<br>
                srcRowBase := srcRowBase + srcRowByteSize].<br>
        ^newBits!<br>
<br>
Item was changed:<br>
  ----- Method: ImageReadWriter>>unpackBits:depthTo8From:with:height:pad: (in category 'private') -----<br>
  unpackBits: bits depthTo8From: depth with: width height: height pad: pad<br>
        "Unpack bits of depth 1, 2, or 4 image to it of depth 8 image."<br>
<br>
        | bitMask pixelInByte bitsWidth upBitsWidth stopWidth<br>
         trailingSize upBits bitIndex upBitIndex val |<br>
        (#(1 2 4) includes: depth)<br>
+               ifFalse: [^self error: 'depth must be 1, 2, or 4' translated].<br>
-               ifFalse: [^self error: 'depth must be 1, 2, or 4'].<br>
        (#(8 16 32) includes: pad)<br>
+               ifFalse: [^self error: 'pad must be 8, 16, or 32' translated].<br>
-               ifFalse: [^self error: 'pad must be 8, 16, or 32'].<br>
        bitMask := (1 bitShift: depth) - 1.<br>
        pixelInByte := 8 / depth.<br>
        bitsWidth := width * depth + pad - 1 // pad * (pad / 8).<br>
        upBitsWidth := width * 8 + pad - 1 // pad * (pad / 8).<br>
        stopWidth := width * depth + 7 // 8.<br>
        trailingSize := width - (stopWidth - 1 * pixelInByte).<br>
        upBits := ByteArray new: upBitsWidth * height.<br>
        1 to: height do: [:i |<br>
                bitIndex := i - 1 * bitsWidth.<br>
                upBitIndex := i - 1 * upBitsWidth.<br>
                1 to: stopWidth - 1 do: [:j |<br>
                        val := bits at: (bitIndex := bitIndex + 1).<br>
                        upBitIndex := upBitIndex + pixelInByte.<br>
                        1 to: pixelInByte do: [:k |<br>
                                upBits at: (upBitIndex - k + 1) put: (val bitAnd: bitMask).<br>
                                val := val bitShift: depth negated]].<br>
                val := (bits at: (bitIndex := bitIndex + 1))<br>
                                bitShift: depth negated * (pixelInByte - trailingSize).<br>
                upBitIndex := upBitIndex + trailingSize.<br>
                1 to: trailingSize do: [:k |<br>
                        upBits at: (upBitIndex - k + 1) put: (val bitAnd: bitMask).<br>
                        val := val bitShift: depth negated]].<br>
        ^ upBits<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadStream>>decodeValueFrom: (in category 'huffman trees') -----<br>
  decodeValueFrom: table<br>
        "Decode the next value in the receiver using the given huffman table."<br>
        | bits bitsNeeded tableIndex value |<br>
        bitsNeeded := (table at: 1) bitShift: -24.      "Initial bits needed"<br>
        tableIndex := 2.                                                        "First real table"<br>
        [bits := self getBits: bitsNeeded.                      "Get bits"<br>
        value := table at: (tableIndex + bits).         "Lookup entry in table"<br>
        (value bitAnd: 16r3F000000) = 0]                        "Check if it is a non-leaf node"<br>
                whileFalse:["Fetch sub table"<br>
                        tableIndex := value bitAnd: 16rFFFF.    "Table offset in low 16 bit"<br>
                        bitsNeeded := (value bitShift: -24) bitAnd: 255. "Additional bits in high 8 bit"<br>
+                       bitsNeeded > MaxBits ifTrue:[^self error: 'Invalid huffman table entry' translated]].<br>
-                       bitsNeeded > MaxBits ifTrue:[^self error:'Invalid huffman table entry']].<br>
        ^value!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadStream>>getBits: (in category 'accessing') -----<br>
  getBits: requestedBits<br>
        | value |<br>
        requestedBits > bitsInBuffer ifTrue:[<br>
                self fillBuffer.<br>
                requestedBits > bitsInBuffer ifTrue:[<br>
+                       self error: 'not enough bits available to decode' translated]].<br>
-                       self error: 'not enough bits available to decode']].<br>
        value := bitBuffer bitShift: (requestedBits - bitsInBuffer).<br>
        bitBuffer := bitBuffer bitAnd: (1 bitShift: (bitsInBuffer - requestedBits)) -1.<br>
        bitsInBuffer := bitsInBuffer - requestedBits.<br>
        ^ value!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>nextImageDitheredToDepth: (in category 'public access') -----<br>
  nextImageDitheredToDepth: depth<br>
<br>
        | form xStep yStep x y bb |<br>
        ditherMask := DitherMasks<br>
                at: depth<br>
+               ifAbsent: [self error: 'can only dither to display depths' translated].<br>
-               ifAbsent: [self error: 'can only dither to display depths'].<br>
        residuals := WordArray new: 3.<br>
        sosSeen := false.<br>
        self parseFirstMarker.<br>
        [sosSeen] whileFalse: [self parseNextMarker].<br>
        form := Form extent: (width @ height) depth: depth.<br>
        bb := BitBlt toForm: form.<br>
        bb sourceForm: mcuImageBuffer.<br>
        bb colorMap: (mcuImageBuffer colormapIfNeededFor: form).<br>
        bb sourceRect: mcuImageBuffer boundingBox.<br>
        bb combinationRule: Form over.<br>
        xStep := mcuWidth * DCTSize.<br>
        yStep := mcuHeight * DCTSize.<br>
        y := 0.<br>
        1 to: mcuRowsInScan do:<br>
                [:row |<br>
                x := 0.<br>
                1 to: mcusPerRow do:<br>
                        [:col |<br>
                        self decodeMCU.<br>
                        self idctMCU.<br>
                        self colorConvertMCU.<br>
                        bb destX: x; destY: y; copyBits.<br>
                        x := x + xStep].<br>
                y := y + yStep].<br>
        ^ form!<br>
<br>
Item was removed:<br>
- ----- Method: JPEGReadWriter>>notSupported: (in category 'error handling') -----<br>
- notSupported: aString<br>
- <br>
-       self error: aString , ' is not currently supported'!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>parseAPPn (in category 'marker parsing') -----<br>
  parseAPPn<br>
<br>
        | length buffer thumbnailLength markerStart |<br>
        markerStart := self position.<br>
        length := self nextWord.<br>
        buffer := self next: 4.<br>
        (buffer asString = 'JFIF') ifFalse: [<br>
                "Skip APPs that we're not interested in"<br>
                stream next: length-6.<br>
                ^self].<br>
        self next.<br>
        majorVersion := self next.<br>
        minorVersion := self next.<br>
        densityUnit := self next.<br>
        xDensity := self nextWord.<br>
        yDensity := self nextWord.<br>
        thumbnailLength := self next * self next * 3.<br>
        length := length - (self position - markerStart).<br>
+       length = thumbnailLength ifFalse: [self error: 'APP0 thumbnail length is incorrect.' translated].<br>
-       length = thumbnailLength ifFalse: [self error: 'APP0 thumbnail length is incorrect.'].<br>
        self next: length!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>parseDecoderRestartInterval (in category 'marker parsing') -----<br>
  parseDecoderRestartInterval<br>
<br>
        | length |<br>
        length := self nextWord.<br>
+       length = 4 ifFalse: [self error: 'DRI length is incorrect.' translated].<br>
-       length = 4 ifFalse: [self error: 'DRI length incorrect'].<br>
        restartInterval := self nextWord.!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>parseFirstMarker (in category 'marker parsing') -----<br>
  parseFirstMarker<br>
<br>
        | marker |<br>
+       self next = 16rFF ifFalse: [self error: 'JFIF marker expected' translated].<br>
-       self next = 16rFF ifFalse: [self error: 'JFIF marker expected'].<br>
        marker := self next.<br>
        marker = 16rD9<br>
                ifTrue: [^self "halt: 'EOI encountered.'"].<br>
+       marker = 16rD8 ifFalse: [self error: 'SOI marker expected' translated].<br>
-       marker = 16rD8 ifFalse: [self error: 'SOI marker expected'].<br>
        self parseStartOfInput.<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>parseHuffmanTable (in category 'marker parsing') -----<br>
  parseHuffmanTable<br>
<br>
        | length markerStart index bits count huffVal isACTable hTable |<br>
        markerStart := self position.<br>
        length := self nextWord.<br>
        [self position - markerStart >= length] whileFalse:<br>
                [index := self next.<br>
                isACTable := (index bitAnd: 16r10) ~= 0.<br>
                index := (index bitAnd: 16r0F) + 1.<br>
                index > HuffmanTableSize<br>
+                       ifTrue: [self error: ('image has more than {1} quantization tables' translated format: {HuffmanTableSize})].<br>
-                       ifTrue: [self error: 'image has more than ', HuffmanTableSize printString,<br>
-                               ' quantization tables'].<br>
                bits := self next: 16.<br>
                count := bits sum.<br>
                (count > 256 or: [(count > (length - (self position - markerStart)))])<br>
+                       ifTrue: [self error: 'Huffman Table count is incorrect' translated].<br>
-                       ifTrue: [self error: 'Huffman Table count is incorrect'].<br>
                huffVal := self next: count.<br>
                hTable := stream buildLookupTable: huffVal counts: bits.<br>
                isACTable<br>
                        ifTrue:<br>
                                [self hACTable at: index put: hTable]<br>
                        ifFalse:<br>
                                [self hDCTable at: index put: hTable]].!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>parseNextMarker (in category 'marker parsing') -----<br>
  parseNextMarker<br>
        "Parse the next marker of the stream"<br>
<br>
        | byte discardedBytes |<br>
        discardedBytes := 0.<br>
        [(byte := self next) = 16rFF] whileFalse: [discardedBytes := discardedBytes + 1].       <br>
        [[(byte := self next) = 16rFF] whileTrue. byte = 16r00] whileTrue:<br>
                [discardedBytes := discardedBytes + 2].<br>
        discardedBytes > 0 ifTrue: [self "notifyWithLabel: 'warning: extraneous data discarded'"].<br>
        self perform:<br>
                (JFIFMarkerParser<br>
                        at: byte<br>
                        ifAbsent:<br>
                                [(self okToIgnoreMarker: byte)<br>
                                        ifTrue: [#skipMarker]<br>
+                                       ifFalse: [self error: ('marker {1} cannot be handled' translated format: {byte printStringHex})]])!<br>
-                                       ifFalse: [self error: 'marker ', byte printStringHex , ' cannot be handled']])!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>parseQuantizationTable (in category 'marker parsing') -----<br>
  parseQuantizationTable<br>
<br>
        | length markerStart n prec value table |<br>
        markerStart := self position.<br>
        length := self nextWord.<br>
        [self position - markerStart >= length] whileFalse:<br>
                [value := self next.<br>
                n := (value bitAnd: 16r0F) + 1.<br>
                prec := (value >> 4) > 0.<br>
                n > QuantizationTableSize<br>
+                        ifTrue: [self error: ('image has more than {1} quantization tables' translated format: {QuantizationTableSize})].<br>
-                        ifTrue: [self error: 'image has more than ',<br>
-                               QuantizationTableSize printString,<br>
-                               ' quantization tables'].<br>
                table := IntegerArray new: DCTSize2.<br>
                1 to: DCTSize2 do:<br>
                        [:i |<br>
                        value := (prec<br>
                                ifTrue: [self nextWord]<br>
                                ifFalse: [self next]).<br>
                        table at: (JPEGNaturalOrder at: i) put: value].<br>
                self useFloatingPoint ifTrue: [self scaleQuantizationTable: table].<br>
                self qTable at: n put: table]!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>parseStartOfFile (in category 'marker parsing') -----<br>
  parseStartOfFile<br>
<br>
        | length markerStart value n |<br>
        markerStart := self position.<br>
        length := self nextWord.<br>
        dataPrecision := self next.<br>
        dataPrecision = 8<br>
+               ifFalse: [self error: ('cannot handle {1}-bit components' translated format: {dataPrecision})].<br>
-               ifFalse: [self error: 'cannot handle ', dataPrecision printString, '-bit components'].<br>
        height := self nextWord.<br>
        width := self nextWord.<br>
        n := self next.<br>
+       (height = 0) | (width = 0) | (n = 0) ifTrue: [self error: 'empty image' translated].<br>
-       (height = 0) | (width = 0) | (n = 0) ifTrue: [self error: 'empty image'].<br>
        (length - (self position - markerStart)) ~= (n * 3)<br>
+               ifTrue: [self error: 'component length is incorrect' translated].<br>
-               ifTrue: [self error: 'component length is incorrect'].<br>
        components := Array new: n.<br>
        1 to: components size do:<br>
                [:i |<br>
                components<br>
                        at: i<br>
                        put:<br>
                                (JPEGColorComponent new<br>
                                        id: self next;<br>
                                        "heightInBlocks: (((value := self next) >> 4) bitAnd: 16r0F);<br>
                                        widthInBlocks: (value bitAnd: 16r0F);"<br>
                                        widthInBlocks: (((value := self next) >> 4) bitAnd: 16r0F);<br>
                                        heightInBlocks: (value bitAnd: 16r0F);<br>
<br>
                                        qTableIndex: self next + 1)]!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>parseStartOfScan (in category 'marker parsing') -----<br>
  parseStartOfScan<br>
<br>
        | length n id value dcNum acNum comp |<br>
        length := self nextWord.<br>
        n := self next.<br>
+       (length ~= (n*2 + 6)) | (n < 1) ifTrue: [self error: 'SOS length is incorrect' translated].<br>
-       (length ~= (n*2 + 6)) | (n < 1) ifTrue: [self error: 'SOS length is incorrect'].<br>
        currentComponents := Array new: n.<br>
        1 to: n do: [:i |<br>
                id := self next.<br>
                value := self next.<br>
                dcNum := (value >> 4) bitAnd: 16r0F.<br>
                acNum := value bitAnd: 16r0F.<br>
                comp := components detect: [:c | c id = id].<br>
                comp<br>
                        dcTableIndex: dcNum+1;<br>
                        acTableIndex: acNum+1.<br>
                currentComponents at: i put: comp].<br>
        ss := self next.<br>
        se := self next.<br>
        value := self next.<br>
        ah := (value >> 4) bitAnd: 16r0F.<br>
        al := value bitAnd: 16r0F.<br>
        self initialSOSSetup.<br>
        self perScanSetup.<br>
        sosSeen := true!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter>>perScanSetup (in category 'marker parsing') -----<br>
  perScanSetup<br>
<br>
        mcusPerRow := (width / (mcuWidth * DCTSize)) ceiling.<br>
        mcuRowsInScan := (height / (mcuHeight * DCTSize)) ceiling.<br>
        (currentComponents size = 3 or: [currentComponents size = 1])<br>
+               ifFalse: [self error: 'JPEG color space not recognized' translated].<br>
-               ifFalse: [self error: 'JPEG color space not recognized'].<br>
        mcuMembership := OrderedCollection new.<br>
        currentComponents withIndexDo:<br>
                [:c :i |<br>
                c priorDCValue: 0.<br>
                mcuMembership addAll: ((1 to: c totalMcuBlocks) collect: [:b | i])].<br>
        mcuMembership := mcuMembership asArray.<br>
        mcuSampleBuffer := (1 to: mcuMembership size) collect: [:i | IntegerArray new: DCTSize2].<br>
        currentComponents withIndexDo:<br>
                [:c :i |<br>
                        c initializeSampleStreamBlocks:<br>
                                ((1 to: mcuMembership size)<br>
                                        select: [:j | i = (mcuMembership at: j)]<br>
                                        thenCollect: [:j | mcuSampleBuffer at: j])].<br>
        mcuImageBuffer := Form<br>
                extent: (mcuWidth @ mcuHeight) * DCTSize<br>
                depth: 32.<br>
        restartsToGo := restartInterval.!<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter2>>compress:quality: (in category 'public access') -----<br>
  compress: aForm quality: quality<br>
        "Encode the given Form and answer the compressed ByteArray. Quality goes from 0 (low) to 100 (high), where -1 means default.<br>
        We can only compress:<br>
                * 32-bit deep Forms <br>
                * -32-bit deep Forms<br>
                * 16-bit deep Forms<br>
                * -16-bit deep Forms<br>
                * GrayScale ColorForms (see #isGrayScale)"<br>
        | sourceForm jpegCompressStruct jpegErrorMgr2Struct buffer byteCount |<br>
<br>
        aForm unhibernate.<br>
<br>
        sourceForm := self supports8BitGrayscaleJPEGs <br>
                ifTrue: [ <br>
                        (aForm depth = 32) | (aForm depth = 16) | (aForm isGrayScale)<br>
                                ifTrue: [aForm]<br>
                                ifFalse: [aForm asFormOfDepth: 32 ]]<br>
                ifFalse: [<br>
                        (aForm nativeDepth > 0) & ((aForm depth = 32) | ((aForm depth = 16) & (aForm width even)))<br>
                                ifTrue: [aForm]<br>
                                ifFalse: [aForm asFormOfDepth: 32 ]].<br>
<br>
        jpegCompressStruct := ByteArray new: self primJPEGCompressStructSize.<br>
        jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.<br>
        buffer := ByteArray new: sourceForm width * sourceForm height + 1024.<br>
        byteCount := self primJPEGWriteImage: jpegCompressStruct <br>
                onByteArray: buffer<br>
                form: sourceForm<br>
                quality: quality<br>
                progressiveJPEG: false<br>
                errorMgr: jpegErrorMgr2Struct.<br>
+       byteCount = 0 ifTrue: [self error: 'buffer too small for compressed data' translated].<br>
-       byteCount = 0 ifTrue: [self error: 'buffer too small for compressed data'].<br>
        ^ buffer copyFrom: 1 to: byteCount<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter2>>nextPutImage:quality:progressiveJPEG: (in category 'public access') -----<br>
  nextPutImage: aForm quality: quality progressiveJPEG: progressiveFlag<br>
        "Encode the given Form on my stream with the given settings. Quality goes from 0 (low) to 100 (high), where -1 means default. If progressiveFlag is true, encode as a progressive JPEG.<br>
        We can compress:<br>
                * 32-bit deep Forms <br>
                * -32-bit deep Forms<br>
                * 16-bit deep<br>
                * -16-bit deep<br>
                * GrayScale ColorForms (see #isGrayScale)"<br>
<br>
        | sourceForm jpegCompressStruct jpegErrorMgr2Struct buffer byteCount |<br>
<br>
        aForm unhibernate.<br>
<br>
        sourceForm := self supports8BitGrayscaleJPEGs <br>
                ifTrue: [ <br>
                        (aForm depth = 32) | (aForm depth = 16) | (aForm isGrayScale)<br>
                                ifTrue: [aForm]<br>
                                ifFalse: [aForm asFormOfDepth: 32 ]]<br>
                ifFalse: [<br>
                        (aForm nativeDepth > 0) & ((aForm depth = 32) | ((aForm depth = 16) & (aForm width even)))<br>
                                ifTrue: [aForm]<br>
                                ifFalse: [aForm asFormOfDepth: 32 ]].<br>
<br>
        jpegCompressStruct := ByteArray new: self primJPEGCompressStructSize.<br>
        jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.<br>
        buffer := ByteArray new: sourceForm width * sourceForm height + 1024.<br>
        "Try to write the image. Retry with a larger buffer if needed."<br>
        [<br>
                byteCount := self primJPEGWriteImage: jpegCompressStruct <br>
                        onByteArray: buffer<br>
                        form: sourceForm<br>
                        quality: quality<br>
                        progressiveJPEG: progressiveFlag<br>
                        errorMgr: jpegErrorMgr2Struct.<br>
                byteCount = 0 and: [ buffer size < (sourceForm width * sourceForm height * 3 + 1024) ] ]<br>
                        whileTrue: [ buffer := ByteArray new: buffer size * 2 ].<br>
+       byteCount = 0 ifTrue: [ self error: 'buffer too small for compressed data' translated ].<br>
-       byteCount = 0 ifTrue: [ self error: 'buffer too small for compressed data' ].<br>
        stream next: byteCount putAll: buffer startingAt: 1.<br>
        self close.<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: JPEGReadWriter2>>uncompress:into:doDithering: (in category 'public access') -----<br>
  uncompress: aByteArray into: aForm doDithering: ditherFlag<br>
        "Uncompress an image from the given ByteArray into the given Form. <br>
        Fails if the given Form has the wrong dimensions or depth.<br>
        We can read RGB JPEGs into:<br>
                * 32-bit Forms<br>
                * -32-bit Forms<br>
                * 16-bit Forms (with or without dithering!!)<br>
                * -16-bit Forms (with or without dithering!!)<br>
        We can read grayscale JPEGs into:<br>
                * 32-bit Forms<br>
                * -32-bit Forms<br>
                * 16-bit Forms (with or without dithering!!)<br>
                * -16-bit Forms (with or without dithering!!)<br>
                * 8-bit grayScale ColorForms (see #isGrayScale)<br>
                * -8-bit grayScale ColorForms (see #isGrayScale)"<br>
<br>
        | jpegDecompressStruct jpegErrorMgr2Struct width height components |<br>
<br>
        aForm unhibernate.<br>
<br>
        jpegDecompressStruct := ByteArray new: self primJPEGDecompressStructSize.<br>
        jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.<br>
        self <br>
                primJPEGReadHeader: jpegDecompressStruct <br>
                fromByteArray: aByteArray<br>
                errorMgr: jpegErrorMgr2Struct.<br>
        width := self primImageWidth: jpegDecompressStruct.<br>
        height := self primImageHeight: jpegDecompressStruct.<br>
        components := self primImageNumComponents: jpegDecompressStruct.<br>
<br>
        ((aForm width = width) & (aForm height = height)) ifFalse: [<br>
+               ^ self error: 'form dimensions do not match' translated ].<br>
-               ^ self error: 'form dimensions do not match' ].<br>
        self supports8BitGrayscaleJPEGs<br>
                ifTrue: [<br>
                        components = 3<br>
                                ifTrue: [<br>
                                        aForm depth = 8<br>
+                                               ifTrue: [ ^ self error: 'Cannot uncompress multi-channel JPEGs into 8-bit deep forms' translated ]].<br>
-                                               ifTrue: [ ^ self error: 'Cannot uncompress multi-channel JPEGs into 8-bit deep forms' ]].<br>
                        components = 1<br>
                                ifTrue: [<br>
                                        aForm depth = 8<br>
                                                ifTrue: [<br>
                                                        aForm isGrayScale <br>
+                                                               ifFalse: [ ^ self error: 'Cannot uncompress single-channel JPEGs into 8-bit deep forms that are not grayscale' translated ]]]]<br>
-                                                               ifFalse: [ ^ self error: 'Cannot uncompress single-channel JPEGs into 8-bit deep forms that are not grayscale' ]]]]<br>
<br>
                ifFalse: [<br>
                        aForm nativeDepth < 0<br>
+                               ifTrue: [ ^ self error: 'Current plugin version doesn''t support uncompressing JPEGs into little-endian forms' translated ]<br>
-                               ifTrue: [ ^ self error: 'Current plugin version doesn''t support uncompressing JPEGs into little-endian forms' ]<br>
                                ifFalse: [<br>
                                        aForm depth = 16<br>
                                                ifTrue: [<br>
                                                        width odd<br>
+                                                               ifTrue: [ ^ self error: 'Current plugin version doesn''t support uncompressing JPEGs with an odd width into 16-bit deep forms' translated ]].<br>
-                                                               ifTrue: [ ^ self error: 'Current plugin version doesn''t support uncompressing JPEGs with an odd width into 16-bit deep forms' ]].<br>
                                        aForm depth = 8<br>
+                                               ifTrue: [ ^ self error: 'Current plugin version doesn''t support uncompressing JPEGs into 8-bit deep forms' translated ]]].<br>
-                                               ifTrue: [ ^ self error: 'Current plugin version doesn''t support uncompressing JPEGs into 8-bit deep forms' ]]].<br>
<br>
        self primJPEGReadImage: jpegDecompressStruct<br>
                fromByteArray: aByteArray<br>
                onForm: aForm<br>
                doDithering: ditherFlag<br>
+               errorMgr: jpegErrorMgr2Struct.!<br>
-               errorMgr: jpegErrorMgr2Struct.<br>
- !<br>
<br>
Item was changed:<br>
  ----- Method: LayoutFrame class>>classVersion (in category 'accessing') -----<br>
  classVersion<br>
+       ^ 2 "fractions and offsets are never 'nil' anymore"<br>
-       ^1 "changed treatment of bottomOffset and rightOffset"<br>
  !<br>
<br>
Item was added:<br>
+ ----- Method: LayoutFrame class>>withClassVersion: (in category 'objects from disk') -----<br>
+ withClassVersion: aVersion<br>
+ <br>
+       aVersion <= self classVersion ifTrue: [^ self].<br>
+       ^ super withClassVersion: aVersion!<br>
<br>
Item was changed:<br>
  ----- Method: LayoutFrame>>convertToCurrentVersion:refStream: (in category 'objects from disk') -----<br>
  convertToCurrentVersion: varDict refStream: smartRefStrm<br>
        | className oldClassVersion |<br>
<br>
        "JW 2/1/2001"<br>
        "Since class version isn't passed in varDict, look it up through smartRefSrm."<br>
        className := varDict at: #ClassName.<br>
        oldClassVersion := (smartRefStrm structures at: className) first.<br>
+       (oldClassVersion = 0) ifTrue: [ self negateBottomRightOffsets; fixup ].<br>
+       (oldClassVersion = 1) ifTrue: [ self fixup ].<br>
-       (oldClassVersion = 0) ifTrue: [ self negateBottomRightOffsets ].<br>
        ^super convertToCurrentVersion: varDict refStream: smartRefStrm.<br>
  !<br>
<br>
Item was changed:<br>
+ ----- Method: LayoutFrame>>fixup (in category 'objects from disk') -----<br>
- ----- Method: LayoutFrame>>fixup (in category 'initialize-release') -----<br>
  fixup<br>
        "Set-up default value for un-initialized layout frames"<br>
<br>
        "LayoutFrame allInstancesDo: [:e | e fixup]."<br>
<br>
        leftFraction ifNil: [leftFraction := 0].<br>
        leftOffset ifNil: [leftOffset := 0].<br>
        topFraction ifNil: [topFraction := 0].<br>
        topOffset ifNil: [topOffset := 0].<br>
        rightFraction ifNil: [rightFraction := 0].<br>
        rightOffset ifNil: [rightOffset := 0].<br>
        bottomFraction ifNil: [bottomFraction := 0].<br>
        bottomOffset ifNil: [bottomOffset := 0].!<br>
<br>
Item was changed:<br>
  ----- Method: PCXReadWriter>>readPalette (in category 'private-decoding') -----<br>
  readPalette<br>
<br>
        | r g b array |<br>
+       self next = 12 ifFalse: [self error: 'no Color Palette!!' translated].<br>
-       self next = 12 ifFalse: [self error: 'no Color Palette!!'].<br>
        array := Array new: (1 bitShift: bitsPerPixel).<br>
        1 to: array size do:<br>
                [:i |<br>
                r := self next.  g := self next.  b := self next.<br>
                array at: i put: (Color r: r g: g b: b range: 255)].<br>
        ^ array.<br>
  !<br>
<br>
Item was changed:<br>
+ ----- Method: PNGReadWriter class>>computeSwizzleMapForDepth: (in category 'class initialization') -----<br>
- ----- Method: PNGReadWriter class>>computeSwizzleMapForDepth: (in category 'as yet unclassified') -----<br>
  computeSwizzleMapForDepth: depth<br>
        "Answer a map that maps pixels in a word to their opposite location. Used for 'middle-endian' forms where the byte-order is different from the bit order (good joke, eh?)."<br>
        | map swizzled |<br>
        map := Bitmap new: 256.<br>
        depth = 4 ifTrue:[<br>
                0 to: 255 do:[:pix|<br>
                        swizzled := 0.<br>
                        swizzled := swizzled bitOr: (((pix bitShift: 0) bitAnd: 15) bitShift: 4).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -4) bitAnd: 15) bitShift: 0).<br>
                        map at: pix+1 put: swizzled.<br>
                ].<br>
                ^ColorMap colors: map<br>
        ].<br>
<br>
        depth = 2 ifTrue:[<br>
                0 to: 255 do:[:pix|<br>
                        swizzled := 0.<br>
                        swizzled := swizzled bitOr: (((pix bitShift: 0) bitAnd: 3) bitShift: 6).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -2) bitAnd: 3) bitShift: 4).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -4) bitAnd: 3) bitShift: 2).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -6) bitAnd: 3) bitShift: 0).<br>
                        map at: pix+1 put: swizzled.<br>
                ].<br>
                ^ColorMap colors: map<br>
        ].<br>
<br>
        depth = 1 ifTrue:[<br>
                0 to: 255 do:[:pix|<br>
                        swizzled := 0.<br>
                        swizzled := swizzled bitOr: (((pix bitShift: 0) bitAnd: 1) bitShift: 7).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -1) bitAnd: 1) bitShift: 6).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -2) bitAnd: 1) bitShift: 5).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -3) bitAnd: 1) bitShift: 4).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -4) bitAnd: 1) bitShift: 3).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -5) bitAnd: 1) bitShift: 2).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -6) bitAnd: 1) bitShift: 1).<br>
                        swizzled := swizzled bitOr: (((pix bitShift: -7) bitAnd: 1) bitShift: 0).<br>
                        map at: pix+1 put: swizzled.<br>
                ].<br>
                ^ColorMap colors: map<br>
        ].<br>
        self error: 'Unrecognized depth'!<br>
<br>
Item was changed:<br>
+ ----- Method: PNGReadWriter class>>debugging: (in category 'support') -----<br>
- ----- Method: PNGReadWriter class>>debugging: (in category 'as yet unclassified') -----<br>
  debugging: aBoolean<br>
<br>
        Debugging := aBoolean!<br>
<br>
Item was changed:<br>
+ ----- Method: PNGReadWriter class>>initialize (in category 'class initialization') -----<br>
- ----- Method: PNGReadWriter class>>initialize (in category 'as yet unclassified') -----<br>
  initialize<br>
        "<br>
        PNGReadWriter initialize<br>
        "<br>
<br>
        BPP := {        #(1 2 4 8 16).<br>
                        #(0 0 0 0 0).<br>
                        #(0 0 0 24 48).<br>
                        #(1 2 4 8 0).<br>
                        #(0 0 0 16 32).<br>
                        #(0 0 0 0 0).<br>
                        #(0 0 0 32 64).<br>
                        #(0 0 0 0 0) }.<br>
<br>
        BlockHeight := #(8 8 4 4 2 2 1).<br>
        BlockWidth := #(8 4 4 2 2 1 1).<br>
<br>
        StandardColors := Color indexedColors collect:[:aColor|<br>
                Color <br>
                        r: (aColor red * 255) truncated / 255<br>
                        g: (aColor green * 255) truncated / 255<br>
                        b: (aColor blue * 255) truncated / 255.<br>
        ].<br>
<br>
        StandardSwizzleMaps := Array new: 4.<br>
        #(1 2 4) do:[:i| StandardSwizzleMaps at: i put: (self computeSwizzleMapForDepth: i)].!<br>
<br>
Item was removed:<br>
- ----- Method: PNGReadWriter class>>insertMorph:named:into: (in category 'as yet unclassified') -----<br>
- insertMorph: aMorph named: aString into: aBook<br>
- <br>
-       | newPage |<br>
- <br>
-       aBook ifNil: [^self].<br>
-       newPage := aBook insertPageLabel: aString morphs: {aMorph}.<br>
-       newPage color: Color lightYellow.<br>
-       newPage extent: (<br>
-               newPage submorphs inject: 10@10 into: [ :ex :m |<br>
-                       m left: 10.<br>
-                       ex max: m width @ m bottom<br>
-               ]<br>
-       ) + (20@20).<br>
- !<br>
<br>
Item was changed:<br>
  ----- Method: PNGReadWriter>>nextImage (in category 'accessing') -----<br>
  nextImage<br>
        bigEndian := Smalltalk isBigEndian.<br>
        filtersSeen := Bag new.<br>
        idatChunkStream := nil.<br>
        transparentPixelValue := nil.<br>
        unknownChunks := Set new.<br>
        stream reset.<br>
        stream binary.<br>
        stream skip: 8.<br>
        [stream atEnd] whileFalse: [self processNextChunk].<br>
        "Set up our form"<br>
        palette ifNotNil: <br>
                        ["Dump the palette if it's the same as our standard palette"<br>
<br>
                        palette = (StandardColors copyFrom: 1 to: palette size) <br>
                                ifTrue: [palette := nil]].<br>
        (depth <= 8 and: [palette notNil]) <br>
                ifTrue: <br>
                        [form := ColorForm extent: width @ height depth: depth.<br>
                        form colors: palette]<br>
                ifFalse: [form := Form extent: width @ height depth: depth].<br>
        backColor ifNotNil: [form fillColor: backColor].<br>
        idatChunkStream <br>
+               ifNil: [ self error: 'image data is missing' translated ]<br>
-               ifNil: [ self error: 'image data is missing' ]<br>
                ifNotNil: [ self processIDATChunk ].<br>
        unknownChunks isEmpty <br>
                ifFalse: <br>
                        ["Transcript show: ' ',unknownChunks asSortedCollection asArray printString."<br>
<br>
                        ].<br>
        self debugging <br>
                ifTrue: <br>
                        [Transcript<br>
                                cr;<br>
                                show: 'form = ' , form printString.<br>
                        Transcript<br>
                                cr;<br>
                                show: 'colorType = ' , colorType printString.<br>
                        Transcript<br>
                                cr;<br>
                                show: 'interlaceMethod = ' , interlaceMethod printString.<br>
                        Transcript<br>
                                cr;<br>
                                show: 'filters = ' , filtersSeen sortedCounts asArray printString].<br>
        ^form!<br>
<br>
Item was changed:<br>
  ----- Method: PNGReadWriter>>processInterlaced (in category 'chunks') -----<br>
  processInterlaced<br>
        | z startingCol colIncrement rowIncrement startingRow |<br>
        startingCol := #(0 4 0 2 0 1 0 ).<br>
        colIncrement := #(8 8 4 4 2 2 1 ).<br>
        rowIncrement := #(8 8 8 4 4 2 2 ).<br>
        startingRow := #(0 0 4 0 2 0 1 ).<br>
        z := ZLibReadStream <br>
                on: idatChunkStream originalContents<br>
                from: 1<br>
                to: idatChunkStream position.<br>
        1 to: 7 do: [:pass |<br>
                | cx sc bytesPerPass |<br>
                (self doPass: pass)<br>
                        ifTrue:<br>
                                [cx := colIncrement at: pass.<br>
                                sc := startingCol at: pass.<br>
                                bytesPerPass := width - sc + cx - 1 // cx * bitsPerPixel + 7 // 8.<br>
                                prevScanline := ByteArray new: bytesPerPass.<br>
                                thisScanline := ByteArray new: bytesPerScanline.<br>
                                (startingRow at: pass)<br>
                                        to: height - 1<br>
                                        by: (rowIncrement at: pass)<br>
                                        do: [:y |<br>
                                                | filter temp |<br>
                                                filter := z next.<br>
                                                filtersSeen add: filter.<br>
                                                (filter isNil or: [(filter between: 0 and: 4) not])<br>
                                                        ifTrue: [^ self].<br>
                                                thisScanline := z next: bytesPerPass into: thisScanline startingAt: 1.<br>
                                                self filterScanline: filter count: bytesPerPass.<br>
                                                self copyPixels: y at: sc by: cx.<br>
                                                temp := prevScanline.<br>
                                                prevScanline := thisScanline.<br>
                                                thisScanline := temp.<br>
                                        ]<br>
                                ]<br>
        ].<br>
+       z atEnd ifFalse:[self error: 'Unexpected data' translated].!<br>
-       z atEnd ifFalse:[self error:'Unexpected data'].!<br>
<br>
Item was changed:<br>
  ----- Method: PNGReadWriter>>processNextChunk (in category 'chunks') -----<br>
  processNextChunk<br>
<br>
        | length chunkType crc chunkCrc |<br>
<br>
        length := self nextLong.<br>
<br>
        chunkType := (self next: 4) asString.<br>
        (chunk isNil or: [ chunk size ~= length ])<br>
                ifTrue: [ chunk := self next: length ]<br>
                ifFalse: [ stream next: length into: chunk startingAt: 1 ].<br>
        chunkCrc := self nextLong bitXor: 16rFFFFFFFF.<br>
        crc := self updateCrc: 16rFFFFFFFF from: 1 to: 4 in: chunkType.<br>
        crc := self updateCrc: crc from: 1 to: length in: chunk.<br>
        crc = chunkCrc ifFalse:[<br>
+               self error: ('PNGReadWriter crc error in chunk {1}' translated format: {chunkType}).<br>
-               self error: 'PNGReadWriter crc error in chunk ', chunkType.<br>
        ].<br>
<br>
        chunkType = 'IEND' ifTrue: [stream setToEnd. ^self      "*should* be the last chunk"].<br>
        chunkType = 'sBIT' ifTrue: [^self processSBITChunk "could indicate unusual sample depth in original"].<br>
        chunkType = 'gAMA' ifTrue: [^self       "indicates gamma correction value"].<br>
        chunkType = 'bKGD' ifTrue: [^self processBackgroundChunk].<br>
        chunkType = 'pHYs' ifTrue: [^self processPhysicalPixelChunk].<br>
        chunkType = 'tRNS' ifTrue: [^self processTransparencyChunk].<br>
<br>
        chunkType = 'IHDR' ifTrue: [^self processIHDRChunk].<br>
        chunkType = 'PLTE' ifTrue: [^self processPLTEChunk].<br>
        chunkType = 'IDAT' ifTrue: [<br>
                "---since the compressed data can span multiple<br>
                chunks, stitch them all together first. later,<br>
                if memory is an issue, we need to figure out how<br>
                to do this on the fly---"<br>
                idatChunkStream<br>
                        ifNil: [ idatChunkStream := WriteStream with: chunk copy ]<br>
                        ifNotNil: [ idatChunkStream nextPutAll: chunk ].<br>
                ^self<br>
        ].<br>
        unknownChunks add: chunkType.<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: PNGReadWriter>>processPLTEChunk (in category 'chunks') -----<br>
  processPLTEChunk<br>
<br>
        | colorCount i |<br>
<br>
+       colorCount := chunk size // 3.<br>
+       self flag: #todo. "validate colorCount against depth"<br>
-       colorCount := chunk size // 3. "TODO - validate colorCount against depth"<br>
        palette := Array new: colorCount.<br>
        0 to: colorCount-1 do: [ :index |<br>
                i := index * 3 + 1.<br>
                palette at: index+1 put:<br>
                        (Color r: (chunk at: i)/255.0 g: (chunk at: i+1)/255.0 b: (chunk at: i+2)/255.0)<br>
                ].!<br>
<br>
Item was changed:<br>
  ----- Method: PNGReadWriter>>writeSBITChunkOn: (in category 'writing') -----<br>
  writeSBITChunkOn: aStream<br>
        "Write the IDAT chunk"<br>
        aStream nextPutAll: 'sBIT' asByteArray.<br>
+       form depth = 16 ifFalse: [self notYetImplemented].<br>
-       form depth = 16 ifFalse:[self error: 'Unimplemented feature'].<br>
        aStream nextPut: 5.<br>
        aStream nextPut: 5.<br>
        aStream nextPut: 5.<br>
        aStream nextPut: 1.!<br>
<br>
Item was changed:<br>
  ----- Method: PNMReadWriter>>nextImage (in category 'reading') -----<br>
  nextImage<br>
        "read one image"<br>
        | data p |<br>
        first ifNil:[<br>
                first := false.<br>
                data := stream contentsOfEntireFile.<br>
                stream := (RWBinaryOrTextStream with: data) reset.<br>
        ]<br>
        ifNotNil:[<br>
                type < 4 ifTrue:[<br>
+                       self error: 'Plain PBM, PGM or PPM have only one image' translated<br>
-                       self error:'Plain PBM, PGM or PPM have only one image'<br>
                ].<br>
        ].<br>
        stream ascii.<br>
        p := stream next.<br>
        type := (stream next) asInteger - 48.<br>
        (p = $P and:[type > 0 and:[type < 8]]) ifFalse:[<br>
+               self error: 'Not a PNM file' translated<br>
-               self error:'Not a PNM file'<br>
        ].<br>
        type = 7 ifTrue:[<br>
                self readHeaderPAM<br>
        ]<br>
        ifFalse: [<br>
                self readHeader<br>
        ].<br>
        type caseOf: {<br>
                [1]     ->      [^self readPlainBW].<br>
                [2]     ->      [^self readPlainGray].<br>
                [3]     ->      [^self readPlainRGB].<br>
                [4]     ->      [^self readBWreverse: false].<br>
                [5]     ->      [^self readGray].<br>
                [6]     ->      [^self readRGB].<br>
                [7]     ->      [       "PAM"<br>
                                        (tupleType asUppercase) caseOf: {<br>
                                                ['BLACKANDWHITE']               -> [^self readBWreverse: true].<br>
                                                ['GRAYSCALE']                   -> [^self readGray].<br>
                                                ['RGB']                                         -> [^self readRGB].<br>
+                                               ['RGB_ALPHA']                   -> [^self notYetImplemented].<br>
+                                               ['GRAYSCALE_ALPHA']     -> [^self notYetImplemented].<br>
-                                               ['RGB_ALPHA']                   -> [^self error:'Not implemented'].<br>
-                                               ['GRAYSCALE_ALPHA']     -> [^self error:'Not implemented'].<br>
                                        } otherwise: [^self readData].<br>
                                ]<br>
        }!<br>
<br>
Item was changed:<br>
  ----- Method: PNMReadWriter>>readGray (in category 'reading') -----<br>
  readGray<br>
        "gray form, return ColorForm with gray ramp"<br>
        | form poker |<br>
+       maxValue > 255 ifTrue:[self error: ('Gray value > {1} bits not supported in Squeak' translated format: {8})].<br>
-       maxValue > 255 ifTrue:[self error:'Gray value > 8 bits not supported in Squeak'].<br>
        stream binary.<br>
        form := ColorForm extent: cols@rows depth: depth.<br>
        form colors: nil.<br>
        poker := BitBlt bitPokerToForm: form.<br>
        0 to: rows-1 do: [:y |<br>
                0 to: cols-1 do: [:x |<br>
                        |val|<br>
                        val := stream next.<br>
                        poker pixelAt: x@y put: val.<br>
                ]<br>
        ].<br>
        "a better way is using a gamma corrected palette"<br>
        form colors: ((0 to: 255) collect:[:c|<br>
                c > maxValue<br>
                        ifTrue:[Color white]<br>
                        ifFalse:[Color gray: (c/maxValue) asFloat]]).<br>
        form colors at: 1 put: (Color black).<br>
        ^form<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: PNMReadWriter>>readHeader (in category 'reading') -----<br>
  readHeader<br>
        "read header for pbm, pgm or ppm"<br>
        | tokens aux d c  |<br>
        tokens := OrderedCollection new.<br>
        aux := self getTokenPbm: tokens.<br>
        cols := aux at: 1. tokens := aux at: 2.<br>
        aux := self getTokenPbm: tokens.<br>
        rows := aux at: 1. tokens := aux at: 2.<br>
<br>
        (type = 1 or:[type = 4]) ifTrue:[<br>
                maxValue := 1<br>
        ]<br>
        ifFalse: [<br>
                aux := self getTokenPbm: tokens.<br>
                maxValue := aux at: 1. tokens := aux at: 2.<br>
        ].<br>
        d := {1 . 2 . 4 .       8 .             16 . 32}.<br>
        c := {2 . 4 . 16 . 256 . 32768 . 16777216}. <br>
        (type = 3 or:[type = 6]) ifTrue: [<br>
                maxValue >= 65536 ifTrue:[<br>
+                       self error: ('Pixmap > {1} bits not supported in PPM' translated format: {48})<br>
-                       self error:'Pixmap > 48 bits not supported in PPM'<br>
                ].<br>
                maxValue >= 256 ifTrue:[<br>
+                       self error: ('Pixmap > {1} bits are not supported in Squeak' translated format: {32})<br>
-                       self error:'Pixmap > 32 bits are not supported in Squeak'<br>
                ].<br>
                maxValue < 32 ifTrue:[depth := 16] ifFalse:[depth := 32].<br>
        ]<br>
        ifFalse: [<br>
                depth := nil.<br>
                1 to: c size do:[:i| ((c at: i) > maxValue and:[depth = nil]) ifTrue:[depth:=d at: i]].<br>
        ].<br>
        Transcript cr; show: 'PBM file class ', type asString, ' size ', cols asString, ' x ', <br>
                rows asString, ' maxValue =', maxValue asString, ' depth=', depth asString.<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: PNMReadWriter>>readPlainBW (in category 'reading') -----<br>
  readPlainBW<br>
        "plain BW"<br>
        | val form poker |<br>
        form := Form extent: cols@rows depth: depth.<br>
        poker := BitBlt bitPokerToForm: form.<br>
        0 to: rows-1 do: [:y |<br>
                0 to: cols-1 do: [:x |<br>
                        [val := stream next. (val = $0 or:[val = $1])] whileFalse:[<br>
+                               val ifNil: [self error: 'End of file reading PBM' translated].<br>
-                               val ifNil:[self error:'End of file reading PBM'].<br>
                        ].<br>
                        poker pixelAt: x@y put: (val asInteger).<br>
                ]<br>
        ].<br>
        ^form<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: PNMReadWriter>>readPlainRGB (in category 'reading') -----<br>
  readPlainRGB<br>
        "RGB form, use 32 bits"<br>
        | val form poker tokens aux |<br>
+       maxValue > 255 ifTrue:[self error: ('RGB value > {1} bits not supported in Squeak' translated format: {32})].<br>
-       maxValue > 255 ifTrue:[self error:'RGB value > 32 bits not supported in Squeak'].<br>
        form := Form extent: cols@rows depth: 32.<br>
        poker := BitBlt bitPokerToForm: form.<br>
        tokens := OrderedCollection new.<br>
        0 to: rows-1 do: [:y |<br>
                0 to: cols-1 do: [:x | | r g b|<br>
                        aux := self getTokenPbm: tokens. r := aux at: 1. tokens := aux at: 2.<br>
                        aux := self getTokenPbm: tokens. g := aux at: 1. tokens := aux at: 2.<br>
                        aux := self getTokenPbm: tokens. b := aux at: 1. tokens := aux at: 2.<br>
                        val := self r: r g: g b: b for: depth.<br>
                        poker pixelAt: x@y put: val.<br>
                ]<br>
        ].<br>
        ^form<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: PNMReadWriter>>readRGB (in category 'reading') -----<br>
  readRGB<br>
        "RGB form, use 16/32 bits"<br>
        | val form poker sample shift |<br>
+       maxValue > 255 ifTrue:[self error: ('RGB value > {1} bits not supported in Squeak' translated format: {32})].<br>
-       maxValue > 255 ifTrue:[self error:'RGB value > 32 bits not supported in Squeak'].<br>
        stream binary.<br>
        form := Form extent: cols@rows depth: depth.<br>
        poker := BitBlt bitPokerToForm: form.<br>
        depth = 32 ifTrue:[shift := 8] ifFalse:[shift := 5].<br>
        0 to: rows-1 do: [:y |<br>
                0 to: cols-1 do: [:x |<br>
                        val := 16rFF.   "no transparency"<br>
                        1 to: 3 do: [:i |<br>
                                sample := stream next.<br>
                                val := val << shift + sample.<br>
                        ].<br>
                        poker pixelAt: x@y put: val.<br>
                ]<br>
        ].<br>
        ^form<br>
  !<br>
<br>
Item was added:<br>
+ ----- Method: Point>>exactCenter: (in category 'converting to rectangle') -----<br>
+ exactCenter: aPoint <br>
+       "Answer a Rectangle whose extent is the receiver and whose center is exactly aPoint. This is one of the infix ways of expressing the creation of a rectangle."<br>
+ <br>
+       ^ Rectangle exactCenter: aPoint extent: self!<br>
<br>
Item was added:<br>
+ ----- Method: Rectangle>>pointAtFraction: (in category 'rectangle functions') -----<br>
+ pointAtFraction: relativePoint<br>
+ <br>
+       | result |<br>
+       result := self origin + (self extent * relativePoint).<br>
+       ^ self isIntegerRectangle<br>
+               ifTrue: [result rounded]<br>
+               ifFalse: [result]!<br>
<br>
Item was added:<br>
+ ----- Method: Rectangle>>randomPoint (in category 'random') -----<br>
+ randomPoint<br>
+ <br>
+       ^ self randomPoint: ThreadSafeRandom value!<br>
<br>
Item was added:<br>
+ ----- Method: Rectangle>>randomPoint: (in category 'random') -----<br>
+ randomPoint: aGenerator<br>
+       "Answers a random point that lies within the receiver."<br>
+ <br>
+       ^ self pointAtFraction: aGenerator next @ aGenerator next!<br>
<br>
Item was changed:<br>
  AbstractFont subclass: #StrikeFont<br>
+       instanceVariableNames: 'characterToGlyphMap xTable glyphs name type minAscii maxAscii maxWidth strikeLength ascent descent xOffset raster subscript superscript emphasis derivativeFonts pointSize fallbackFont charIndexCompatibilitySlot lineGap lineGapSlice'<br>
-       instanceVariableNames: 'characterToGlyphMap xTable glyphs name type minAscii maxAscii maxWidth strikeLength ascent descent xOffset raster subscript superscript emphasis derivativeFonts pointSize fallbackFont charIndexCompatibilitySlot'<br>
        classVariableNames: 'DefaultStringScanner'<br>
        poolDictionaries: 'TextConstants'<br>
        category: 'Graphics-Fonts'!<br>
<br>
  !StrikeFont commentStamp: 'fbs 11/28/2013 08:50' prior: 0!<br>
  I represent a compact encoding of a set of Forms corresponding to characters in the ASCII character set. All the forms are placed side by side in a large form whose height is the font height, and whose width is the sum of all the character widths. The xTable variable gives the left-x coordinates of the subforms corresponding to the glyphs. Characters are mapped to glyphs by using the characterToGyphMap.<br>
<br>
  Subclasses can have non-trivial mapping rules as well as different representations for glyphs sizes (e.g., not using an xTable). If so, these classes should return nil when queried for xTable and/or the characterToGlyphMap. This will cause the CharacterScanner primitive to fail and query the font for the width of a character (so that a more programatical approach can be implemented).<br>
<br>
  For display, fonts need to implement two messages:<br>
        #installOn: aDisplayContext foregroundColor: foregroundColor backgroundColor: backgroundColor<br>
  This method installs the receiver (a font) on the given DisplayContext (which may be an instance of BitBlt or Canvas (or any of its subclasses). The font should take the appropriate action to initialize the display context so that further display operations can be optimized.<br>
        #displayString: aString on: aDisplayContext from: startIndex to: stopIndex at: aPoint kern: kernDelta<br>
  This method is called for each subsequent run of characters in aString which is to be displayed with the (previously installed) settings.<br>
  !<br>
<br>
Item was removed:<br>
- ----- Method: StrikeFont class>>cleanUp (in category 'class initialization') -----<br>
- cleanUp<br>
-       "Flush synthesized strike fonts"<br>
- <br>
-       self allInstancesDo:[:sf| sf reset].!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont class>>cleanUp: (in category 'class initialization') -----<br>
+ cleanUp: aggressive<br>
+ <br>
+       aggressive ifTrue: [self allInstancesDo: [:sf | sf reset]].!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont class>>createDejaVu: (in category 'font creation') -----<br>
  createDejaVu: pointSize<br>
        "Warning: Uses the methods in 'dejaVu font data' category, that will be removed soon (or are already removed) to save space."<br>
<br>
+       | base bold oblique boldOblique point actualPointSize |<br>
-       | base bold oblique boldOblique point |<br>
        point := pointSize asString.<br>
+       actualPointSize := self fixDejaVuPointSIze: pointSize.<br>
        base := (StrikeFont new<br>
                buildFromForm: (self perform: ('dejaVuSansBook', point, 'Form') asSymbol)<br>
                data: (self perform: ('dejaVuSansBook', point, 'Data') asSymbol)<br>
                name: 'Bitmap DejaVu Sans ', point)<br>
+                       pointSize: actualPointSize.<br>
-                       pointSize: pointSize.<br>
        bold := (StrikeFont new<br>
                buildFromForm:  (self perform: ('dejaVuSansBold', point, 'Form') asSymbol)<br>
                data: (self perform: ('dejaVuSansBold', point, 'Data') asSymbol)<br>
                name: 'Bitmap DejaVu Sans ', point, 'B')<br>
                        emphasis: 1;<br>
+                       pointSize: actualPointSize.<br>
-                       pointSize: pointSize.<br>
        oblique := (StrikeFont new<br>
                buildFromForm: (self perform: ('dejaVuSansOblique', point, 'Form') asSymbol)<br>
                data: (self perform: ('dejaVuSansOblique', point, 'Data') asSymbol)<br>
                name: 'Bitmap DejaVu Sans ', point, 'I')<br>
                        emphasis: 2;<br>
+                       pointSize: actualPointSize.<br>
-                       pointSize: pointSize.<br>
        boldOblique := (StrikeFont new<br>
                buildFromForm: (self perform: ('dejaVuSansBoldOblique', point, 'Form') asSymbol)<br>
                data: (self perform: ('dejaVuSansBoldOblique', point, 'Data') asSymbol)<br>
                name: 'Bitmap DejaVu Sans ', point, 'BI')<br>
                        emphasis: 3;<br>
+                       pointSize: actualPointSize.<br>
-                       pointSize: pointSize.<br>
<br>
        base derivativeFont: bold at: 1.<br>
        base derivativeFont: oblique at: 2.<br>
        base derivativeFont: boldOblique at: 3.<br>
<br>
        ^base!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont class>>createDejaVuDark: (in category 'font creation') -----<br>
  createDejaVuDark: pointSize<br>
<br>
+       | base bold oblique boldOblique point actualPointSize |<br>
-       | base bold oblique boldOblique point |<br>
        point := pointSize asString.<br>
+       actualPointSize := self fixDejaVuPointSIze: pointSize.<br>
        base := (StrikeFont new<br>
                buildFromForm: (self perform: ('dejaVuSansBookDark', point, 'Form') asSymbol)<br>
                data: (self perform: ('dejaVuSansBookDark', point, 'Data') asSymbol)<br>
                name: 'Darkmap DejaVu Sans', point)<br>
+                       pointSize: actualPointSize.<br>
-                       pointSize: pointSize.<br>
        bold := (StrikeFont new<br>
                buildFromForm:  (self perform: ('dejaVuSansBoldDark', point, 'Form') asSymbol)<br>
                data: (self perform: ('dejaVuSansBoldDark', point, 'Data') asSymbol)<br>
                name: 'Darkmap DejaVu Sans', point, 'B')<br>
                        emphasis: 1;<br>
+                       pointSize: actualPointSize.<br>
-                       pointSize: pointSize.<br>
        oblique := (StrikeFont new<br>
                buildFromForm: (self perform: ('dejaVuSansObliqueDark', point, 'Form') asSymbol)<br>
                data: (self perform: ('dejaVuSansObliqueDark', point, 'Data') asSymbol)<br>
                name: 'Darkmap DejaVu Sans', point, 'I')<br>
                        emphasis: 2;<br>
+                       pointSize: actualPointSize.<br>
-                       pointSize: pointSize.<br>
        boldOblique := (StrikeFont new<br>
                buildFromForm: (self perform: ('dejaVuSansBoldObliqueDark', point, 'Form') asSymbol)<br>
                data: (self perform: ('dejaVuSansBoldObliqueDark', point, 'Data') asSymbol)<br>
                name: 'Darkmap DejaVu Sans', point, 'BI')<br>
                        emphasis: 3;<br>
+                       pointSize: actualPointSize.<br>
-                       pointSize: pointSize.<br>
<br>
        base derivativeFont: bold at: 1.<br>
        base derivativeFont: oblique at: 2.<br>
        base derivativeFont: boldOblique at: 3.<br>
<br>
        ^base!<br>
<br>
Item was removed:<br>
- ----- Method: StrikeFont class>>defaultSized: (in category 'accessing') -----<br>
- defaultSized: aNumber<br>
-       | fonts f |<br>
-       "This used to be the default textstyle, but it needs to be a StrikeFont and not a TTCFont and sometimes the default textstyle is a TTCFont.  So, we use a typical StrikeFont as the default fallback font."<br>
-       fonts := (TextConstants at: #Accuny ifAbsent:[TextStyle default]) fontArray.<br>
-       f := fonts first.<br>
-       1 to: fonts size do: [:i |<br>
-               aNumber > (fonts at: i) height ifTrue: [f := fonts at: i].<br>
-       ].<br>
-       ^f<br>
- !<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont class>>fixDejaVuPointSIze: (in category 'font creation') -----<br>
+ fixDejaVuPointSIze: pointSize<br>
+       "Maps the given pointSize to 96 ppi."<br>
+       <br>
+       ^ (Dictionary newFrom: {<br>
+               7 -> 7.5.<br>
+               9 -> 10.5.<br>
+               12 -> 14.5.<br>
+               14 -> 16.5.<br>
+               17 -> 19.5.<br>
+               20 -> 23.5}) at: pointSize<br>
+       !<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont class>>generateDejaVuMethods: (in category 'font creation') -----<br>
  generateDejaVuMethods: directory<br>
        "StrikeFont generateDejaVuMethods: 'DejaVu'."<br>
        | dir formTemplate dataTemplate methodCategory |<br>
        methodCategory := #'dejaVu font data'.<br>
        formTemplate := '{1}<br>
        <generated><br>
        " Form data for {2}. Generated with StrikeFont generateDejaVuMethods: ''{3}''"<br>
        ^Form fromBinaryStream: (<br>
  ''{4}''<br>
        ) base64Decoded asByteArray readStream<br>
  '.<br>
        dataTemplate := '{1}<br>
        <generated><br>
        " Font meta data for {2}. Generated with StrikeFont generateDejaVuMethods: ''{3}''"<br>
        ^ #({4})<br>
  '.<br>
        dir := FileDirectory default / directory.<br>
        #('*.txt' 'Data' '*.png' 'Form') pairsDo: <br>
                [:match :suffix | (dir fileNamesMatching: match)<br>
                        do: <br>
                                [:local | | selector source stringContent|<br>
                                        " .txt and .png have both length 4"<br>
                                        selector := (local allButLast: 4) asLegalSelector, suffix.<br>
                                        stringContent := dir readOnlyFileNamed: local do:<br>
                                                [:stream | suffix = 'Data'<br>
                                                        ifTrue: [stream contentsOfEntireFile]<br>
                                                        ifFalse: [(stream binary; contentsOfEntireFile) base64Encoded]].<br>
                                        source := (suffix = 'Data' ifTrue: [dataTemplate] ifFalse: [formTemplate])<br>
                                                format: {selector . (local allButLast: 4) . directory . stringContent }.<br>
                                        self class compile: source classified: methodCategory]<br>
+                       displayingProgress: [:local | 'Generating {1}' translated format: {local}]].!<br>
-                       displayingProgress: [:local | 'Generating ', local]].!<br>
<br>
Item was removed:<br>
- ----- Method: StrikeFont class>>localeChanged (in category 'font creation') -----<br>
- localeChanged<br>
-       self setupDefaultFallbackFont!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont class>>passwordFont (in category 'instance creation') -----<br>
+ passwordFont<br>
+ <br>
+       ^ self passwordFontPointSize: TextStyle defaultFont pointSize!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont class>>passwordFontPointSize: (in category 'instance creation') -----<br>
+ passwordFontPointSize: pointSize<br>
+ <br>
+       ^ FixedFaceFont new passwordFont fontPointSize: pointSize!<br>
<br>
Item was changed:<br>
+ ----- Method: StrikeFont>>basicHasGlyphOf: (in category 'private') -----<br>
- ----- Method: StrikeFont>>basicHasGlyphOf: (in category 'multibyte character methods') -----<br>
  basicHasGlyphOf: aCharacter<br>
<br>
        ^ self hasGlyphForCode: (self codeForCharacter: aCharacter)<br>
  !<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont>>closeHtmlOn: (in category 'html') -----<br>
+ closeHtmlOn: aStream <br>
+ <br>
+       aStream nextPutAll: '</font>'.!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont>>fallbackFont (in category 'accessing') -----<br>
  fallbackFont<br>
+       "Overwritten to add a cache."<br>
+       <br>
+       ^ fallbackFont ifNil: [fallbackFont := super fallbackFont]!<br>
-       "Answers the fallbackFont for the receiver. The fallback font must be some derivative of the receiver since it will not be asked to install itself properly on the target BitBlt so rendering a completely different font here is simply not possible. The default implementation uses a synthetic font that maps all characters to question marks."<br>
-       ^ fallbackFont<br>
-               ifNil: [fallbackFont := FixedFaceFont new errorFont baseFont: self]!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont>>formOf: (in category 'private') -----<br>
+ formOf: aCharacter<br>
+       "Like #characterFormAt: but checks for #hasGlyphOf: and supports #fallbackFont."<br>
+ <br>
+       (self hasGlyphOf: aCharacter)<br>
+               ifFalse: [^ self fallbackFont formOf: aCharacter].<br>
+               <br>
+       ^ self characterFormAt: aCharacter!<br>
<br>
Item was changed:<br>
+ ----- Method: StrikeFont>>glyphOf: (in category 'private') -----<br>
- ----- Method: StrikeFont>>glyphOf: (in category 'accessing') -----<br>
  glyphOf: aCharacter <br>
-       "Answer the width of the argument as a character in the receiver."<br>
<br>
+       self flag: #deprecated.<br>
+       ^ self formOf: aCharacter!<br>
-       | code |<br>
-       (self hasGlyphOf: aCharacter) ifFalse: [<br>
-               fallbackFont ifNotNil: [<br>
-                       ^ fallbackFont glyphOf: aCharacter.<br>
-               ].<br>
-               ^ (Form extent: 1@self height) fillColor: Color white<br>
-       ].<br>
-       code := self codeForCharacter: aCharacter.<br>
-       ^ glyphs copy: (((xTable at: code + 1)@0) corner: (xTable at: code +2)@self height).<br>
- !<br>
<br>
Item was changed:<br>
+ ----- Method: StrikeFont>>hasGlyphForCode: (in category 'private') -----<br>
- ----- Method: StrikeFont>>hasGlyphForCode: (in category 'multibyte character methods') -----<br>
  hasGlyphForCode: aCharacterCode<br>
+       "Note that missing glyphs are encoded as -1 in the xTable but to speed up the #widthOf: check, the next offset must be adjacent and thus be duplicated. For example: #(-1 -1 0 24 -1 -1 -1 24 48 -1 ...). Since aCharacterCode is 0-based, that codes offset is at +1 while its width needs to consult +2, too. See #widthOf:." <br>
<br>
+       (aCharacterCode between: self minAscii and: self maxAscii)<br>
+               ifFalse: [^ false].<br>
+       (xTable at: aCharacterCode + 1) >= 0<br>
+               ifFalse: [^ false].<br>
+       (xTable at: aCharacterCode + 2) >= 0<br>
+               ifFalse: [^ false].<br>
+       ^ true!<br>
-       ((aCharacterCode between: self minAscii and: self maxAscii) not) ifTrue: [<br>
-               ^ false.<br>
-       ].<br>
-       (xTable at: aCharacterCode + 1) < 0 ifTrue: [<br>
-               ^ false.<br>
-       ].<br>
-       ^ true.<br>
- !<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont>>lineGap (in category 'accessing') -----<br>
+ lineGap<br>
+       "Historical. The #lineGap has been 2 pixels for all fonts in the system for a very long time. Since the #referenceHeight is 14 pixels, use that to compute a #lineGap relative to the receivers #pointSize/#pixelSize. Also see TTCFont >> #lineGap."<br>
+       <br>
+       ^ lineGap ifNil: [lineGap := ((self height asFloat / self class referenceHeight) * 2 "pixels") rounded]!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont>>lineGapSlice (in category 'accessing') -----<br>
+ lineGapSlice<br>
+       "Cached portion of the receiver's #lineGap, which can be used to center one-liners in text fields. Also see TTCFont >> #lineGapSlice."<br>
+       <br>
+       ^ lineGapSlice ifNil: [lineGapSlice := (self lineGap asFloat / 2) rounded]!<br>
<br>
Item was removed:<br>
- ----- Method: StrikeFont>>lineGrid (in category 'accessing') -----<br>
- lineGrid<br>
-       ^ ascent + descent!<br>
<br>
Item was removed:<br>
- ----- Method: StrikeFont>>maxAscii (in category 'accessing') -----<br>
- maxAscii<br>
-       "Answer the integer that is the last Ascii character value of the receiver."<br>
- <br>
-       ^maxAscii!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont>>maxCodePoint (in category 'accessing') -----<br>
+ maxCodePoint<br>
+       "Overwritten to configure ranges of glyphs per pre-rendered font."<br>
+ <br>
+       ^maxAscii!<br>
<br>
Item was removed:<br>
- ----- Method: StrikeFont>>minAscii (in category 'accessing') -----<br>
- minAscii<br>
-       "Answer the integer that is the first Ascii character value of the receiver."<br>
- <br>
-       ^minAscii!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont>>minCodePoint (in category 'accessing') -----<br>
+ minCodePoint<br>
+       "Overwritten to configure ranges of glyphs per pre-rendered font."<br>
+ <br>
+       ^minAscii!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont>>newFromStrike: (in category 'file in/out') -----<br>
  newFromStrike: fileName<br>
        "Build an instance from the strike font file name. The '.strike' extension<br>
        is optional."<br>
<br>
        | strike startName raster16 |<br>
        name := fileName copyUpTo: $..  "assumes extension (if any) is '.strike'"<br>
        strike := FileStream readOnlyFileNamed: name, '.strike.'.<br>
        strike binary.<br>
<br>
        "strip off direcory name if any"<br>
        startName := name size.<br>
        [startName > 0 and: [((name at: startName) ~= $>) & ((name at: startName) ~= $])]]<br>
                whileTrue: [startName := startName - 1].<br>
        name := name copyFrom: startName+1 to: name size.<br>
<br>
        type                    :=              strike nextWord.                "type is ignored now -- simplest<br>
                                                                                                assumed.  Kept here to make<br>
                                                                                                writing and consistency more<br>
                                                                                                straightforward."<br>
        minAscii                :=              strike nextWord.<br>
        maxAscii                :=              strike nextWord.<br>
        maxWidth                :=              strike nextWord.<br>
        strikeLength    :=              strike nextWord.<br>
        ascent                  :=              strike nextWord.<br>
        descent                 :=              strike nextWord.<br>
        "xOffset                        :="             strike nextWord.        <br>
        raster16                        :=              strike nextWord.        <br>
        superscript             :=              ascent - descent // 3.  <br>
        subscript               :=              descent - ascent // 3.  <br>
        emphasis                :=              0.<br>
        glyphs                  :=      Form extent: (raster16 * 16) @ (self height)  <br>
                                                        offset: 0@0.<br>
                glyphs bits fromByteStream: strike.<br>
<br>
        xTable := (Array new: maxAscii + 3) atAllPut: 0.<br>
        (minAscii + 1 to: maxAscii + 3) do:<br>
                [:index | xTable at: index put: strike nextWord].<br>
<br>
        "Set up space character"<br>
        ((xTable at: (Space asciiValue + 2))  = 0 or:<br>
                        [(xTable at: (Space asciiValue + 2)) = (xTable at: (Space asciiValue + 1))])<br>
                ifTrue: [(Space asciiValue + 2) to: xTable size do:<br>
+                                       [:index | xTable at: index put: ((xTable at: index) + 4 "DefaultSpace")]].<br>
-                                       [:index | xTable at: index put: ((xTable at: index) + DefaultSpace)]].<br>
        strike close.<br>
        characterToGlyphMap := nil.!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont>>openHtmlOn: (in category 'html') -----<br>
+ openHtmlOn: aStream <br>
+ <br>
+       aStream<br>
+               nextPutAll: '<font face="';<br>
+               nextPutAll: self familyName;<br>
+               nextPutAll: '" size="';<br>
+               nextPutAll: self pointSize asString;<br>
+               nextPutAll: '">'.!<br>
<br>
Item was added:<br>
+ ----- Method: StrikeFont>>pixelSize (in category 'accessing') -----<br>
+ pixelSize<br>
+       "Overwritten because the receiver is pre-rendered using a fixed pixels-per-inch (PPI) value, usually 96 PPI. Note that you can change the #pointSize to match 96 PPI (see TextStyle class >> #pixelsPerInch) as follows:<br>
+       <br>
+       self pointSize: ((72 * self pixelSize / 96) roundTo: 0.5).<br>
+       self derivativeFonts do: [:d | d pointSize: font pointSize].<br>
+       <br>
+       Note that a line gap (similar to a TTCFont's #lineGap) for pre-rendered fonts is managed via TextStyle's #lineGrid.<br>
+       <br>
+       Also see StrikeFont class >> #referenceHeight."<br>
+       <br>
+       ^ self height!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont>>postCopy (in category 'copying') -----<br>
  postCopy<br>
+       "The receiver is a just created shallow copy. This method gives it the final touch."<br>
+       <br>
+       glyphs := glyphs copy.<br>
+       xTable := xTable copy.<br>
+       characterToGlyphMap := characterToGlyphMap copy.<br>
+       derivativeFonts := derivativeFonts copy.<br>
+       <br>
+       self reset.  " takes care of the derivative fonts "!<br>
-  " the receiver is a just created shallow copy. This method gives it the final touch. " <br>
-  <br>
-     glyphs := glyphs copy.<br>
-     xTable := xTable copy.<br>
-     characterToGlyphMap := characterToGlyphMap copy.<br>
-  <br>
-     self reset.  " takes care of the derivative fonts "!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont>>printOn: (in category 'file in/out') -----<br>
  printOn: aStream<br>
+ <br>
        super printOn: aStream.<br>
+ <br>
+       aStream nextPut: $(.<br>
+       self printShortDescriptionOn: aStream.<br>
+       aStream nextPut: $).!<br>
-       aStream<br>
-               nextPut: $(;<br>
-               nextPutAll: self name;<br>
-               space;<br>
-               print: self height;<br>
-               nextPut: $)!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont>>printShortDescriptionOn: (in category 'printing') -----<br>
  printShortDescriptionOn: aStream<br>
+       <br>
+       aStream<br>
+               nextPutAll: self familyName;<br>
+               space; print: self pointSize; nextPutAll: 'pt';<br>
+               space; print: self pixelsPerInch; nextPutAll: 'ppi';<br>
+               space; print: self height; nextPutAll: 'px';<br>
+               space; nextPutAll: self emphasisString.!<br>
-       aStream space; nextPutAll: self name!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont>>readFromStrike2Stream: (in category 'file in/out') -----<br>
  readFromStrike2Stream: file <br>
        "Build an instance from the supplied binary stream on data in strike2 format"<br>
        type := file nextInt32.  type = 2 ifFalse: [file close. self error: 'not strike2 format'].<br>
        minAscii := file nextInt32.<br>
        maxAscii := file nextInt32.<br>
        maxWidth := file nextInt32.<br>
        ascent := file nextInt32.<br>
        descent := file nextInt32.<br>
        pointSize := file nextInt32.<br>
        superscript := ascent - descent // 3.   <br>
        subscript := descent - ascent // 3.     <br>
        emphasis := file nextInt32.<br>
        xTable := (Array new: maxAscii + 3) atAllPut: 0.<br>
        (minAscii + 1 to: maxAscii + 3) do:<br>
                [:index | xTable at: index put: file nextInt32].<br>
        glyphs := Form new readFrom: file.<br>
<br>
        "Set up space character"<br>
        ((xTable at: (Space asciiValue + 2))  = 0 or:<br>
                        [(xTable at: (Space asciiValue + 2)) = (xTable at: (Space asciiValue + 1))])<br>
                ifTrue: [(Space asciiValue + 2) to: xTable size do:<br>
+                                       [:index | xTable at: index put: ((xTable at: index) + 4 "DefaultSpace")]].<br>
-                                       [:index | xTable at: index put: ((xTable at: index) + DefaultSpace)]].<br>
        characterToGlyphMap := nil.!<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont>>reset (in category 'emphasis') -----<br>
  reset<br>
        "Reset the cache of derivative emphasized fonts"<br>
<br>
+       lineGap := lineGapSlice := nil.<br>
+ <br>
        fallbackFont class = FixedFaceFont<br>
                ifTrue: [fallbackFont := nil].<br>
<br>
        derivativeFonts notNil ifTrue: [<br>
                derivativeFonts withIndexDo: [ :f :i |<br>
                        (f notNil and: [f isSynthetic]) ifTrue: [derivativeFonts at: i put: nil]]].<br>
        "<br>
        derivativeFonts := Array new: 32.<br>
        #('B' 'I' 'BI') doWithIndex:<br>
                [:tag :index | <br>
                (style := TextStyle named: self familyName) ifNotNil:<br>
                        [(font := style fontArray<br>
                                detect: [:each | each name = (self name , tag)]<br>
                                ifNone: [nil]) ifNotNil: [derivativeFonts at: index put: font]]]<br>
        "!<br>
<br>
Item was removed:<br>
- ----- Method: StrikeFont>>setupDefaultFallbackFont (in category 'emphasis') -----<br>
- setupDefaultFallbackFont<br>
-       "This used to be the default textstyle, but it needs to be a StrikeFont and not a TTCFont and sometimes the default textstyle is a TTCFont.  So, we use a typical StrikeFont as the default fallback font."<br>
-       self fallbackFont: (StrikeFont defaultSized: self height).<br>
-       self reset.<br>
- <br>
- !<br>
<br>
Item was changed:<br>
  ----- Method: StrikeFont>>textStyle (in category 'accessing') -----<br>
  textStyle<br>
+       "Overwritten to not create a new style for orphaned fonts."<br>
+ <br>
+       ^ self textStyleOrNil!<br>
-       ^ TextStyle actualTextStyles detect:<br>
-               [:aStyle | aStyle fontArray includes: self] ifNone: [nil]!<br>
<br>
Item was changed:<br>
  ----- Method: TextComposer>>addNullLineWithIndex:andRectangle: (in category 'private') -----<br>
  addNullLineWithIndex: index andRectangle: r<br>
+       "TextEditor has emphasisHere, which encodes the emphasis of future input. We don't have that info here. Therefore we just use the height of the last text line if there is any."<br>
+       <br>
- <br>
        lines addLast: (<br>
                (<br>
                        TextLine <br>
                                start: index <br>
                                stop: index - 1<br>
                                internalSpaces: 0 <br>
                                paddingWidth: 0<br>
                )<br>
                        rectangle: r;<br>
+                       lineHeight: (lines<br>
+                               ifEmpty: [defaultLineHeight]<br>
+                               ifNotEmpty: [lines last lineHeight])<br>
+                       baseline: (lines<br>
+                               ifEmpty: [theTextStyle baseline]<br>
+                               ifNotEmpty: [lines last baseline])<br>
-                       lineHeight: defaultLineHeight baseline: theTextStyle baseline<br>
        )<br>
  !<br>
<br>
Item was changed:<br>
  ----- Method: TextComposer>>composeAllRectangles: (in category 'private') -----<br>
  composeAllRectangles: rectangles<br>
<br>
        | charIndexBeforeLine numberOfLinesBefore reasonForStopping |<br>
<br>
+       actualHeight := 0.<br>
-       actualHeight := defaultLineHeight.<br>
        charIndexBeforeLine := currCharIndex.<br>
        numberOfLinesBefore := lines size.<br>
        reasonForStopping := self composeEachRectangleIn: rectangles.<br>
<br>
        currentY := currentY + actualHeight.<br>
        currentY > theContainer bottom ifTrue: [<br>
                "Oops -- the line is really too high to fit -- back out"<br>
                currCharIndex := charIndexBeforeLine.<br>
                lines size - numberOfLinesBefore timesRepeat: [lines removeLast].<br>
                ^self<br>
        ].<br>
<br>
        "It's OK -- the line still fits."<br>
        maxRightX := maxRightX max: scanner rightX.<br>
        1 to: rectangles size - 1 do: [ :i | |lineIndex|<br>
                "Adjust heights across rectangles if necessary"<br>
                lineIndex:=lines size - rectangles size + i.<br>
                (lines size between: 1 and: lineIndex) ifTrue: <br>
                        [(lines at: lineIndex)<br>
                                lineHeight: lines last lineHeight<br>
                                baseline: lines last baseline] <br>
        ].<br>
        isFirstLine := false.<br>
        reasonForStopping == #columnBreak ifTrue: [^nil].<br>
        currCharIndex > theText size ifTrue: [<br>
                ^nil            "we are finished composing"<br>
        ].<br>
        !<br>
<br>
Item was changed:<br>
  ----- Method: TextComposer>>composeEachRectangleIn: (in category 'private') -----<br>
  composeEachRectangleIn: rectangles<br>
<br>
        | myLine lastChar |<br>
<br>
        1 to: rectangles size do: [:i | <br>
                currCharIndex <= theText size ifFalse: [^false].<br>
                myLine := scanner <br>
                        composeFrom: currCharIndex <br>
                        inRectangle: (rectangles at: i)                         <br>
                        firstLine: isFirstLine <br>
                        leftSide: i=1 <br>
                        rightSide: i=rectangles size.<br>
                lines addLast: myLine.<br>
+               myLine moveByTopMargin.<br>
+               actualHeight := actualHeight max: myLine lineHeightWithMargins.  "includes font changes and text style's #lineSpacing"<br>
-               actualHeight := actualHeight max: myLine lineHeight.  "includes font changes"<br>
                currCharIndex := myLine last + 1.<br>
                lastChar := theText at: myLine last.<br>
                (CharacterSet crlf includes: lastChar) ifTrue: [^#cr].<br>
                wantsColumnBreaks ifTrue: [<br>
                        lastChar = Character characterForColumnBreak ifTrue: [^#columnBreak].<br>
                ].<br>
        ].<br>
        ^false!<br>
<br>
Item was changed:<br>
  Object subclass: #TextLine<br>
+       instanceVariableNames: 'left right top bottom firstIndex lastIndex internalSpaces paddingWidth baseline leftMargin topMargin bottomMargin'<br>
-       instanceVariableNames: 'left right top bottom firstIndex lastIndex internalSpaces paddingWidth baseline leftMargin'<br>
        classVariableNames: ''<br>
        poolDictionaries: 'TextConstants'<br>
        category: 'Graphics-Text'!<br>
<br>
  !TextLine commentStamp: '<historical>' prior: 0!<br>
  A TextLine embodies the layout of a line of composed text.<br>
        left right top bottom           The full line rectangle<br>
        firstIndex lastIndex            Starting and stopping indices in the full text<br>
        internalSpaces          Number of spaces to share paddingWidth<br>
        paddingWidth            Number of pixels of extra space in full line<br>
        baseline                                Distance of baseline below the top of the line<br>
        leftMargin                      Left margin due to paragraph indentation<br>
  TextLine's rather verbose message protocol is required for compatibility with the old CharacterScanners.!<br>
<br>
Item was added:<br>
+ ----- Method: TextLine>>bottomMargin (in category 'accessing') -----<br>
+ bottomMargin<br>
+ <br>
+       ^ bottomMargin!<br>
<br>
Item was added:<br>
+ ----- Method: TextLine>>lineHeightWithMargins (in category 'accessing') -----<br>
+ lineHeightWithMargins<br>
+ <br>
+       ^ bottom - top + topMargin + bottomMargin!<br>
<br>
Item was added:<br>
+ ----- Method: TextLine>>moveByTopMargin (in category 'updating') -----<br>
+ moveByTopMargin<br>
+ <br>
+       top := top + topMargin.<br>
+       bottom := bottom + topMargin.<br>
+ !<br>
<br>
Item was added:<br>
+ ----- Method: TextLine>>topMargin (in category 'accessing') -----<br>
+ topMargin<br>
+ <br>
+       ^ topMargin!<br>
<br>
Item was added:<br>
+ ----- Method: TextLine>>topMargin:bottomMargin: (in category 'private') -----<br>
+ topMargin: tm bottomMargin: bm<br>
+       topMargin := tm.<br>
+       bottomMargin := bm.!<br>
<br>
Item was changed:<br>
  Object subclass: #TextStyle<br>
+       instanceVariableNames: 'fontArray alignment firstIndent restIndent rightIndent tabsArray marginTabsArray defaultFontIndex lineSpacing'<br>
+       classVariableNames: 'NumSpacesPerTab'<br>
-       instanceVariableNames: 'fontArray fontFamilySize lineGrid baseline alignment firstIndent restIndent rightIndent tabsArray marginTabsArray leading defaultFontIndex'<br>
-       classVariableNames: ''<br>
        poolDictionaries: 'TextConstants'<br>
        category: 'Graphics-Text'!<br>
<br>
+ !TextStyle commentStamp: 'mt 2/21/2022 10:55' prior: 0!<br>
+ A text style comprises the formatting information for composing and displaying a unit (usually a paragraph) of text. It provides a #defaultFont to use, but text attributes can change that per character (see CompositionScanner and DisplayScanner). It also has a default #alignment that attributes can override. Those defaults make it possible to compose and display (unformatted) strings in paragraphs without having to style them first as texts (i.e., string+attributes).<br>
- !TextStyle commentStamp: '<historical>' prior: 0!<br>
- A textStyle comprises the formatting information for composing and displaying a unit (usually a paragraph) of text.  Typically one makes a copy of a master textStyle (such as TextStyle default), and then that copy may get altered in the process of editing.  Bad things can happen if you do not copy first.<br>
<br>
+ NOTE THAT for each use you *must* make a copy of a font's master text style (e.g., "TextStyle default copy") or create a fresh one with at least a single font (see TextStyle class >> #fontArray: and AbstractFont >> #asNewTextStyle). That specific instance is typically altered in the process of editing: change default font size, change default alignment, ... and you wouldn't want to change that properties for other applications by accident.<br>
- Each of my instances consists of...<br>
-       fontArray               An array of StrikeFonts<br>
-       fontFamilySize  unused<br>
-       lineGrid                        An integer; default line spacing for paragraphs<br>
-       baseline                        An integer; default baseline (dist from line top to bottom of an 'a')<br>
-       alignment               An integer; text alignment, see TextStyle alignment:<br>
-       firstIndent             An integer; indent of first line in pixels<br>
-       restIndent              An integer; indent of remaining lines in pixels<br>
-       rightIndent             An integer; indent of right margin rel to section<br>
-       tabsArray               An array of integers giving tab offsets in pixels<br>
-       marginTabsArray An array of margin tabs<br>
-       leading                 An integer giving default vertical line separation<br>
<br>
+ A text style also drives the interpretation of Character tab. Both tabsArray and marginTabsArray are initialized for the #defaultFont(Index:). When you change a style's default font size, those "tab positions" will be recomputed for fast access during composition. See the preference #numSpacesPerTab(:).<br>
+ <br>
+ While each text style looks like it could handle an arbitrary array of fonts, it is *best practice* to only store fonts of the same font family. A font's master style thus collects all known point sizes at a single place (i.e. "TextStyle named: aFamilyName"). Copies will share that array. The attribute TextFontChange makes it possible to switch to any index in that array, but this is not portable and hence discouraged. TextFontReference adds an explicit reference to font, which is also not good. (February 2022: We plan to add TextFont(Point)Size and TextFontFamily as a portable way to change the font per character.).<br>
+ <br>
+ There are some legacy information, which should no longer be used:<br>
+       - #baseline: ... used to prescribe baseline info but is now completely derived from #defaultFont<br>
+       - #lineGrid: ... same as #baseLine:<br>
+       - #leading(:) ... is replaced by #lineSpacing(:) and denotes the extra spacing relative to the respective line's height in the composition<br>
+ <br>
+ The #lineSpacing is noticeable in a paragraph's text selection. Line spacing < 0.0 will appear as overlaps between (translucent) selection rectangles. Lince spacing > 0.0 will appear as gaps between selection rectangles.<br>
+ <br>
+ Here are some example styles to explore:<br>
+       - TextStyle default<br>
+       - TextStyle defaultFixes!<br>
- For a concrete example, look at TextStyle default copy inspect!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle class>>actualTextStyles (in category 'TextConstants access') -----<br>
  actualTextStyles<br>
        | aDict |<br>
        "TextStyle actualTextStyles"<br>
<br>
        "Answer dictionary whose keys are the names of styles in the system and whose values are the actual styles"<br>
<br>
        aDict := TextConstants select: [:thang | thang isKindOf: self ].<br>
+       self defaultFamilyNames do: [ :sym | aDict removeKey: sym ifAbsent: [] ].<br>
-       self defaultFamilyNames do: [ :sym | aDict removeKey: sym ].<br>
        ^ aDict!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle class>>defaultFamilyNames (in category 'TextConstants access') -----<br>
  defaultFamilyNames<br>
+       ^#(DefaultTextStyle DefaultFixedTextStyle DefaultMultiStyle DefaultFallbackTextStyle)!<br>
-       ^#(DefaultTextStyle DefaultFixedTextStyle DefaultMultiStyle)!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle class>>defaultTT (in category 'constants') -----<br>
+ defaultTT<br>
+       "Answer the default TrueType text style."<br>
+ <br>
+       ^ self default isTTCStyle<br>
+               ifTrue: [self default]<br>
+               ifFalse: [self named: #BitstreamVeraSans]!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle class>>defaultTTFont (in category 'constants') -----<br>
+ defaultTTFont<br>
+ <br>
+       ^ self defaultTT defaultFont!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle class>>fontSizeSummaryContents (in category 'utilities') -----<br>
  fontSizeSummaryContents<br>
<br>
+       ^ Text streamContents: [:aStream |<br>
+                       | knownStyles knownTTCStyles knownLegacyStyles defaultStyles printBlock |<br>
+                       knownStyles := self knownTextStylesWithoutDefault sorted.<br>
+                       defaultStyles := self defaultFamilyNames sorted.<br>
+                       <br>
+                       aStream nextPutAll: ('This page lists all known text styles and for each style''s font the available point sizes. Most text fields offer the {1} where you can choose a different font or point size. Note that you can use any new point size for TrueType fonts. This is, however, not possible for our pre-rendred legacy fonts. If you need more fonts, use the {2} to import TrueType fonts from your current platform. Click {3} to browse all styles by example.\\'<br>
+                               translated withCRs asText format: {<br>
+                                       'FontChooserTool' asText<br>
+                                               addAttribute: (PluggableTextAttribute evalBlock: [FontChooserTool open]); yourself.<br>
+                                       'FontImporterTool' asText<br>
+                                               addAttribute: (PluggableTextAttribute evalBlock: [FontImporterTool open]); yourself.<br>
+                                       'here' asText<br>
+                                               addAttribute: (PluggableTextAttribute evalBlock: [TextStyle browseAllStyles])}).<br>
+                       <br>
+                       defaultStyles do: [:styleName |<br>
+                               | style prefix |<br>
+                               style := self named: styleName.<br>
+                               prefix := (style isNil or: [(self named: style defaultFamilyName) == style]) ifTrue: [''] ifFalse: [' !! '].<br>
+                               aStream<br>
+                                       nextPutAll: (((styleName padded: #left to: 24 with: Character space), ': ', prefix) asText addAttribute: (TextFontReference toFont: TextStyle defaultFixedFont); yourself);<br>
+                                       nextPutAll: (style ifNil: ['-'] ifNotNil: [(style defaultFamilyName asText addAttribute: (TextFontReference toFont: style defaultFont); addAttribute: (PluggableTextAttribute evalBlock: [style explore]); yourself)]);<br>
+                                       cr].<br>
+                       <br>
+                       printBlock :=  [:styleName |<br>
+                                       | style defaultFont preferredPointSize exampleFont |<br>
+                                       style := self named: styleName.<br>
+                                       preferredPointSize := TextStyle defaultFont pointSize. "system's current default"<br>
+                                       defaultFont := style defaultFont. "style's current default"<br>
+                                       exampleFont := defaultFont isSymbolFont<br>
+                                               ifTrue: [TextStyle defaultFont "famiyl name should be legible"]<br>
+                                               ifFalse: [defaultFont asPointSize: preferredPointSize].<br>
+                                       aStream<br>
+                                               nextPutAll: (styleName asText addAttribute: (TextFontReference toFont: exampleFont)).<br>
+                                       styleName ~= style defaultFamilyName ifTrue: ["style alias"<br>
+                                               aStream nextPutAll: ' (', style defaultFamilyName, ')'].<br>
+                                       aStream<br>
+                                               nextPutAll: ((Text new,<br>
+                                                       ((style isTTCStyle ifFalse: [''] ifTrue: [' TrueType', (defaultFont isRemoteFont ifFalse: [''] ifTrue: [' (remote)']), (defaultFont isSymbolFont ifFalse: [''] ifTrue: [' (symbols)'])])<br>
+                                                               asText addAttribute: (TextColor color: ((self userInterfaceTheme get: #balloonTextColor for: #PluggableTextMorphPlus) ifNil: [Color gray])); yourself),<br>
+                                                       (style isTTCStyle ifFalse: [''] ifTrue: [ | eg |<br>
+                                                               '  ... ' asText,<br>
+                                                               ((' ', (defaultFont extraGlyphScale * 100) rounded asString, '%') asText addAttribute: (PluggableTextAttribute evalBlock: [style chooseExtraScale]); yourself),<br>
+                                                               (('  ', ((eg := defaultFont extraLineGap) >= 0 ifTrue: ['+', eg asString] ifFalse: [eg asString])) asText addAttribute: (PluggableTextAttribute evalBlock: [style chooseExtraGap]); yourself) ]),<br>
+                                                       '  ...  ',<br>
+                                                       ('explore' translated asText addAttribute: (PluggableTextAttribute evalBlock: [style explore]); yourself),<br>
+                                                       '  ',<br>
+                                                       ('browse' translated asText addAttribute: (PluggableTextAttribute evalBlock: [defaultFont browseAllGlyphs; browseAllGlyphsByCategory]); yourself),<br>
+                                                       <br>
+                                                       '  ') addAttribute: (TextFontReference toFont: Preferences standardButtonFont); yourself);<br>
+                                               cr.<br>
+                                       aStream nextPutAll:      (((self fontPointSizesFor: styleName) inject: '    ' asText into: [:text :pointSize |<br>
+                                                       pointSize = defaultFont pointSize<br>
+                                                               ifFalse: [text, ((pointSize asFloat printShowingDecimalPlaces: 1) padded: #left to: 5 with: Character space)]<br>
+                                                               ifTrue: [text, (((pointSize asFloat printShowingDecimalPlaces: 1) padded: #left to: 5 with: Character space) asText addAttribute: TextEmphasis bold; yourself)]]) addAttribute: (TextFontReference toFont: TextStyle defaultFixedFont); yourself).<br>
+                                       aStream cr; cr].<br>
+                       <br>
+                       knownTTCStyles := knownStyles select: [:ea | (self named: ea) isTTCStyle].<br>
+                       knownLegacyStyles := knownStyles reject: [:ea | (self named: ea) isTTCStyle].<br>
+                       <br>
+                       aStream cr.<br>
+                       knownTTCStyles do: printBlock.<br>
+                       aStream nextPutAll: ('The following pre-rendered legacy fonts are still available. Note that you can only choose from the point sizes that are listed here. Each point size has a pixel size for 96 PPI. The system scales currently for {1} PPI.' translated format: {TextStyle pixelsPerInch}) ; cr; cr.<br>
+                       knownLegacyStyles do: printBlock.<br>
+                               ].!<br>
-       ^ Text streamContents:<br>
-               [:aStream |<br>
-                       self knownTextStyles do: [:aStyleName |<br>
-                               aStream nextPutAll:<br>
-                                       (aStyleName  asText addAttribute: (TextFontReference toFont: (TextStyle named: aStyleName) defaultFont)), '  ',<br>
-                                       (self fontPointSizesFor: aStyleName) asArray storeString.<br>
-                               aStream cr]].!<br>
<br>
Item was removed:<br>
- ----- Method: TextStyle class>>new (in category 'instance creation') -----<br>
- new<br>
-       ^ super new leading: 2!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle class>>numSpacesPerTab (in category 'preferences') -----<br>
+ numSpacesPerTab<br>
+       <preference: 'Tab width (i.e., number of spaces)'<br>
+               categoryList: #(tools visuals Accessibility)<br>
+               description: 'Amount of spaces to be used when calculating the width of a tab character for a specific font face and point size.'<br>
+               type: #Number><br>
+       ^ NumSpacesPerTab ifNil: [6]!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle class>>numSpacesPerTab: (in category 'preferences') -----<br>
+ numSpacesPerTab: anInteger<br>
+ <br>
+       anInteger = NumSpacesPerTab ifTrue: [^ self].<br>
+       NumSpacesPerTab := anInteger ifNotNil: [anInteger truncated max: 1].<br>
+       TextStyle allInstancesDo: [:ea | ea initializeTabsArray].<br>
+ <br>
+       "Avoid dependency to Morphic project..."<br>
+       (self environment classNamed: #TextMorph)<br>
+               ifNotNil: [:tmClass | tmClass allSubInstancesDo: [:tm |<br>
+                       tm releaseParagraph; changed]].!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle class>>pixelsPerInch: (in category 'utilities') -----<br>
  pixelsPerInch: aNumber<br>
        "Set the nominal number of pixels per inch to aNumber."<br>
+ <br>
+       self pixelsPerInch = aNumber ifTrue: [^ self].<br>
+       TextConstants at: #pixelsPerInch put: aNumber.<br>
-       TextConstants at: #pixelsPerInch put: aNumber asFloat.<br>
        AbstractFont allSubInstancesDo: [ :font | font pixelsPerInchChanged ].<br>
+       TextStyle allInstancesDo: [ :style | style pixelsPerInchChanged ].!<br>
-       Display restore.!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle class>>referenceHeight (in category 'utilities') -----<br>
+ referenceHeight<br>
+       "See commentary in RealEstateAgent class >> #scaleFactor."<br>
+       <br>
+       ^ self default isTTCStyle<br>
+               ifTrue: [self pointsToPixels: TTCFont referencePointSize]<br>
+               ifFalse: [self defaultFont height]!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>baseline (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>baseline (in category 'accessing') -----<br>
  baseline<br>
        "Answer the distance from the top of the line to the bottom of most of the <br>
        characters (by convention, bottom of the letter 'A')."<br>
<br>
+       ^ self defaultFont ascent!<br>
-       ^baseline!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>baseline: (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>baseline: (in category 'accessing') -----<br>
  baseline: anInteger <br>
-       "Set the distance from the top of the line to the bottom of most of the <br>
-       characters."<br>
<br>
+       self flag: #deprecated. "Either change #defaultFont in this style or use custom fonts via text attributes."!<br>
-       baseline := anInteger!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle>>consistOnlyOf: (in category 'fonts and font indexes') -----<br>
  consistOnlyOf: aFont<br>
+ <br>
+       self deprecated.<br>
+       ^ self newFontArray: {aFont}!<br>
-       fontArray := Array with: aFont.<br>
-       defaultFontIndex := 1!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle>>defaultFamilyName (in category 'accessing - default font') -----<br>
+ defaultFamilyName<br>
+       ^ self defaultFont familyName!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>defaultFont (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>defaultFont (in category 'accessing') -----<br>
  defaultFont<br>
        ^ fontArray at: self defaultFontIndex!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>defaultFontIndex (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>defaultFontIndex (in category 'default font') -----<br>
  defaultFontIndex<br>
        ^ defaultFontIndex ifNil: [defaultFontIndex := 1]!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>defaultFontIndex: (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>defaultFontIndex: (in category 'default font') -----<br>
  defaultFontIndex: anIndex<br>
+ <br>
+       defaultFontIndex := anIndex.<br>
+       self initializeTabsArray.!<br>
-       defaultFontIndex := anIndex!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle>>gridForFont:withLead: (in category 'private') -----<br>
  gridForFont: fontIndex withLead: leadInteger <br>
        "Force whole style to suit one of its fonts. Assumes only one font referred<br>
        to by runs."<br>
+ <br>
+       self flag: #deprecated.<br>
+       self defaultFontIndex: fontIndex.!<br>
-       | font |<br>
-       font := self fontAt: fontIndex.<br>
-       self lineGrid: font height + leadInteger.<br>
-       self baseline: font ascent.<br>
-       self leading: leadInteger!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle>>initializeTabsArray (in category 'initialize-release') -----<br>
+ initializeTabsArray<br>
+ <br>
+       | fontToUse numSpacesPerTab tabWidth maxWidth |<br>
+       self flag: #discuss. "mt: Add cache per font and pointSize? Maybe it is not worth it..."<br>
+       <br>
+       numSpacesPerTab := self class numSpacesPerTab.<br>
+       fontToUse := self defaultFont.<br>
+       maxWidth := Display width max: 3840.    <br>
+       tabWidth := ((fontToUse widthOf: Character space) max: 1 "For tiny point sizes...") * numSpacesPerTab.<br>
+       <br>
+       "Note that using Interval via #to:by: and #asArray would be about 4x slower."<br>
+       tabsArray := Array new: maxWidth // tabWidth.<br>
+       1 to: tabsArray size do: [:i | tabsArray at: i put: tabWidth * i].<br>
+       <br>
+       marginTabsArray := Array new: (maxWidth // tabWidth) // 2.<br>
+       1 to: marginTabsArray size do: [:i | | offset |<br>
+               marginTabsArray at: i put: (Array with: (offset := tabWidth * i) with: offset)].!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>leading (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>leading (in category 'accessing') -----<br>
  leading<br>
        "Leading (from typographers historical use of extra lead (type metal))<br>
        is the extra spacing above and beyond that needed just to accomodate<br>
        the various font heights in the set."<br>
+ <br>
+       self flag: #deprecated. "mt: Fonts provide their #lineGap (and #lineGapSlice) in the CompositionScanner to accommodate various font heights in a set. Use #lineSpacing to define a factor that moves the lines further apart."<br>
+       ^ 0!<br>
-       ^ leading!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>leading: (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>leading: (in category 'accessing') -----<br>
  leading: yDelta<br>
<br>
+       self flag: #deprecated. "See commentary in #leading."!<br>
-       leading := yDelta!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>lineGrid (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>lineGrid (in category 'accessing') -----<br>
  lineGrid<br>
        "Answer the relative space between lines of a paragraph in the style of <br>
        the receiver."<br>
<br>
+       ^ self defaultFont lineGrid!<br>
-       ^lineGrid!<br>
<br>
Item was changed:<br>
+ ----- Method: TextStyle>>lineGrid: (in category 'accessing - default font') -----<br>
- ----- Method: TextStyle>>lineGrid: (in category 'accessing') -----<br>
  lineGrid: anInteger <br>
-       "Set the relative space between lines of a paragraph in the style of the <br>
-       receiver to be the argument, anInteger."<br>
<br>
+       self flag: #deprecated. "Either change #defaultFont in this style or use custom fonts via text attributes."!<br>
-       lineGrid := anInteger!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle>>lineSpacing (in category 'accessing') -----<br>
+ lineSpacing<br>
+       "Answer the factor that is used to compute extra spacing between text lines. The default is 0.0, which means that the CompositionScanner will just rely on the various font metrics in a line. There will be 0% of extra spacing. Use the current line height as blank space with a factor of 1.0 and so on (i.e. the common misnomer 'double line space')."<br>
+       <br>
+       ^ lineSpacing ifNil: [0.0]!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle>>lineSpacing: (in category 'accessing') -----<br>
+ lineSpacing: aFactor<br>
+ <br>
+       lineSpacing := aFactor.!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle>>newFontArray: (in category 'private') -----<br>
  newFontArray: anArray<br>
        "Currently there is no supporting protocol for changing these arrays. If an editor wishes to implement margin setting, then a copy of the default should be stored with these instance variables.  <br>
        , Make size depend on first font."<br>
<br>
        fontArray := anArray.<br>
+       self defaultFontIndex: 1.<br>
-       lineGrid := (fontArray at: 1) height + leading. "For whole family"<br>
-       baseline := (fontArray at: 1) ascent + leading.<br>
        alignment := 0.<br>
        firstIndent := 0.<br>
        restIndent := 0.<br>
        rightIndent := 0.<br>
-       tabsArray := DefaultTabsArray.<br>
-       marginTabsArray := DefaultMarginTabsArray<br>
  "<br>
  TextStyle allInstancesDo: [:ts | ts newFontArray: TextStyle default fontArray].<br>
  "!<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle>>pixelsPerInchChanged (in category 'notifications') -----<br>
+ pixelsPerInchChanged<br>
+ <br>
+       self reset.!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle>>printOn: (in category 'printing') -----<br>
  printOn: aStream<br>
<br>
        super printOn: aStream.<br>
+       aStream nextPut: $(.<br>
+       aStream print: fontArray size.<br>
+       aStream nextPut: $).<br>
+       aStream space.<br>
+       self defaultFont printShortDescriptionOn: aStream.<br>
-       self defaultFont printShortDescriptionOn: aStream<br>
  !<br>
<br>
Item was added:<br>
+ ----- Method: TextStyle>>reset (in category 'initialize-release') -----<br>
+ reset<br>
+       "Reset values cached from the receiver's default font."<br>
+       <br>
+       self defaultFontIndex: self defaultFontIndex.!<br>
<br>
Item was changed:<br>
  ----- Method: TextStyle>>tabWidth (in category 'tabs and margins') -----<br>
  tabWidth<br>
        "Answer the width of a tab."<br>
<br>
+       ^ tabsArray at: 1 ifAbsent: [24]!<br>
-       ^DefaultTab!<br>
<br>
Item was changed:<br>
+ ----- Method: TranslucentColor>>isOpaque (in category 'testing') -----<br>
- ----- Method: TranslucentColor>>isOpaque (in category 'queries') -----<br>
  isOpaque<br>
        ^alpha = 255!<br>
<br>
Item was changed:<br>
+ ----- Method: TranslucentColor>>isTranslucent (in category 'testing') -----<br>
- ----- Method: TranslucentColor>>isTranslucent (in category 'queries') -----<br>
  isTranslucent<br>
        ^ alpha < 255!<br>
<br>
Item was changed:<br>
+ ----- Method: TranslucentColor>>isTranslucentColor (in category 'testing') -----<br>
- ----- Method: TranslucentColor>>isTranslucentColor (in category 'queries') -----<br>
  isTranslucentColor<br>
        "This means: self isTranslucent, but isTransparent not"<br>
        ^ alpha > 0!<br>
<br>
Item was changed:<br>
+ ----- Method: TranslucentColor>>isTransparent (in category 'testing') -----<br>
- ----- Method: TranslucentColor>>isTransparent (in category 'queries') -----<br>
  isTransparent<br>
        ^ alpha = 0!<br>
<br>
Item was added:<br>
+ ----- Method: TranslucentColor>>printOn: (in category 'printing') -----<br>
+ printOn: aStream<br>
+       | name |<br>
+       self isTransparent ifTrue: [^ aStream nextPutAll: 'Color transparent'].<br>
+       (name := self asNontranslucentColor name) ifNil: [^self storeOn: aStream].<br>
+       aStream<br>
+               nextPutAll: '(Color ';<br>
+               nextPutAll: name;<br>
+               nextPutAll: ' alpha: ';<br>
+               print: self alpha maxDecimalPlaces: 3;<br>
+               nextPut: $)<br>
+       !<br>
<br>
Item was changed:<br>
  ----- Method: TranslucentColor>>storeOn: (in category 'printing') -----<br>
  storeOn: aStream<br>
<br>
+       self isTransparent ifTrue: [^ aStream nextPutAll: 'Color transparent'].<br>
-       self isTransparent ifTrue: [^ aStream nextPutAll: '(Color transparent)'].<br>
        super storeOn: aStream.<br>
        aStream<br>
                skip: -1;         "get rid of trailing )"<br>
                nextPutAll: ' alpha: ';<br>
                print: self alpha maxDecimalPlaces: 3;<br>
                nextPutAll: ')'.<br>
  !<br>
<br>
Item was changed:<br>
+ (PackageInfo named: 'Graphics') postscript: '"Compute the correct tab widths per style using its current default font."<br>
+ TextStyle allInstancesDo: [:ea | ea initializeTabsArray].'!<br>
- (PackageInfo named: 'Graphics') postscript: 'Smalltalk removeFromStartUpList: DisplayScreen. "see Project class >> #startUp"<br>
- Smalltalk removeFromShutDownList: DisplayScreen. "see Project class >> #shutDown"'!<br>
<br>
<br>
<br>
</blockquote></div>
</div></blockquote>
                                        </div><span></span><br></div></blockquote></div></div></blockquote>
                                        </div></body>