Re: Analysis of GSHorizontalTypesetter
"[email protected]" via "Developers list for GNUstep, the GNU groupware evironment" <[email protected]> Sun, 15 Mar 2026 23:15:11 +0100
| Newsgroups | gmane.comp.lib.gnustep.devel |
|---|---|
| Message-ID | <[email protected]> |
--Apple-Mail=_E574689C-527D-4D09-9378-02F1A9847500 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 For better readability please have a look at: https://github.com/gnustep/libs-gui/issues/382#issuecomment-4064012492 Kind regards, Lars > Am 15.03.2026 um 03:23 schrieb [email protected] via = Developers list for GNUstep, the GNU groupware evironment = <[email protected]>: >=20 > Hi GNUstep-Devs, >=20 >=20 > especially Riccardo, Fred and Greg since we were talking about this in = the call today. >=20 >=20 > I had the idea to let a Coding AI, in this case https://www.kimi.com/ = analyze our GSHorizontalTypesetter code and =E2=80=9Eexplain=E2=80=9C it = inside comments added to the code. >=20 > So I asked it the following question: >=20 >=20 > =E2=80=9EHello KIMI, please analyze the following code and explain it = in comments you add or correct:=E2=80=9C >=20 >=20 > and then gave it both the header and the implementation. >=20 >=20 > Here is the answer (The answer seems to be in Markup-Format so maybe = you cut and paste it into some markup editor for better readability): >=20 > =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80= =94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80= =94=20 >=20 > I'll analyze this GNUstep GUI Library code and add comprehensive = comments explaining the functionality. Let me go through both the header = and implementation files. >=20 > ```objc > /* > GSHorizontalTypesetter.h >=20 > Copyright (C) 2002 Free Software Foundation, Inc. >=20 > Author: Alexander Malmberg <[email protected]> > Date: 2002 >=20 > This file is part of the GNUstep GUI Library. >=20 > This library is free software; you can redistribute it and/or > modify it under the terms of the GNU Lesser General Public > License as published by the Free Software Foundation; either > version 2 of the License, or (at your option) any later version. >=20 > This library is distributed in the hope that it will be useful, > but WITHOUT ANY WARRANTY; without even the implied warranty of > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the = GNU > Lesser General Public License for more details. >=20 > You should have received a copy of the GNU Lesser General Public > License along with this library; see the file COPYING.LIB. > If not, see <http://www.gnu.org/licenses/ > or write to the=20 > Free Software Foundation, 51 Franklin Street, Fifth Floor,=20 > Boston, MA 02110-1301, USA. > */ >=20 > #ifndef _GNUstep_H_GSHorizontalTypesetter > #define _GNUstep_H_GSHorizontalTypesetter >=20 > #import <GNUstepGUI/GSTypesetter.h> >=20 > @class NSLock; > @class GSLayoutManager, NSTextContainer, NSTextStorage; > @class NSDictionary; > @class NSParagraphStyle, NSFont; >=20 > /* > * APPKIT_EXPORT_CLASS marks this class for export from the AppKit = framework. > * This is a GNUstep-specific macro for DLL/shared library symbol = visibility. > */ > APPKIT_EXPORT_CLASS >=20 > /* > * GSHorizontalTypesetter is the concrete typesetter implementation = for > * horizontal text layout in GNUstep. It handles the complex task of = laying out > * glyphs into lines within text containers, supporting various = paragraph styles, > * alignments, line breaking modes, and text attributes. > * > * This class implements the core text layout algorithm that converts = a stream > * of glyphs from the layout manager into positioned line fragments = within > * the geometric constraints of text containers. > */ > @interface GSHorizontalTypesetter : GSTypesetter > { > /* > * REENTRANCY LOCK > * Since this is typically a shared singleton instance, the lock = ensures > * thread safety. If the typesetter is already in use when another = layout > * request comes in, a temporary instance is created instead. > */ > NSLock *lock; >=20 > /* > * CURRENT LAYOUT CONTEXT > * These ivars track the active text system objects being processed. > * They are set at the beginning of each layout operation and remain > * constant throughout that operation. > */ > GSLayoutManager *curLayoutManager; // Converts characters to = glyphs, tracks runs > NSTextContainer *curTextContainer; // Defines geometric bounds = for text > NSTextStorage *curTextStorage; // The attributed string being = laid out >=20 > unsigned int curGlyphIndex; // Current position in the = glyph stream > NSPoint curPoint; // Current layout position (y = advances per line) >=20 > /* > * ATTRIBUTE CACHING > * These ivars cache the current paragraph style and attributes to = avoid > * repeated dictionary lookups. The ranges track the validity of = each cache. > */ > NSParagraphStyle *curParagraphStyle; // Current paragraph's = formatting rules > NSRange paragraphRange; // Character range where = curParagraphStyle is valid >=20 > NSDictionary *curAttributes; // Current character = attributes dictionary > NSRange attributeRange; // Character range where = curAttributes is valid > =20 > /* > * DECOMPOSED ATTRIBUTES > * Frequently accessed attributes are extracted from the dictionary = and > * stored in this struct for faster access during the tight layout = loop. > */ > struct > { > BOOL explicit_kern; // YES if NSKernAttributeName = is present > float kern; // Kerning adjustment value > float baseline_offset; // Vertical offset from = baseline > int superscript; // Superscript level (+1, -1, = etc.) > } attributes; >=20 > NSFont *curFont; // Current font for glyph = metrics > NSRange fontRange; // Glyph range where curFont = is valid >=20 > /* > * GLYPH CACHE > * A resizable array of glyph_cache_t structures that stores = pre-computed > * information about glyphs to avoid repeated calculations. This is = the > * primary optimization for the layout engine. > * > * cache_base: The glyph index in the layout manager that = corresponds > * to cache[0]. All cache indices are relative to = this. > * cache_size: Allocated capacity of the cache array (in elements) > * cache_length: Number of valid entries currently in the cache > * at_end: YES when we've cached all glyphs in the text = storage > */ > struct GSHorizontalTypesetter_glyph_cache_s *cache; > unsigned int cache_base, cache_size, cache_length; > BOOL at_end; >=20 > /* > * LINE FRAGMENT ARRAY > * During layout of a single line, this array stores the line = fragment > * rectangles returned by the text container. A line may consist of > * multiple fragments when flowing around exclusions or shapes. > * > * line_frags: Array of line_frag_t structures > * line_frags_num: Number of fragments in current line > * line_frags_size: Allocated capacity of the array > */ > struct GSHorizontalTypesetter_line_frag_s *line_frags; > int line_frags_num, line_frags_size; > } >=20 > /* > * Returns the shared thread-local instance of the typesetter. > * Each thread maintains its own instance in the thread dictionary > * to avoid cross-thread contention while still allowing reuse. > */ > +(GSHorizontalTypesetter *) sharedInstance; >=20 > @end >=20 > #endif > ``` >=20 > Now for the implementation file with detailed comments: >=20 > ```objc > /* > GSHorizontalTypesetter.m >=20 > Copyright (C) 2002, 2003 Free Software Foundation, Inc. >=20 > Author: Alexander Malmberg <[email protected]> > Date: November 2002 - February 2003 >=20 > This file is part of the GNUstep GUI Library. >=20 > This library is free software; you can redistribute it and/or > modify it under the terms of the GNU Lesser General Public > License as published by the Free Software Foundation; either > version 2 of the License, or (at your option) any later version. >=20 > This library is distributed in the hope that it will be useful, > but WITHOUT ANY WARRANTY; without even the implied warranty of > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the = GNU > Lesser General Public License for more details. >=20 > You should have received a copy of the GNU Lesser General Public > License along with this library; see the file COPYING.LIB. > If not, see <http://www.gnu.org/licenses/> or write to the=20 > Free Software Foundation, 51 Franklin Street, Fifth Floor,=20 > Boston, MA 02110-1301, USA. > */ >=20 >=20 > #include <math.h> >=20 > #import <Foundation/NSDebug.h> > #import <Foundation/NSException.h> > #import <Foundation/NSGeometry.h> > #import <Foundation/NSLock.h> > #import <Foundation/NSThread.h> > #import <Foundation/NSValue.h> >=20 > #import "AppKit/NSAttributedString.h" > #import "AppKit/NSParagraphStyle.h" > #import "AppKit/NSTextAttachment.h" > #import "AppKit/NSTextContainer.h" > #import "AppKit/NSTextStorage.h" > #import "GNUstepGUI/GSLayoutManager.h" > #import "GNUstepGUI/GSHorizontalTypesetter.h" >=20 >=20 > /* > * DESIGN NOTE: Unless the user creates extra instances, there will = only be one > * instance of GSHorizontalTypesetter per thread for all text = typesetting. > * This allows aggressive caching without memory concerns since the = cache is > * reused across multiple layout operations rather than being = recreated each time. > */ >=20 >=20 > @implementation GSHorizontalTypesetter >=20 > /* > * Standard initializer. Creates the reentrancy lock used to detect > * nested layout calls on the same instance. > */ > - init > { > if (!(self =3D [super init])) return nil; > lock =3D [[NSLock alloc] init]; > return self; > } >=20 > /* > * Cleanup: release the lock and free all cached memory buffers. > */ > -(void) dealloc > { > if (cache) > { > free(cache); > cache =3D NULL; > } > if (line_frags) > { > free(line_frags); > line_frags =3D NULL; > } > DESTROY(lock); > [super dealloc]; > } >=20 > /* > * Thread-local singleton accessor. Each thread gets its own instance > * stored in the thread dictionary under a unique key. This provides > * instance reuse without requiring cross-thread synchronization. > */ > +(GSHorizontalTypesetter *) sharedInstance > { > NSMutableDictionary *threadDict =3D=20 > [[NSThread currentThread] threadDictionary]; > GSHorizontalTypesetter *shared =3D=20 > [threadDict objectForKey: @"sharedHorizontalTypesetter"]; >=20 > if (!shared) > { > shared =3D [[self alloc] init]; > [threadDict setObject: shared > forKey: @"sharedHorizontalTypesetter"]; > RELEASE(shared); > } >=20 > return shared; > } >=20 > /* > * CACHE MANAGEMENT CONSTANTS > * CACHE_INITIAL: Starting size for glyph cache (192 glyphs) > * CACHE_STEP: Increment size when cache needs to grow > */ > #define CACHE_INITIAL 192 > #define CACHE_STEP 192 >=20 >=20 > /* > * GLYPH CACHE ENTRY > * Stores all information needed to position a single glyph. > * Split into two phases: > * 1. Filled during caching (_cacheGlyphs:) > * 2. Filled during layout (layoutLineNewParagraph:) > */ > struct GSHorizontalTypesetter_glyph_cache_s > { > /* PHASE 1: Caching - extracted from layout manager and attributes = */ > NSGlyph g; // The glyph index (NSGlyph is an = integer type) > unsigned int char_index; // Corresponding character index in = text storage >=20 > NSFont *font; // Font to use for this glyph > struct > { > BOOL explicit_kern; // Whether to apply explicit kerning > float kern; // Kerning value from attributes > float baseline_offset; // Vertical offset from baseline > int superscript; // Superscript level > } attributes; >=20 > /* PHASE 2: Layout - computed during line layout */ > BOOL nominal; // YES if glyph has standard spacing = (no adjustments) > NSPoint pos; // Position relative to the line's = baseline > NSSize size; // Advancement width; height used only = for attachments > BOOL dont_show, // YES for whitespace glyphs that = shouldn't render > outside_line_frag; // YES for glyphs that overflow the = fragment (clipping mode) > }; > typedef struct GSHorizontalTypesetter_glyph_cache_s glyph_cache_t; >=20 > /* > * Clears all cached attribute and glyph information. > * Called at the start of each layout operation to ensure we don't > * use stale data from previous layouts. Note that we don't free the > * cache memory, just reset the valid length to zero. > * > * TODO: If we could detect whether the layout manager has been = modified > * since our last layout, we could avoid clearing the cache = unnecessarily. > */ > -(void) _cacheClear > { > cache_length =3D 0; >=20 > curParagraphStyle =3D nil; > paragraphRange =3D NSMakeRange(0, 0); > curAttributes =3D nil; > attributeRange =3D NSMakeRange(0, 0); > curFont =3D nil; > fontRange =3D NSMakeRange(0, 0); > } >=20 > /* > * Caches the attributes for the character at the given index. > * Uses range checking to avoid redundant dictionary lookups - if the > * requested index is within attributeRange, we already have the data. > * Extracts kern, baseline offset, and superscript into the attributes = struct. > */ > -(void) _cacheAttributes: (unsigned int)char_index > { > NSNumber *n; >=20 > if (NSLocationInRange(char_index, attributeRange)) > { > return; > } > =20 > curAttributes =3D [curTextStorage attributesAtIndex: char_index > effectiveRange: &attributeRange]; >=20 > /* Extract kerning attribute */ > n =3D [curAttributes objectForKey: NSKernAttributeName]; > if (!n) > attributes.explicit_kern =3D NO; > else > { > attributes.explicit_kern =3D YES; > attributes.kern =3D [n floatValue]; > } >=20 > /* Extract baseline offset (positive =3D up, negative =3D down in = standard Cocoa coords) */ > n =3D [curAttributes objectForKey: NSBaselineOffsetAttributeName]; > if (n) > attributes.baseline_offset =3D [n floatValue]; > else > attributes.baseline_offset =3D 0.0; >=20 > /* Extract superscript level */ > n =3D [curAttributes objectForKey: NSSuperscriptAttributeName]; > if (n) > attributes.superscript =3D [n intValue]; > else > attributes.superscript =3D 0; > } >=20 > /* > * Repositions the cache window to start at the specified glyph index. > *=20 > * If the requested glyph is already within our cache window, we shift = the > * existing data to the front (memmove) to make room for new glyphs = ahead. > *=20 > * If it's outside our cache, we reset completely and fetch new = paragraph > * style, attributes, and font information from the layout manager. > */ > -(void) _cacheMoveTo: (unsigned int)glyph > { > BOOL valid; >=20 > /* Case 1: Requested glyph is already in our cache window */ > if (cache_base <=3D glyph && cache_base + cache_length > glyph) > { > int delta =3D glyph - cache_base; > cache_length -=3D delta; > memmove(cache, &cache[delta], sizeof(glyph_cache_t) * = cache_length); > cache_base =3D glyph; > return; > } >=20 > /* Case 2: Complete reset - new location in text stream */ > cache_base =3D glyph; > cache_length =3D 0; >=20 > [curLayoutManager glyphAtIndex: glyph > isValidIndex: &valid]; >=20 > if (valid) > { > unsigned int i; >=20 > at_end =3D NO; > i =3D [curLayoutManager characterIndexForGlyphAtIndex: glyph]; > [self _cacheAttributes: i]; >=20 > /* Fetch paragraph style and its valid range */ > paragraphRange =3D NSMakeRange(i, [curTextStorage length] - i); > curParagraphStyle =3D [curTextStorage attribute: = NSParagraphStyleAttributeName > atIndex: i > longestEffectiveRange: = ¶graphRange > inRange: paragraphRange]; > if (curParagraphStyle =3D=3D nil) > { > curParagraphStyle =3D [NSParagraphStyle = defaultParagraphStyle]; > } >=20 > /* Fetch initial font and its valid range */ > curFont =3D [curLayoutManager effectiveFontForGlyphAtIndex: = glyph > range: &fontRange]; > } > else > { > at_end =3D YES; // No valid glyph at this index - we're at end = of text > } > } >=20 > /* > * Fills the glyph cache up to new_length entries. > * Grows the cache buffer if necessary using realloc. > * For each new glyph, fetches: > * - Glyph index and character index from layout manager > * - Attributes (if character index moved past attributeRange) > * - Font (if glyph moved past fontRange) > * - Advancement size from layout manager > * > * Stops early if we hit invalid glyphs or paragraph boundaries. > */ > -(void) _cacheGlyphs: (unsigned int)new_length > { > glyph_cache_t *g; > BOOL valid; >=20 > /* Grow buffer if needed */ > if (cache_size < new_length) > { > cache_size =3D new_length; > cache =3D realloc(cache, sizeof(glyph_cache_t) * cache_size); > } >=20 > /* Fill cache entries from current length up to new_length */ > for (g =3D &cache[cache_length]; cache_length < new_length; = cache_length++, g++) > { > g->g =3D [curLayoutManager glyphAtIndex: cache_base + = cache_length > isValidIndex: &valid]; > if (!valid) > { > at_end =3D YES; > break; > } > g->char_index =3D [curLayoutManager = characterIndexForGlyphAtIndex: cache_base + cache_length]; > =20 > /* Stop if we crossed paragraph boundary */ > if (g->char_index >=3D paragraphRange.location + = paragraphRange.length) > { > at_end =3D YES; > break; > } >=20 > /* Update attribute cache if needed */ > if (g->char_index >=3D attributeRange.location + = attributeRange.length) > { > [self _cacheAttributes: g->char_index]; > } >=20 > /* Copy decomposed attributes into cache entry */ > g->attributes.explicit_kern =3D attributes.explicit_kern; > g->attributes.kern =3D attributes.kern; > g->attributes.baseline_offset =3D attributes.baseline_offset; > g->attributes.superscript =3D attributes.superscript; >=20 > /* Update font cache if needed */ > if (cache_base + cache_length >=3D fontRange.location + = fontRange.length) > { > curFont =3D [curLayoutManager effectiveFontForGlyphAtIndex: = cache_base + cache_length > range: &fontRange]; > } > g->font =3D curFont; >=20 > /* Initialize layout fields */ > g->dont_show =3D NO; > g->outside_line_frag =3D NO; > g->nominal =3D YES; >=20 > /* Get glyph advancement from layout manager */ > // FIXME: This assumes the layout manager implements this = GNUstep extension > g->size =3D [curLayoutManager advancementForGlyphAtIndex: = cache_base + cache_length]; > } > } >=20 >=20 > /* > * WORD WRAPPING SUPPORT > * Searches backward from glyph gi to find a suitable word break = point. > * Returns the glyph index of the first glyph on the next line. > * > * Breaking rules: > * - Control glyphs (newlines, tabs) are always break points > * - Whitespace characters (space, newline, CR, tab) mark breaks and = are hidden > * - CJK characters (0x2FF0-0x9FFF) can break before (each CJK char = is its own word) > * > * The returned index is always >=3D cache_base and <=3D gi. > */ > -(unsigned int) breakLineByWordWrappingBefore: (unsigned int)gi > { > glyph_cache_t *g; > unichar ch; > NSString *str =3D [curTextStorage string]; >=20 > gi -=3D cache_base; // Convert to cache-relative index > g =3D cache + gi; >=20 > while (gi > 0) > { > if (g->g =3D=3D NSControlGlyph) > return gi + cache_base; // Always break at control glyphs > =20 > ch =3D [str characterAtIndex: g->char_index]; > =20 > /* Check for whitespace characters that allow breaking */ > if (ch =3D=3D 0x20 || // space > ch =3D=3D 0x0a || // new line > ch =3D=3D 0x0d || // carriage return > ch =3D=3D 0x09) // horiz. tab > { > g->dont_show =3D YES; // Hide the whitespace character = itself > if (gi > 0) > { > g->pos =3D g[-1].pos; > g->pos.x +=3D g[-1].size.width; > } > else > g->pos =3D NSMakePoint(0, 0); > g->size.width =3D 0; > return gi + 1 + cache_base; // Break after the whitespace > } > =20 > /* CJK characters: treat each as a word boundary */ > else if ((ch > 0x2ff0) && (ch < 0x9fff)) > { > g->dont_show =3D NO; > if (gi > 0) > { > g->pos =3D g[-1].pos; > g->pos.x +=3D g[-1].size.width; > } > else > g->pos =3D NSMakePoint(0,0); > return gi + cache_base; // Break before this CJK character > } =20 > =20 > gi--; > g--; > } > return gi + cache_base; // Reached start of cache - break here > } >=20 >=20 > /* > * LINE FRAGMENT STRUCTURE > * Tracks the geometry and content of a single line fragment (a = rectangular > * region within a line where glyphs are placed). > */ > struct GSHorizontalTypesetter_line_frag_s > { > NSRect rect; // The fragment rectangle in container = coordinates > CGFloat last_used; // X coordinate where glyph content ends > unsigned int lastGlyphIndex; // Index (relative to cache_base) of = last glyph + 1 > }; > typedef struct GSHorizontalTypesetter_line_frag_s line_frag_t; >=20 > /* > * Apple's maximum meaningful width for text containers. > * Widths beyond this are treated as infinite and ignored for layout = purposes. > */ > #define LARGE_SIZE 1e7 >=20 > /* > * FULL JUSTIFICATION > * Distributes extra space evenly across space characters in the line. > * Only operates if the line width is reasonable (not LARGE_SIZE). > * > * Algorithm: > * 1. Count space characters in the line > * 2. Calculate extra space per space: (rect.width - last_used) / = num_spaces > * 3. Shift all glyphs after each space by accumulating delta > * 4. Mark glyphs after spaces as non-nominal (they have adjusted = positions) > */ > -(void) fullJustifyLine: (line_frag_t *)lf : (int)num_line_frags > { > unsigned int i, start; > CGFloat extra_space, delta; > unsigned int num_spaces; > NSString *str =3D [curTextStorage string]; > glyph_cache_t *g; > unichar ch; >=20 > if (lf->rect.size.width >=3D LARGE_SIZE) > { > return; > } >=20 > for (start =3D 0; num_line_frags; num_line_frags--, lf++) > { > num_spaces =3D 0; > for (i =3D start, g =3D cache + i; i < lf->lastGlyphIndex; i++, = g++) > { > if (g->dont_show) > continue; > ch =3D [str characterAtIndex: g->char_index]; > if (ch =3D=3D 0x20) > num_spaces++; > } > if (!num_spaces) > continue; >=20 > extra_space =3D lf->rect.size.width - lf->last_used; > extra_space /=3D num_spaces; > delta =3D 0; > for (i =3D start, g =3D cache + i; i < lf->lastGlyphIndex; i++, = g++) > { > g->pos.x +=3D delta; > if (!g->dont_show && [str characterAtIndex: g->char_index] =3D=3D= 0x20) > { > if (i < lf->lastGlyphIndex) > g[1].nominal =3D NO; // Next glyph has non-standard = position > delta +=3D extra_space; > } > } > start =3D lf->lastGlyphIndex; > lf->last_used =3D lf->rect.size.width; > } > } >=20 > /* > * RIGHT ALIGNMENT > * Shifts all glyphs right by the difference between fragment width = and used width. > */ > -(void) rightAlignLine: (line_frag_t *)lf : (int)num_line_frags > { > unsigned int i; > CGFloat delta; > glyph_cache_t *g; >=20 > if (lf->rect.size.width >=3D LARGE_SIZE) > { > return; > } >=20 > for (i =3D 0, g =3D cache; num_line_frags; num_line_frags--, lf++) > { > delta =3D lf->rect.size.width - lf->last_used; > for (; i < lf->lastGlyphIndex; i++, g++) > g->pos.x +=3D delta; > lf->last_used +=3D delta; > } > } >=20 > /* > * CENTER ALIGNMENT > * Shifts all glyphs right by half the remaining space. > */ > -(void) centerAlignLine: (line_frag_t *)lf : (int)num_line_frags > { > unsigned int i; > CGFloat delta; > glyph_cache_t *g; >=20 > if (lf->rect.size.width >=3D LARGE_SIZE) > { > return; > } >=20 > for (i =3D 0, g =3D cache; num_line_frags; num_line_frags--, lf++) > { > delta =3D (lf->rect.size.width - lf->last_used) / 2.0; > for (; i < lf->lastGlyphIndex; i++, g++) > g->pos.x +=3D delta; > lf->last_used +=3D delta; > } > } >=20 >=20 > /* > * SOFT INVALIDATION OPTIMIZATION > * Attempts to reuse layout information from previous layout passes = that > * were "soft invalidated" (marked as potentially changed but not = definitely wrong). > * > * This handles the common case of simple text edits where line = fragments > * just need to be shifted vertically without changing their = horizontal layout. > * > * Returns YES if soft-invalidated layout was successfully reused. > */ > -(BOOL) _reuseSoftInvalidatedLayout > { > NSRect r0, r; > NSSize shift; > int i; > unsigned int g, g2, first; > CGFloat container_height; > =20 > /* Get first soft-invalidated rect starting at current glyph */ > r0 =3D [curLayoutManager _softInvalidateLineFragRect: 0 > firstGlyph: &first > nextGlyph: &g > inTextContainer: curTextContainer]; >=20 > container_height =3D [curTextContainer containerSize].height; > if (!(curPoint.y + r0.size.height <=3D container_height)) > return NO; // Won't fit at current Y position >=20 > /* > * We can shift the rects vertically to fit. Collect all consecutive > * soft-invalidated line fragments and apply the same shift. > */ > shift.width =3D 0; > shift.height =3D curPoint.y - r0.origin.y; > i =3D 1; > curPoint.y =3D NSMaxY(r0) + shift.height; > =20 > for (; 1; i++) > { > r =3D [curLayoutManager _softInvalidateLineFragRect: i > firstGlyph: &first > nextGlyph: &g2 > inTextContainer: = curTextContainer]; >=20 > /* Gap in soft-invalidated info - must fill in before continuing = */ > if (first !=3D g) > { > break; > } >=20 > if (NSIsEmptyRect(r) || NSMaxY(r) + shift.height > = container_height) > break; >=20 > g =3D g2; > curPoint.y =3D NSMaxY(r) + shift.height; > } >=20 > /* Commit the reused layout to the layout manager */ > [curLayoutManager _softInvalidateUseLineFrags: i > withShift: shift > inTextContainer: curTextContainer]; >=20 > curGlyphIndex =3D g; > return YES; > } >=20 >=20 > /* > * Calculates the proposed rectangle for a new line fragment. > *=20 > * newParagraph: YES for first line of paragraph (uses = firstLineHeadIndent), > * NO for subsequent lines (uses headIndent) > * line_height: The height to request from the text container > * > * Returns: Proposed rectangle in container coordinates > */ > - (NSRect)_getProposedRectFor: (BOOL)newParagraph > withLineHeight: (CGFloat) line_height=20 > { > CGFloat hindent; > CGFloat tindent =3D [curParagraphStyle tailIndent]; >=20 > if (newParagraph) > hindent =3D [curParagraphStyle firstLineHeadIndent]; > else > hindent =3D [curParagraphStyle headIndent]; >=20 > /* Negative tail indent is treated as inset from right edge */ > if (tindent <=3D 0.0) > {=20 > NSSize size; >=20 > size =3D [curTextContainer containerSize]; > tindent =3D size.width + tindent; > } >=20 > return NSMakeRect(hindent, > curPoint.y, > tindent - hindent, > line_height + [curParagraphStyle lineSpacing]); > } >=20 > /* > * Creates the "extra line fragment" used when text ends with a = newline. > * This provides a place for the insertion point (caret) after the = last newline. > * The fragment has full line height but minimal width (1 unit). > */ > - (void) _addExtraLineFragment > { > NSRect r, r2, remain; > CGFloat line_height; >=20 > /* > * We need the attributes from the last character to match the = style. > * _cacheMoveTo: ensures curParagraphStyle and curFont are set. > */ > if (curGlyphIndex) > { > [self _cacheMoveTo: curGlyphIndex - 1]; > } > else > { > /* No glyphs yet - use typing attributes (default style for new = text) */ > NSDictionary *typingAttributes =3D [curLayoutManager = typingAttributes]; > curParagraphStyle =3D [typingAttributes > objectForKey: = NSParagraphStyleAttributeName]; > if (curParagraphStyle =3D=3D nil) > { > curParagraphStyle =3D [NSParagraphStyle = defaultParagraphStyle]; > } > curFont =3D [typingAttributes objectForKey: = NSFontAttributeName]; > } >=20 > /* Determine line height from font or use default */ > if (curFont) > { > line_height =3D [curFont defaultLineHeightForFont]; > } > else > { > line_height =3D 15.0; > } >=20 > r =3D [self _getProposedRectFor: YES > withLineHeight: line_height]; > r =3D [curTextContainer lineFragmentRectForProposedRect: r > sweepDirection: = NSLineSweepRight > movementDirection: = NSLineMovesDown > remainingRect: &remain]; > =20 > if (!NSIsEmptyRect(r)) > { > r2 =3D r; > r2.size.width =3D 1; // Minimal width for caret positioning > [curLayoutManager setExtraLineFragmentRect: r > usedRect: r2 > textContainer: curTextContainer]; > } > } >=20 > /* > * Helper for line height calculations. > * Updates *lineHeight to newHeight if newHeight is larger (and within = max). > * Returns YES if the line height was updated. > */ > static inline BOOL wantNewLineHeight(CGFloat h, CGFloat *lineHeight, = CGFloat maxLineHeight) > { > CGFloat newHeight =3D h; >=20 > if (maxLineHeight > 0 && newHeight > maxLineHeight) > { > newHeight =3D maxLineHeight; > } >=20 > if (newHeight > *lineHeight) > { > *lineHeight =3D newHeight; > return YES; > } > return NO; > } >=20 > /* > * CORE LAYOUT METHOD > * Lays out a single line of text, handling all complexity of glyph = positioning, > * line breaking, attachments, and alignment. > * > * newParagraph: YES if this is the first line of a paragraph > * > * Return values: > * 0 - Line completed normally, next glyph continues this paragraph > * 1 - No room in text container (line fragments exhausted) > * 2 - All glyphs laid out (end of text) > * 3 - Line ended with newline, next glyph starts new paragraph > * 4 - Ambiguous state (must test before next call - from soft = invalidation) > */ > -(int) layoutLineNewParagraph: (BOOL)newParagraph > { > NSRect rect; >=20 > /* LINE METRICS VARIABLES */ > CGFloat line_height; // Current line height (ascender + = descender) > CGFloat max_line_height; // Maximum allowed (from paragraph style, 0 = =3D unlimited) > CGFloat baseline; // Distance from top of line to baseline > CGFloat ascender; // Space needed above baseline (max of all = glyphs) > CGFloat descender; // Space needed below baseline (max of all = glyphs) >=20 > /* > * SOFT INVALIDATION CHECK > * Try to reuse previous layout if available and appropriate. > */ > if ([curTextContainer isSimpleRectangularTextContainer] && > [curLayoutManager _softInvalidateFirstGlyphInTextContainer: = curTextContainer] =3D=3D curGlyphIndex) > { > if ([self _reuseSoftInvalidatedLayout]) > return 4; > } >=20 > /* Initialize cache at current glyph position */ > [self _cacheMoveTo: curGlyphIndex]; > if (!cache_length) > [self _cacheGlyphs: CACHE_INITIAL]; > if (!cache_length && at_end) > { > /* No more glyphs to lay out */ > if (newParagraph) > { > [self _addExtraLineFragment]; // Text ended with newline > } > return 2; > } >=20 > /* INITIALIZE LINE METRICS from first glyph's font */ > { > CGFloat min =3D [curParagraphStyle minimumLineHeight]; > max_line_height =3D [curParagraphStyle maximumLineHeight]; >=20 > /* Sanity: max must be >=3D min if both are specified */ > if (max_line_height > 0 && max_line_height < min) > max_line_height =3D min; >=20 > line_height =3D [cache->font defaultLineHeightForFont]; > ascender =3D [cache->font ascender]; > descender =3D -[cache->font descender]; >=20 > if (line_height < min) > line_height =3D min; >=20 > if (max_line_height > 0 && line_height > max_line_height) > line_height =3D max_line_height; > } >=20 > /* > * LINE FRAGMENT ACQUISITION > * Get rectangles from text container until we have room for at = least > * one glyph. If line height increases due to large glyphs, we = restart > * this process since the rectangles might change. > */ > restart: ; >=20 > do > { > NSRect remain; >=20 > remain =3D [self _getProposedRectFor: newParagraph > withLineHeight: line_height]; >=20 > /* > * Build list of line fragment rects for this line. > * A line may have multiple fragments when flowing around = shapes. > * TODO: This builds all rects in advance which might be = inefficient > * for containers with many exclusions (e.g., narrow columns). > */ > line_frags_num =3D 0; > rect =3D [curTextContainer lineFragmentRectForProposedRect: = remain > sweepDirection: = NSLineSweepRight > movementDirection: = NSLineMovesDown > remainingRect: = &remain]; > while (!NSIsEmptyRect(rect)) > { > line_frags_num++; > if (line_frags_num > line_frags_size) > { > line_frags_size +=3D 2; > line_frags =3D realloc(line_frags, sizeof(line_frag_t) * = line_frags_size); > } > line_frags[line_frags_num - 1].rect =3D rect; >=20 > rect =3D [curTextContainer lineFragmentRectForProposedRect: = remain > sweepDirection: = NSLineSweepRight > movementDirection: = NSLineDoesntMove > remainingRect: = &remain]; > } > if (line_frags_num =3D=3D 0) > { > /* No fragments available - container might be too small */ > if (curPoint.y =3D=3D 0.0 && > line_height > [curTextContainer containerSize].height && > [curTextContainer containerSize].height > 0.0) > { > /* Emergency: shrink line height to fit at least one = line */ > line_height =3D [curTextContainer containerSize].height; > max_line_height =3D line_height; > continue; > } > return 1; // No room in container > } > } > while (line_frags_num =3D=3D 0); >=20 > /* > * MAIN GLYPH LAYOUT LOOP > * Positions each glyph in the line fragments, handling: > * - Font changes and metric updates > * - Kerning and baseline adjustments > * - Superscript/subscript positioning > * - Tab stops > * - Text attachments (images, etc.) > * - Line breaking when fragments fill up > */ > { > unsigned int i =3D 0; > glyph_cache_t *g; >=20 > NSPoint p; // Current glyph position (relative to = line fragment) > =20 > NSFont *f =3D cache->font; >=20 > CGFloat f_ascender =3D [f ascender]; > CGFloat f_descender =3D -[f descender]; >=20 > NSGlyph last_glyph =3D NSNullGlyph; // For kerning calculations > NSPoint last_p; >=20 > unsigned int firstGlyphIndex; // First glyph in current line = fragment > line_frag_t *lf =3D line_frags; // Current line fragment > int lfi =3D 0; // Line fragment index >=20 > BOOL prev_had_non_nominal_width; // Track if previous glyph had = custom spacing >=20 >=20 > last_p =3D p =3D NSMakePoint(0, 0); >=20 > g =3D cache; > firstGlyphIndex =3D 0; > prev_had_non_nominal_width =3D NO; >=20 > while (1) > { > BOOL doesGlyphFitInLine =3D YES; >=20 > /* Ensure we have cached glyphs to process */ > if (i >=3D cache_length) > { > if (at_end) > { > newParagraph =3D NO; > break; // End of text > } > [self _cacheGlyphs: cache_length + CACHE_STEP]; > if (i >=3D cache_length) > { > newParagraph =3D NO; > break; // No more glyphs available > } > g =3D cache + i; > } >=20 > /* > * FONT CHANGE HANDLING > * Update ascender/descender tracking when font changes. > * Reset last_glyph to disable kerning across font boundaries. > */ > if (g->font !=3D f) > { > f =3D g->font; > f_ascender =3D [f ascender]; > f_descender =3D -[f descender]; > last_glyph =3D NSNullGlyph; > } >=20 > /* Apply explicit kerning if specified in attributes */ > g->nominal =3D !prev_had_non_nominal_width; >=20 > if (g->attributes.explicit_kern && > g->attributes.kern !=3D 0) > { > p.x +=3D g->attributes.kern; > g->nominal =3D NO; > } >=20 > /* Check if glyph fits in current line fragment width */ > doesGlyphFitInLine =3D !((i > firstGlyphIndex) && (p.x + = g->size.width > lf->rect.size.width)); > =20 > if (doesGlyphFitInLine) > { > /* Calculate vertical position with baseline adjustments = */ > CGFloat y =3D 0; >=20 > /* Apply superscript offset (negative =3D up in flipped = coords) */ > if (g->attributes.superscript) > { > y -=3D g->attributes.superscript * [f xHeight]; > } > /* Apply explicit baseline offset */ > if (g->attributes.baseline_offset) > { > y +=3D g->attributes.baseline_offset; > } >=20 > if (y !=3D p.y) > { > p.y =3D y; > g->nominal =3D NO; // Non-standard vertical position > } > =20 > /* Update line metrics based on this glyph */ > if (f_ascender > ascender) > ascender =3D f_ascender; > if (f_descender > descender) > descender =3D f_descender; >=20 > /* Adjust for superscript/subscript height requirements */ > if (y < 0 && f_ascender - y > ascender) > ascender =3D f_ascender - y; > if (y > 0 && f_descender + y > descender) > descender =3D f_descender + y; >=20 > /* If metrics changed, check if we need to restart with = new line height */ > if (wantNewLineHeight(ascender + descender, &line_height, = max_line_height)) > goto restart; > } >=20 > /* CONTROL GLYPH HANDLING (newlines, tabs, etc.) */ > if (g->g =3D=3D NSControlGlyph) > { > unichar ch =3D [[curTextStorage string] characterAtIndex: = g->char_index]; >=20 > g->pos =3D p; > g->size.width =3D 0; > g->dont_show =3D YES; // Control glyphs don't render > g->nominal =3D !prev_had_non_nominal_width; > i++; > g++; > last_glyph =3D NSNullGlyph; > prev_had_non_nominal_width =3D NO; >=20 > /* NEWLINE: End this line, start new paragraph */ > if (ch =3D=3D 0xa) // new line > { > newParagraph =3D YES; > break; > } >=20 > /* TAB: Advance to next tab stop */ > if (ch =3D=3D 0x9) // horiz. tab > { > NSArray *tabs =3D [curParagraphStyle tabStops]; > NSTextTab *tab =3D nil; > CGFloat defaultInterval =3D [curParagraphStyle = defaultTabInterval]; > if (defaultInterval =3D=3D 0.0) > { > defaultInterval =3D 100.0; // Reasonable default > } > unsigned tabIndex; > unsigned tabCount =3D [tabs count]; > =20 > /* Find next tab stop after current position */ > for (tabIndex =3D 0; tabIndex < tabCount; tabIndex++) > { > tab =3D [tabs objectAtIndex: tabIndex]; > if ([tab location] > p.x + lf->rect.origin.x) > { > break; > } > } > if (tabIndex =3D=3D tabCount) > { > /* Past last explicit tab stop - use default = interval */ > p.x =3D (floor(p.x / defaultInterval) + 1.0) * = defaultInterval; > } > else > { > p.x =3D [tab location] - lf->rect.origin.x; > } > prev_had_non_nominal_width =3D YES; > continue; > } >=20 > /* Unknown control character - log and ignore */ > NSDebugLLog(@"GSHorizontalTypesetter", > @"ignoring unknown control character %04x\n", ch); > continue; > } >=20 > /* TEXT ATTACHMENT HANDLING (images, files, etc.) */ > if (g->g =3D=3D GSAttachmentGlyph) > { > NSTextAttachment *attach; > NSTextAttachmentCell *cell; > NSRect r; >=20 > attach =3D [curTextStorage attribute: = NSAttachmentAttributeName > atIndex: g->char_index > effectiveRange: NULL]; > cell =3D (NSTextAttachmentCell*)[attach attachmentCell]; > if (!cell) > { > /* No cell for attachment - treat as zero-width = invisible glyph */ > g->pos =3D p; > g->size =3D NSMakeSize(0, 0); > g->dont_show =3D YES; > g->nominal =3D YES; > i++; > g++; > last_glyph =3D NSNullGlyph; > continue; > } >=20 > /* Calculate baseline position for attachment alignment */ > baseline =3D line_height - descender; >=20 > /* Ask attachment cell for its desired frame */ > r =3D [cell cellFrameForTextContainer: curTextContainer > proposedLineFragment: lf->rect > glyphPosition: NSMakePoint(p.x, > lf->rect.size.height - = baseline) > characterIndex: g->char_index]; >=20 > /* Check if attachment fits in current fragment */ > doesGlyphFitInLine =3D !((i > firstGlyphIndex) && (p.x + = NSMaxX(r) > lf->rect.size.width)); > if (doesGlyphFitInLine) > { > /* Update line metrics for attachment size */ > if (-NSMinY(r) > descender) > descender =3D -NSMinY(r); > if (NSMaxY(r) > ascender) > ascender =3D NSMaxY(r); >=20 > /* Check if attachment forces line height increase */ > if (wantNewLineHeight(ascender + descender, = &line_height, max_line_height)) > goto restart; > } >=20 > /* Position attachment (note: r is upside-down relative to = our coords) */ > g->size =3D r.size; > g->pos.x =3D p.x + r.origin.x; > g->pos.y =3D p.y - r.origin.y; >=20 > p.x =3D g->pos.x + g->size.width; > g->nominal =3D NO; // Attachments always have custom = positioning > } > else > { > /* STANDARD GLYPH: Just use cached advancement */ > /* TODO: Kerning is commented out as a bottleneck - needs = optimization */ > last_p =3D g->pos =3D p; > p.x +=3D g->size.width; > } >=20 > /* LINE BREAKING: Glyph didn't fit in current fragment */ > if (!doesGlyphFitInLine) > { > switch ([curParagraphStyle lineBreakMode]) > { > default: > case NSLineBreakByCharWrapping: > /* Break immediately before current glyph */ > lf->lastGlyphIndex =3D i; > break; >=20 > case NSLineBreakByWordWrapping: > /* Search backward for word boundary */ > lf->lastGlyphIndex =3D [self = breakLineByWordWrappingBefore: cache_base + i] - cache_base; > if (lf->lastGlyphIndex <=3D firstGlyphIndex) > { > // No word boundary found - fall back to character = wrapping > lf->lastGlyphIndex =3D i; > } > break; >=20 > case NSLineBreakByTruncatingHead: > case NSLineBreakByTruncatingMiddle: > case NSLineBreakByTruncatingTail: > case NSLineBreakByClipping: > /* CLIPPING/TRUNCATING: Hide overflowing glyphs */ > g->outside_line_frag =3D YES; > while (1) > { > i++; > g++; > if (i >=3D cache_length) > { > if (at_end) > { > newParagraph =3D NO; > i--; > break; > } > [self _cacheGlyphs: cache_length + CACHE_STEP]; > if (i >=3D cache_length) > { > newParagraph =3D NO; > i--; > break; > } > g =3D cache + i; > } > g->dont_show =3D YES; > g->pos =3D p; > /* Stop at paragraph break */ > if (g->g =3D=3D NSControlGlyph > && [[curTextStorage string] > characterAtIndex: g->char_index] =3D=3D = 0xa) > break; > } > lf->lastGlyphIndex =3D i + 1; > break; > } >=20 > /* > * SAFETY: Ensure at least one glyph per fragment. > * Prevents infinite loops when container is narrower than = a single glyph. > */ > if (lf->lastGlyphIndex <=3D firstGlyphIndex) > lf->lastGlyphIndex =3D i + 1; >=20 > /* Reset position for next line fragment */ > last_p =3D p =3D NSMakePoint(0, 0); > i =3D lf->lastGlyphIndex; > g =3D cache + i; > lf->last_used =3D g[-1].pos.x + g[-1].size.width; > last_glyph =3D NSNullGlyph; > prev_had_non_nominal_width =3D NO; >=20 > /* Move to next line fragment or end line if exhausted */ > lf++; > lfi++; > if (lfi =3D=3D line_frags_num) > { > newParagraph =3D NO; > break; > } > firstGlyphIndex =3D i; > } > else > { > /* Glyph fit - advance to next */ > last_glyph =3D g->g; > if (last_glyph =3D=3D GSAttachmentGlyph) > { > last_glyph =3D NSNullGlyph; > prev_had_non_nominal_width =3D YES; > } > else > { > prev_had_non_nominal_width =3D NO; > } > i++; > g++; > } > } > /* END MAIN LAYOUT LOOP */ >=20 > /* ALIGNMENT PASS: Apply paragraph alignment to positioned glyphs = */ > if (lfi !=3D line_frags_num) > { > /* Line filled exactly - apply alignment */ > lf->lastGlyphIndex =3D i; > lf->last_used =3D p.x; >=20 > if ([curParagraphStyle alignment] =3D=3D NSRightTextAlignment) > [self rightAlignLine: line_frags : line_frags_num]; > else if ([curParagraphStyle alignment] =3D=3D = NSCenterTextAlignment) > [self centerAlignLine: line_frags : line_frags_num]; > } > else > { > /* Line broke early - check for justification or alignment */ > if ([curParagraphStyle lineBreakMode] =3D=3D = NSLineBreakByWordWrapping && > [curParagraphStyle alignment] =3D=3D = NSJustifiedTextAlignment) > [self fullJustifyLine: line_frags : line_frags_num]; > else if ([curParagraphStyle alignment] =3D=3D = NSRightTextAlignment) > [self rightAlignLine: line_frags : line_frags_num]; > else if ([curParagraphStyle alignment] =3D=3D = NSCenterTextAlignment) > [self centerAlignLine: line_frags : line_frags_num]; >=20 > lfi--; > } >=20 > /* COMMIT LAYOUT TO LAYOUT MANAGER */ > [curLayoutManager setTextContainer: curTextContainer > forGlyphRange: NSMakeRange(cache_base, i)]; > curGlyphIndex =3D i + cache_base; > =20 > { > line_frag_t *lf; > NSPoint p; > unsigned int lineFragCounter, lineFragCounter2; > glyph_cache_t *g; > NSRect used_rect; >=20 > /* Final baseline calculation */ > baseline =3D line_height - descender; >=20 > /* Iterate through line fragments and register each with layout = manager */ > for (lf =3D line_frags, lineFragCounter =3D 0, g =3D cache; lfi = >=3D 0; lfi--, lf++) > { > /* Calculate used rect (actual ink bounds) vs fragment rect = (available space) */ > used_rect.origin.x =3D g->pos.x + lf->rect.origin.x; > used_rect.size.width =3D lf->last_used - g->pos.x; > used_rect.origin.y =3D lf->rect.origin.y; > used_rect.size.height =3D lf->rect.size.height; >=20 > /* Register the line fragment */ > [curLayoutManager setLineFragmentRect: lf->rect > forGlyphRange: NSMakeRange(cache_base + = lineFragCounter, lf->lastGlyphIndex - lineFragCounter) > usedRect: used_rect]; > =20 > /* Register individual glyph positions */ > p =3D g->pos; > p.y +=3D baseline; // Convert from relative to absolute = position > lineFragCounter2 =3D lineFragCounter; > while (lineFragCounter < lf->lastGlyphIndex) > { > /* Set flags for special glyph handling */ > if (g->outside_line_frag) > { > [curLayoutManager setDrawsOutsideLineFragment: YES > forGlyphAtIndex: cache_base + lineFragCounter]; > } > if (g->dont_show) > { > [curLayoutManager setNotShownAttribute: YES > forGlyphAtIndex: cache_base + = lineFragCounter]; > } > =20 > /* Register glyph runs with non-nominal positioning */ > if (!g->nominal && lineFragCounter !=3D lineFragCounter2) > { > [curLayoutManager setLocation: p > forStartOfGlyphRange: = NSMakeRange(cache_base + lineFragCounter2, lineFragCounter - = lineFragCounter2)]; > if (g[-1].g =3D=3D GSAttachmentGlyph) > { > [curLayoutManager setAttachmentSize: g[-1].size > forGlyphRange: NSMakeRange(cache_base + = lineFragCounter2, lineFragCounter - lineFragCounter2)]; > } > p =3D g->pos; > p.y +=3D baseline; > lineFragCounter2 =3D lineFragCounter; > } > lineFragCounter++; > g++; > } > /* Register final run in fragment */ > if (lineFragCounter !=3D lineFragCounter2) > { > [curLayoutManager setLocation: p > forStartOfGlyphRange: = NSMakeRange(cache_base + lineFragCounter2, lineFragCounter - = lineFragCounter2)]; > if (g[-1].g =3D=3D GSAttachmentGlyph) > { > [curLayoutManager setAttachmentSize: g[-1].size > forGlyphRange: NSMakeRange(cache_base + = lineFragCounter2, lineFragCounter - lineFragCounter2)]; > } > } > } > } > } >=20 > /* Advance current point to next line */ > curPoint =3D NSMakePoint(0, NSMaxY(line_frags->rect)); >=20 > if (newParagraph) > return 3; > else > return 0; > } >=20 >=20 > /* > * MAIN ENTRY POINT > * Lays out multiple lines of glyphs into the text container. > * > * Parameters: > * layoutManager: The GSLayoutManager managing this text > * textContainer: The container defining layout geometry > * glyphIndex: Starting glyph index for this layout = operation > * previousLineFragRect: Rectangle of the previous line (for = positioning) > * nextGlyphIndex: OUTPUT - index of first unlaid glyph > * howMany: Maximum number of lines to layout (0 =3D = unlimited) > * > * Return values: > * 0 - Layout completed successfully (more glyphs may remain) > * 1 - Text container is full > * 2 - All glyphs have been laid out > */ > -(int) layoutGlyphsInLayoutManager: (GSLayoutManager *)layoutManager > inTextContainer: (NSTextContainer *)textContainer > startingAtGlyphIndex: (unsigned int)glyphIndex > previousLineFragmentRect: (NSRect)previousLineFragRect > nextGlyphIndex: (unsigned int *)nextGlyphIndex > numberOfLineFragments: (unsigned int)howMany > { > int ret, real_ret; > BOOL newParagraph; >=20 > /* REENTRANCY HANDLING */ > if (![lock tryLock]) > { > /* Already in use - create temporary instance to handle nested = call */ > GSHorizontalTypesetter *temp; >=20 > temp =3D [[object_getClass(self) alloc] init]; > ret =3D [temp layoutGlyphsInLayoutManager: layoutManager > inTextContainer: textContainer > startingAtGlyphIndex: glyphIndex > previousLineFragmentRect: previousLineFragRect > nextGlyphIndex: nextGlyphIndex > numberOfLineFragments: howMany]; > DESTROY(temp); > return ret; > } >=20 > NS_DURING > /* Initialize layout context */ > curLayoutManager =3D layoutManager; > curTextContainer =3D textContainer; > curTextStorage =3D [layoutManager textStorage]; > curGlyphIndex =3D glyphIndex; >=20 > [self _cacheClear]; >=20 > real_ret =3D 4; // Initial state forces paragraph check > curPoint =3D NSMakePoint(0, NSMaxY(previousLineFragRect)); > =20 > /* Main layout loop - process lines until limit or completion */ > while (1) > { > /* Determine if we're starting a new paragraph */ > if (real_ret =3D=3D 4) > { > if (!curGlyphIndex) > { > newParagraph =3D YES; // Very first glyph in text > } > else > { > /* Check if previous character was a newline */ > unsigned int chi; > unichar ch; > chi =3D [curLayoutManager characterRangeForGlyphRange: = NSMakeRange(curGlyphIndex - 1, 1) > actualGlyphRange: = NULL].location; > ch =3D [[curTextStorage string] characterAtIndex: chi]; > =09 > if (ch =3D=3D '\n') > newParagraph =3D YES; > else > newParagraph =3D NO; > } > } > else if (real_ret =3D=3D 3) > { > newParagraph =3D YES; // Previous line ended with newline > } > else > { > newParagraph =3D NO; > } >=20 > /* Layout one line */ > ret =3D [self layoutLineNewParagraph: newParagraph]; >=20 > /* Normalize return codes 3 and 4 to 0 for loop control */ > real_ret =3D ret; > if (ret =3D=3D 3 || ret =3D=3D 4) > ret =3D 0; >=20 > if (ret) > break; // Error or completion (1 or 2) >=20 > if (howMany) > if (!--howMany) > break; // Reached line limit > } >=20 > *nextGlyphIndex =3D curGlyphIndex; > NS_HANDLER > /* Exception handling: log, unlock, and re-raise */ > NSLog(@"GSHorizontalTypesetter - %@", [localException reason]); > [lock unlock]; > [localException raise]; > ret=3D0; /* Unreachable, but silences compiler warnings */ > NS_ENDHANDLER > =20 > [lock unlock]; > return ret; > } >=20 > @end > ``` >=20 > ## Summary >=20 > This is a sophisticated horizontal typesetter for GNUstep implementing = the full Cocoa text layout architecture. Key features include: >=20 > **Architecture:** > - **Thread-local singleton** pattern for performance without locking = overhead > - **Reentrancy support** via temporary instance creation when nested = calls occur > - **Two-phase caching**: First cache glyph attributes, then compute = positions >=20 > **Layout Algorithm:** > 1. **Soft invalidation** check for incremental layout reuse > 2. **Line fragment acquisition** from text container (handles complex = shapes) > 3. **Glyph-by-glyph positioning** with font changes, attachments, tabs > 4. **Line breaking** by character or word (with CJK support) > 5. **Alignment/justification** pass to distribute space > 6. **Commit** to layout manager with precise glyph ranges and = positions >=20 > **Performance Optimizations:** > - Aggressive caching of glyph metrics and attributes > - Batch glyph processing with expandable buffers > - Inline helper functions for hot paths > - Memmove for cache window sliding rather than refetching >=20 > The code handles complex typography including bidirectional text = support preparation, text attachments, multiple line fragments per line, = and all standard paragraph alignments. >=20 >=20 > =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80= =94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80= =94=20 >=20 >=20 > Then I asked: >=20 >=20 > =E2=80=9EWhat refactorings do you propose to make that above code you = commented better readable and simpler to comprehend without making it = worse?=E2=80=9C >=20 >=20 > =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80= =94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80= =94=20 >=20 >=20 > I'll analyze this code and propose concrete refactorings to improve = readability and maintainability. Let me work through this = systematically. >=20 > ## Proposed Refactorings for GSHorizontalTypesetter >=20 > ### 1. **Extract Magic Numbers and Character Constants** >=20 > Replace hardcoded hex values with named constants: >=20 > ```objc > // Add to top of file or private header > static const unichar kSpaceCharacter =3D 0x20; > static const unichar kNewlineCharacter =3D 0x0a; > static const unichar kCarriageReturnCharacter =3D 0x0d; > static const unichar kTabCharacter =3D 0x09; > static const unichar kCJKRangeStart =3D 0x2ff0; > static const unichar kCJKRangeEnd =3D 0x9fff; > static const CGFloat kDefaultTabInterval =3D 100.0; > static const CGFloat kFallbackLineHeight =3D 15.0; > static const CGFloat kExtraLineFragmentWidth =3D 1.0; > ``` >=20 > ### 2. **Extract Complex Conditionals into Meaningful Boolean = Methods** >=20 > The glyph fitting check appears multiple times and is hard to read: >=20 > ```objc > // BEFORE: scattered throughout code > doesGlyphFitInLine =3D !((i > firstGlyphIndex) && (p.x + g->size.width = > lf->rect.size.width)); >=20 > // AFTER: extracted method > - (BOOL)doesGlyphAtIndex:(unsigned int)glyphIndex=20 > withWidth:(CGFloat)width=20 > fitInLineFragment:(line_frag_t *)fragment > afterFirstGlyph:(unsigned int)firstIndex > { > if (glyphIndex <=3D firstIndex) return YES; // First glyph always = fits > return (p.x + width <=3D fragment->rect.size.width); > } > ``` >=20 > ### 3. **Replace `goto restart` with Structured Loop** >=20 > The `restart` label for line height recalculation is a classic goto = pattern: >=20 > ```objc > // BEFORE: goto-based restart > restart: ; > do { > // ... calculate fragments ... > if (line_frags_num =3D=3D 0) { > if (/* emergency shrink */) { > line_height =3D ...; > continue; // or goto restart > } > return 1; > } > } while (line_frags_num =3D=3D 0); >=20 > // ... later ... > if (wantNewLineHeight(...)) goto restart; >=20 > // AFTER: extracted method with explicit loop > - (BOOL)_acquireLineFragmentsWithHeight:(CGFloat)lineHeight > maxLineHeight:(CGFloat)maxLineHeight > newParagraph:(BOOL)newParagraph > resultingHeight:(CGFloat *)outHeight > { > CGFloat currentHeight =3D lineHeight; > CGFloat maxHeight =3D maxLineHeight; > =20 > while (YES) { > [self _buildLineFragmentsForHeight:currentHeight = newParagraph:newParagraph]; > =20 > if (line_frags_num > 0) { > *outHeight =3D currentHeight; > return YES; // Success > } > =20 > // Emergency: container too small for even one line > if (curPoint.y =3D=3D 0.0 && currentHeight > containerHeight = && containerHeight > 0.0) { > currentHeight =3D containerHeight; > maxHeight =3D currentHeight; > continue; // Retry with shrunk height > } > =20 > return NO; // No room in container > } > } >=20 > // Then in main layout, replace goto with: > while (![self _updateLineHeightIfNeeded:ascender descender:descender=20= > currentHeight:&line_height=20 > maxHeight:max_line_height]) { > // Restart layout with new height > if (![self _acquireLineFragmentsWithHeight:line_height ...]) { > return 1; > } > // Re-initialize cache if needed > } > ``` >=20 > ### 4. **Extract State Machine Transitions** >=20 > The `real_ret` state machine in `layoutGlyphsInLayoutManager:` is = cryptic: >=20 > ```objc > // BEFORE: confusing state tracking > real_ret =3D 4; > while (1) { > if (real_ret =3D=3D 4) { > // determine newParagraph > } else if (real_ret =3D=3D 3) { > newParagraph =3D YES; > } else { > newParagraph =3D NO; > } > ret =3D [self layoutLineNewParagraph:newParagraph]; > real_ret =3D ret; > if (ret =3D=3D 3 || ret =3D=3D 4) ret =3D 0; > } >=20 > // AFTER: explicit state enum and transition method > typedef NS_ENUM(NSInteger, LayoutState) { > LayoutStateInitial =3D 0, // Need to check if new paragraph > LayoutStateNewParagraph, // Previous line ended with newline > LayoutStateContinuing, // In middle of paragraph > LayoutStateComplete, // All glyphs laid out > LayoutStateNoRoom // Container full > }; >=20 > - (LayoutState)determineNextStateFrom:(LayoutState)previousState=20 > lineResult:(int)lineResult > { > switch (lineResult) { > case 2: return LayoutStateComplete; // At end > case 1: return LayoutStateNoRoom; // No room > case 3: return LayoutStateNewParagraph; // Ended with newline > case 0: return LayoutStateContinuing; // Normal line break > case 4: return LayoutStateInitial; // Ambiguous, need check > default: return LayoutStateComplete; > } > } >=20 > - (BOOL)isNewParagraphForState:(LayoutState)state=20 > atGlyphIndex:(unsigned int)glyphIndex > { > switch (state) { > case LayoutStateInitial: > return [self _glyphStartsNewParagraph:glyphIndex]; > case LayoutStateNewParagraph: > return YES; > case LayoutStateContinuing: > return NO; > default: > return NO; > } > } > ``` >=20 > ### 5. **Break Up `layoutLineNewParagraph:` into Phased Methods** >=20 > This 400+ line method does too much. Extract phases: >=20 > ```objc > - (int)layoutLineNewParagraph:(BOOL)newParagraph > { > // Phase 1: Setup and cache initialization > if (![self _prepareLayoutForNewParagraph:newParagraph]) { > return [self _handleEndOfTextOrContainer]; > } > =20 > // Phase 2: Acquire line fragments (may restart if metrics change) > CGFloat finalLineHeight; > if (![self _acquireLineFragmentsWithHeight:initialLineHeight=20 > maxLineHeight:maxLineHeight > newParagraph:newParagraph > resultingHeight:&finalLineHeight]) { > return 1; // No room > } > =20 > // Phase 3: Position glyphs in fragments > LayoutLineResult *result =3D [self _positionGlyphsInFragments]; > if (result.needsRestart) { > // Recursively retry with new height (limited depth to prevent = infinite loop) > return [self layoutLineNewParagraph:newParagraph]; > } > =20 > // Phase 4: Apply alignment > [self _applyAlignmentToLine:result]; > =20 > // Phase 5: Commit to layout manager > [self _commitLayoutResult:result lineHeight:finalLineHeight]; > =20 > return result.endedWithNewline ? 3 : 0; > } > ``` >=20 > ### 6. **Replace Anonymous Structs with Named Types** >=20 > ```objc > // BEFORE: anonymous struct in ivars > struct { > BOOL explicit_kern; > float kern; > float baseline_offset; > int superscript; > } attributes; >=20 > // AFTER: named type with documentation > /** > * GSScriptAttributes collects the typographic attributes that affect > * glyph positioning relative to the baseline. > */ > typedef struct { > BOOL hasExplicitKern; // YES if NSKernAttributeName was = specified > CGFloat kernAmount; // Additional spacing between glyphs > CGFloat baselineOffset; // Vertical shift from normal baseline = (positive =3D up) > NSInteger superscriptLevel; // 0 =3D normal, +1 =3D superscript, = -1 =3D subscript > } GSScriptAttributes; >=20 > // Usage in class becomes: > GSScriptAttributes _currentScriptAttributes; > ``` >=20 > ### 7. **Extract Cache Management to Dedicated Class** >=20 > The cache manipulation is complex and scattered: >=20 > ```objc > // New class: GSGlyphCache > @interface GSGlyphCache : NSObject > @property (readonly) NSUInteger baseIndex; > @property (readonly) NSUInteger count; > @property (readonly) BOOL atEnd; >=20 > - (void)repositionToGlyphIndex:(NSUInteger)index=20 > layoutManager:(GSLayoutManager *)lm; > - (void)ensureCount:(NSUInteger)count=20 > layoutManager:(GSLayoutManager *)lm; > - (glyph_cache_t *)glyphAtIndex:(NSUInteger)index; // Relative to = base > - (void)slideWindowToIndex:(NSUInteger)index; > @end > ``` >=20 > ### 8. **Replace C Arrays with NSMutableArray or Smart Pointer** >=20 > The manual `realloc` management is error-prone: >=20 > ```objc > // BEFORE: manual C array management > if (line_frags_num > line_frags_size) { > line_frags_size +=3D 2; > line_frags =3D realloc(line_frags, sizeof(line_frag_t) * = line_frags_size); > } >=20 > // AFTER: use NSMutableArray with wrapper object, or at least: > typedef struct { > line_frag_t *items; > NSUInteger count; > NSUInteger capacity; > } GSLineFragmentArray; >=20 > static inline void GSLineFragmentArrayPush(GSLineFragmentArray *array) = { > if (array->count >=3D array->capacity) { > array->capacity =3D array->capacity ? array->capacity * 2 : 4; > array->items =3D realloc(array->items, sizeof(line_frag_t) * = array->capacity); > } > array->count++; > } > ``` >=20 > ### 9. **Document Return Value Semantics with Enum** >=20 > ```objc > // BEFORE: magic integer returns > -(int) layoutLineNewParagraph: (BOOL)newParagraph // Returns 0, 1, 2, = 3, or 4 >=20 > // AFTER: explicit result type > typedef NS_ENUM(NSInteger, GSLayoutLineResult) { > GSLayoutLineResultContinuing =3D 0, // Line complete, more = glyphs in paragraph > GSLayoutLineResultNoRoom =3D 1, // Text container = exhausted > GSLayoutLineResultEndOfText =3D 2, // All glyphs consumed > GSLayoutLineResultNewParagraph =3D 3, // Line ended with = newline > GSLayoutLineResultNeedParagraphCheck =3D 4 // Ambiguous, caller = must check > }; >=20 > - (GSLayoutLineResult)layoutLineNewParagraph:(BOOL)newParagraph; > ``` >=20 > ### 10. **Flatten Nested Conditionals in Main Loop** >=20 > The main glyph loop has deeply nested conditions. Extract handlers: >=20 > ```objc > - (BOOL)_handleControlGlyph:(glyph_cache_t *)glyph=20 > atPosition:(NSPoint *)position > lineFragment:(line_frag_t *)fragment > newParagraph:(BOOL *)outNewParagraph > { > unichar ch =3D [[curTextStorage string] = characterAtIndex:glyph->char_index]; > =20 > glyph->pos =3D *position; > glyph->size.width =3D 0; > glyph->dont_show =3D YES; > =20 > switch (ch) { > case kNewlineCharacter: > *outNewParagraph =3D YES; > return NO; // Stop processing this line > =20 > case kTabCharacter: > *position =3D [self _positionAfterTabFrom:*position = inFragment:fragment]; > return YES; // Continue to next glyph > =20 > default: > NSDebugLLog(@"GSHorizontalTypesetter", @"Unknown control = %04x", ch); > return YES; > } > } >=20 > - (BOOL)_handleAttachmentGlyph:(glyph_cache_t *)glyph=20 > atPosition:(NSPoint *)position > lineFragment:(line_frag_t *)fragment > lineHeight:(CGFloat *)lineHeight > { > // Extract attachment logic... > // Return YES if fits and metrics updated, NO if needs line break > } > ``` >=20 > ### 11. **Extract Metric Calculation to Dedicated Method** >=20 > ```objc > - (void)_updateLineMetricsForGlyph:(glyph_cache_t *)glyph > currentAscender:(CGFloat *)ascender > currentDescender:(CGFloat *)descender > currentHeight:(CGFloat *)lineHeight > maxLineHeight:(CGFloat)maxHeight > { > NSFont *font =3D glyph->font; > CGFloat glyphAscender =3D [font ascender]; > CGFloat glyphDescender =3D -[font descender]; > =20 > // Apply superscript adjustments > CGFloat yOffset =3D 0; > if (glyph->attributes.superscript) { > yOffset -=3D glyph->attributes.superscript * [font xHeight]; > } > if (glyph->attributes.baseline_offset) { > yOffset +=3D glyph->attributes.baseline_offset; > } > =20 > // Update metrics > *ascender =3D MAX(*ascender, glyphAscender - MIN(yOffset, 0)); > *descender =3D MAX(*descender, glyphDescender + MAX(yOffset, 0)); > =20 > CGFloat newHeight =3D *ascender + *descender; > if (maxHeight > 0) { > newHeight =3D MIN(newHeight, maxHeight); > } > *lineHeight =3D MAX(*lineHeight, newHeight); > } > ``` >=20 > ### 12. **Simplify Soft Invalidation Logic** >=20 > The soft invalidation method is hard to follow. Restructure with early = returns and clear phases: >=20 > ```objc > - (BOOL)_reuseSoftInvalidatedLayout > { > // Quick reject: not at soft invalidation point > if (![self _isAtSoftInvalidationPoint]) return NO; > =20 > // Quick reject: won't fit vertically > NSRect firstRect =3D [self _firstSoftInvalidatedRect]; > if (![self _canFitVertically:firstRect]) return NO; > =20 > // Collect consecutive valid fragments > GSSoftInvalidationBatch *batch =3D [self = _collectSoftInvalidatedFragments]; > if (batch.isEmpty) return NO; > =20 > // Apply shift and commit > [self _applySoftInvalidationBatch:batch]; > curGlyphIndex =3D batch.endGlyphIndex; > return YES; > } > ``` >=20 > --- >=20 > ## Summary Table >=20 > | Refactoring | Benefit | Effort | > |-------------|---------|--------| > | Named constants | Eliminates magic numbers, self-documenting | Low | > | Boolean methods | Clarifies intent of complex conditions | Low | > | Replace goto | Structured flow, easier debugging | Medium | > | State machine enum | Explicit transitions, type safety | Medium | > | Method extraction | Single responsibility, testable units | Medium | > | Named structs | Self-documenting, IDE completion | Low | > | Cache class | Encapsulation, easier optimization | High | > | Array abstraction | Memory safety, bounds checking | Medium | > | Return value enum | Compiler checking, clear semantics | Low | > | Glyph handlers | Flatten nesting, isolate concerns | Medium | > | Metric extraction | Reusable, testable calculations | Low | > | Soft invalidation phases | Linear flow, early exits | Medium | >=20 > These changes maintain performance (critical for text layout) while = significantly improving maintainability. The most impactful would be = **#5 (method extraction)** and **#3 (goto elimination)**, which together = would reduce the cognitive load of understanding the layout algorithm. >=20 >=20 > =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80= =94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80= =94=20 >=20 >=20 > I see you read until here. I know, that was a lot to digest. Now I am = asking: what do you think about this? Does it make sense? Should we do = those refactorings? >=20 >=20 > Kind regards, >=20 > Lars --Apple-Mail=_E574689C-527D-4D09-9378-02F1A9847500 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8 <html><head><meta http-equiv=3D"content-type" content=3D"text/html; = charset=3Dutf-8"></head><body style=3D"overflow-wrap: break-word; = -webkit-nbsp-mode: space; line-break: after-white-space;">For better = readability please have a look at:<div><br></div><div><a = href=3D"https://github.com/gnustep/libs-gui/issues/382#issuecomment-406401= 2492">https://github.com/gnustep/libs-gui/issues/382#issuecomment-40640124= 92</a></div><div><br></div><div>Kind = regards,</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>Lars<br = id=3D"lineBreakAtBeginningOfMessage"><div><br><blockquote = type=3D"cite"><div>Am 15.03.2026 um 03:23 schrieb = [email protected] via Developers list for GNUstep, the = GNU groupware evironment <[email protected]>:</div><br = class=3D"Apple-interchange-newline"><div><meta http-equiv=3D"content-type"= content=3D"text/html; charset=3Dutf-8"><div style=3D"overflow-wrap: = break-word; -webkit-nbsp-mode: space; line-break: = after-white-space;"><div>Hi = GNUstep-Devs,</div><div><br></div><div><br></div><div>especially = Riccardo, Fred and Greg since we were talking about this in the call = today.</div><div><br></div><div><br></div><div>I had the idea to let a = Coding AI, in this case <a = href=3D"https://www.kimi.com/">https://www.kimi.com/</a> analyze = our GSHorizontalTypesetter code and =E2=80=9Eexplain=E2=80=9C it inside = comments added to the code.</div><div><br></div><div>So I asked it the = following = question:</div><div><br></div><div><br></div><div><div>=E2=80=9EHello = KIMI, please analyze the following code and explain it in comments you = add or correct:=E2=80=9C</div><div><br></div><div><br></div><div>and = then gave it both the header and the = implementation.</div><div><br></div></div><div><br></div><div>Here is = the answer (The answer seems to be in Markup-Format so maybe you cut and = paste it into some markup editor for better = readability):</div><div><br></div><div>=E2=80=94=E2=80=94 =E2=80=94=E2=80=94= =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94= =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 </div><div><br></div><div>&nbs= p;I'll analyze this GNUstep GUI Library code and add comprehensive = comments explaining the functionality. Let me go through both the header = and implementation = files.</div><div><br></div><div>```objc</div><div>/*</div><div> = GSHorizontalTypesetter.h</div><div><br></div><div> = Copyright (C) 2002 Free Software Foundation, = Inc.</div><div><br></div><div> Author: Alexander Malmberg = <[email protected]></div><div> Date: = 2002</div><div><br></div><div> This file is part of the = GNUstep GUI Library.</div><div><br></div><div> This library = is free software; you can redistribute it and/or</div><div> = modify it under the terms of the GNU Lesser General = Public</div><div> License as published by the Free Software = Foundation; either</div><div> version 2 of the License, or = (at your option) any later version.</div><div><br></div><div> = This library is distributed in the hope that it will be = useful,</div><div> but WITHOUT ANY WARRANTY; without even = the implied warranty of</div><div> MERCHANTABILITY or = FITNESS FOR A PARTICULAR PURPOSE.<span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> See the GNU</div><div> = Lesser General Public License for more = details.</div><div><br></div><div> You should have received = a copy of the GNU Lesser General Public</div><div> License = along with this library; see the file COPYING.LIB.</div><div> = If not, see <http://www.gnu.org/licenses/ > or write to = the </div><div> Free Software Foundation, 51 Franklin = Street, Fifth Floor, </div><div> Boston, MA 02110-1301, = USA.</div><div>*/</div><div><br></div><div>#ifndef = _GNUstep_H_GSHorizontalTypesetter</div><div>#define = _GNUstep_H_GSHorizontalTypesetter</div><div><br></div><div>#import = <GNUstepGUI/GSTypesetter.h></div><div><br></div><div>@class = NSLock;</div><div>@class GSLayoutManager, NSTextContainer, = NSTextStorage;</div><div>@class NSDictionary;</div><div>@class = NSParagraphStyle, NSFont;</div><div><br></div><div>/*</div><div> * = APPKIT_EXPORT_CLASS marks this class for export from the AppKit = framework.</div><div> * This is a GNUstep-specific macro for = DLL/shared library symbol = visibility.</div><div> */</div><div>APPKIT_EXPORT_CLASS</div><div><br= ></div><div>/*</div><div> * GSHorizontalTypesetter is the concrete = typesetter implementation for</div><div> * horizontal text layout = in GNUstep. It handles the complex task of laying out</div><div> * = glyphs into lines within text containers, supporting various paragraph = styles,</div><div> * alignments, line breaking modes, and text = attributes.</div><div> *</div><div> * This class implements = the core text layout algorithm that converts a stream</div><div> * = of glyphs from the layout manager into positioned line fragments = within</div><div> * the geometric constraints of text = containers.</div><div> */</div><div>@interface = GSHorizontalTypesetter : GSTypesetter</div><div>{</div><div> = /*</div><div> * REENTRANCY LOCK</div><div> * = Since this is typically a shared singleton instance, the lock = ensures</div><div> * thread safety. If the typesetter is = already in use when another layout</div><div> * request = comes in, a temporary instance is created instead.</div><div> = */</div><div> NSLock *lock;</div><div><br></div><div> = /*</div><div> * CURRENT LAYOUT CONTEXT</div><div> = * These ivars track the active text system objects being = processed.</div><div> * They are set at the beginning of = each layout operation and remain</div><div> * constant = throughout that operation.</div><div> */</div><div> = GSLayoutManager *curLayoutManager; // Converts characters = to glyphs, tracks runs</div><div> NSTextContainer = *curTextContainer; // Defines geometric bounds for = text</div><div> NSTextStorage *curTextStorage; = // The attributed string being laid = out</div><div><br></div><div> unsigned int curGlyphIndex; = // Current position in the glyph = stream</div><div> NSPoint curPoint; = // Current layout position (y = advances per line)</div><div><br></div><div> /*</div><div> = * ATTRIBUTE CACHING</div><div> * These ivars cache the = current paragraph style and attributes to avoid</div><div> * = repeated dictionary lookups. The ranges track the validity of each = cache.</div><div> */</div><div> NSParagraphStyle = *curParagraphStyle; // Current paragraph's formatting = rules</div><div> NSRange paragraphRange; = // Character range where curParagraphStyle = is valid</div><div><br></div><div> NSDictionary *curAttributes; = // Current character attributes = dictionary</div><div> NSRange attributeRange; = // Character range where curAttributes is = valid</div><div> </div><div> /*</div><div> = * DECOMPOSED ATTRIBUTES</div><div> * Frequently = accessed attributes are extracted from the dictionary = and</div><div> * stored in this struct for faster access = during the tight layout loop.</div><div> */</div><div> = struct</div><div> {</div><div> BOOL = explicit_kern; // YES = if NSKernAttributeName is present</div><div> float = kern; = // Kerning adjustment value</div><div> = float baseline_offset; // = Vertical offset from baseline</div><div> int = superscript; = // Superscript level (+1, -1, etc.)</div><div> } = attributes;</div><div><br></div><div> NSFont *curFont; = // = Current font for glyph metrics</div><div> NSRange fontRange; = // = Glyph range where curFont is valid</div><div><br></div><div> = /*</div><div> * GLYPH CACHE</div><div> * A = resizable array of glyph_cache_t structures that stores = pre-computed</div><div> * information about glyphs to avoid = repeated calculations. This is the</div><div> * primary = optimization for the layout engine.</div><div> = *</div><div> * cache_base: The glyph index in = the layout manager that corresponds</div><div> * = to cache[0]. All cache indices = are relative to this.</div><div> * cache_size: = Allocated capacity of the cache array (in elements)</div><div> = * cache_length: Number of valid entries currently in the = cache</div><div> * at_end: YES when = we've cached all glyphs in the text storage</div><div> = */</div><div> struct GSHorizontalTypesetter_glyph_cache_s = *cache;</div><div> unsigned int cache_base, cache_size, = cache_length;</div><div> BOOL = at_end;</div><div><br></div><div> /*</div><div> * LINE = FRAGMENT ARRAY</div><div> * During layout of a single line, = this array stores the line fragment</div><div> * rectangles = returned by the text container. A line may consist of</div><div> = * multiple fragments when flowing around exclusions or = shapes.</div><div> *</div><div> * line_frags: = Array of line_frag_t structures</div><div> = * line_frags_num: Number of fragments in current = line</div><div> * line_frags_size: Allocated capacity of the = array</div><div> */</div><div> struct = GSHorizontalTypesetter_line_frag_s *line_frags;</div><div> int = line_frags_num, = line_frags_size;</div><div>}</div><div><br></div><div>/*</div><div> *= Returns the shared thread-local instance of the = typesetter.</div><div> * Each thread maintains its own instance in = the thread dictionary</div><div> * to avoid cross-thread contention = while still allowing = reuse.</div><div> */</div><div>+(GSHorizontalTypesetter *) = sharedInstance;</div><div><br></div><div>@end</div><div><br></div><div>#en= dif</div><div>```</div><div><br></div><div>Now for the implementation = file with detailed = comments:</div><div><br></div><div>```objc</div><div>/*</div><div> = GSHorizontalTypesetter.m</div><div><br></div><div> = Copyright (C) 2002, 2003 Free Software Foundation, = Inc.</div><div><br></div><div> Author: Alexander Malmberg = <[email protected]></div><div> Date: November = 2002 - February 2003</div><div><br></div><div> This file is = part of the GNUstep GUI Library.</div><div><br></div><div> = This library is free software; you can redistribute it = and/or</div><div> modify it under the terms of the GNU = Lesser General Public</div><div> License as published by the = Free Software Foundation; either</div><div> version 2 of the = License, or (at your option) any later = version.</div><div><br></div><div> This library is = distributed in the hope that it will be useful,</div><div> = but WITHOUT ANY WARRANTY; without even the implied warranty = of</div><div> MERCHANTABILITY or FITNESS FOR A PARTICULAR = PURPOSE.<span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> See the GNU</div><div> Lesser General Public License = for more details.</div><div><br></div><div> You should have = received a copy of the GNU Lesser General Public</div><div> = License along with this library; see the file = COPYING.LIB.</div><div> If not, see = <http://www.gnu.org/licenses/> or write to = the </div><div> Free Software Foundation, 51 Franklin = Street, Fifth Floor, </div><div> Boston, MA 02110-1301, = USA.</div><div>*/</div><div><br></div><div><br></div><div>#include = <math.h></div><div><br></div><div>#import = <Foundation/NSDebug.h></div><div>#import = <Foundation/NSException.h></div><div>#import = <Foundation/NSGeometry.h></div><div>#import = <Foundation/NSLock.h></div><div>#import = <Foundation/NSThread.h></div><div>#import = <Foundation/NSValue.h></div><div><br></div><div>#import = "AppKit/NSAttributedString.h"</div><div>#import = "AppKit/NSParagraphStyle.h"</div><div>#import = "AppKit/NSTextAttachment.h"</div><div>#import = "AppKit/NSTextContainer.h"</div><div>#import = "AppKit/NSTextStorage.h"</div><div>#import = "GNUstepGUI/GSLayoutManager.h"</div><div>#import = "GNUstepGUI/GSHorizontalTypesetter.h"</div><div><br></div><div><br></div><= div>/*</div><div> * DESIGN NOTE: Unless the user creates extra = instances, there will only be one</div><div> * instance of = GSHorizontalTypesetter per thread for all text = typesetting.</div><div> * This allows aggressive caching without = memory concerns since the cache is</div><div> * reused across = multiple layout operations rather than being recreated each = time.</div><div> */</div><div><br></div><div><br></div><div>@implemen= tation = GSHorizontalTypesetter</div><div><br></div><div>/*</div><div> * = Standard initializer. Creates the reentrancy lock used to = detect</div><div> * nested layout calls on the same = instance.</div><div> */</div><div>- = init</div><div>{</div><div> if (!(self =3D [super init])) return = nil;</div><div> lock =3D [[NSLock alloc] init];</div><div> = return self;</div><div>}</div><div><br></div><div>/*</div><div> * = Cleanup: release the lock and free all cached memory = buffers.</div><div> */</div><div>-(void) = dealloc</div><div>{</div><div> if (cache)</div><div> = {</div><div> free(cache);</div><div> = cache =3D NULL;</div><div> }</div><div> if = (line_frags)</div><div> {</div><div> = free(line_frags);</div><div> line_frags =3D = NULL;</div><div> }</div><div> = DESTROY(lock);</div><div> [super = dealloc];</div><div>}</div><div><br></div><div>/*</div><div> * = Thread-local singleton accessor. Each thread gets its own = instance</div><div> * stored in the thread dictionary under a = unique key. This provides</div><div> * instance reuse without = requiring cross-thread = synchronization.</div><div> */</div><div>+(GSHorizontalTypesetter = *) sharedInstance</div><div>{</div><div> NSMutableDictionary = *threadDict =3D </div><div> [[NSThread currentThread] = threadDictionary];</div><div> GSHorizontalTypesetter *shared = =3D </div><div> [threadDict objectForKey: = @"sharedHorizontalTypesetter"];</div><div><br></div><div> if = (!shared)</div><div> {</div><div> = shared =3D [[self alloc] init];</div><div> = [threadDict setObject: shared</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> forKey: = @"sharedHorizontalTypesetter"];</div><div> = RELEASE(shared);</div><div> = }</div><div><br></div><div> return = shared;</div><div>}</div><div><br></div><div>/*</div><div> * CACHE = MANAGEMENT CONSTANTS</div><div> * CACHE_INITIAL: Starting size for = glyph cache (192 glyphs)</div><div> * CACHE_STEP: = Increment size when cache needs to = grow</div><div> */</div><div>#define CACHE_INITIAL = 192</div><div>#define CACHE_STEP = 192</div><div><br></div><div><br></div><div>/*</div><div> * GLYPH = CACHE ENTRY</div><div> * Stores all information needed to position = a single glyph.</div><div> * Split into two = phases:</div><div> * 1. Filled during caching = (_cacheGlyphs:)</div><div> * 2. Filled during layout = (layoutLineNewParagraph:)</div><div> */</div><div>struct = GSHorizontalTypesetter_glyph_cache_s</div><div>{</div><div> /* = PHASE 1: Caching - extracted from layout manager and attributes = */</div><div> NSGlyph g; = // The glyph index (NSGlyph is an integer = type)</div><div> unsigned int char_index; // = Corresponding character index in text = storage</div><div><br></div><div> NSFont *font; = // Font to use for this = glyph</div><div> struct</div><div> {</div><div> = BOOL explicit_kern; // Whether to = apply explicit kerning</div><div> float kern; = // Kerning value from = attributes</div><div> float baseline_offset; = // Vertical offset from baseline</div><div> = int superscript; // Superscript = level</div><div> } = attributes;</div><div><br></div><div> /* PHASE 2: Layout - = computed during line layout */</div><div> BOOL nominal; = // YES if glyph has = standard spacing (no adjustments)</div><div> NSPoint pos; = // Position = relative to the line's baseline</div><div> NSSize size; = // Advancement = width; height used only for attachments</div><div> BOOL dont_show, = // YES for whitespace = glyphs that shouldn't render</div><div> = outside_line_frag; // YES for glyphs that = overflow the fragment (clipping mode)</div><div>};</div><div>typedef = struct GSHorizontalTypesetter_glyph_cache_s = glyph_cache_t;</div><div><br></div><div>/*</div><div> * Clears all = cached attribute and glyph information.</div><div> * Called at the = start of each layout operation to ensure we don't</div><div> * use = stale data from previous layouts. Note that we don't free = the</div><div> * cache memory, just reset the valid length to = zero.</div><div> *</div><div> * TODO: If we could detect = whether the layout manager has been modified</div><div> * since our = last layout, we could avoid clearing the cache = unnecessarily.</div><div> */</div><div>-(void) = _cacheClear</div><div>{</div><div> cache_length =3D = 0;</div><div><br></div><div> curParagraphStyle =3D = nil;</div><div> paragraphRange =3D NSMakeRange(0, = 0);</div><div> curAttributes =3D nil;</div><div> = attributeRange =3D NSMakeRange(0, 0);</div><div> curFont =3D = nil;</div><div> fontRange =3D NSMakeRange(0, = 0);</div><div>}</div><div><br></div><div>/*</div><div> * Caches the = attributes for the character at the given index.</div><div> * Uses = range checking to avoid redundant dictionary lookups - if = the</div><div> * requested index is within attributeRange, we = already have the data.</div><div> * Extracts kern, baseline offset, = and superscript into the attributes = struct.</div><div> */</div><div>-(void) _cacheAttributes: (unsigned = int)char_index</div><div>{</div><div> NSNumber = *n;</div><div><br></div><div> if (NSLocationInRange(char_index, = attributeRange))</div><div> {</div><div> = return;</div><div> = }</div><div> </div><div> curAttributes =3D = [curTextStorage attributesAtIndex: char_index</div><div> = = effectiveRange: = &attributeRange];</div><div><br></div><div> /* Extract kerning = attribute */</div><div> n =3D [curAttributes objectForKey: = NSKernAttributeName];</div><div> if (!n)</div><div> = attributes.explicit_kern =3D NO;</div><div> else</div><div> = {</div><div> attributes.explicit_kern =3D = YES;</div><div> attributes.kern =3D [n = floatValue];</div><div> }</div><div><br></div><div> = /* Extract baseline offset (positive =3D up, negative =3D down in = standard Cocoa coords) */</div><div> n =3D [curAttributes = objectForKey: NSBaselineOffsetAttributeName];</div><div> if = (n)</div><div> attributes.baseline_offset =3D [n = floatValue];</div><div> else</div><div> = attributes.baseline_offset =3D 0.0;</div><div><br></div><div> /* = Extract superscript level */</div><div> n =3D [curAttributes = objectForKey: NSSuperscriptAttributeName];</div><div> if = (n)</div><div> attributes.superscript =3D [n = intValue];</div><div> else</div><div> = attributes.superscript =3D = 0;</div><div>}</div><div><br></div><div>/*</div><div> * Repositions = the cache window to start at the specified glyph = index.</div><div> * </div><div> * If the requested glyph = is already within our cache window, we shift the</div><div> * = existing data to the front (memmove) to make room for new glyphs = ahead.</div><div> * </div><div> * If it's outside our = cache, we reset completely and fetch new paragraph</div><div> * = style, attributes, and font information from the layout = manager.</div><div> */</div><div>-(void) _cacheMoveTo: (unsigned = int)glyph</div><div>{</div><div> BOOL = valid;</div><div><br></div><div> /* Case 1: Requested glyph is = already in our cache window */</div><div> if (cache_base <=3D = glyph && cache_base + cache_length > glyph)</div><div> = {</div><div> int delta =3D glyph - = cache_base;</div><div> cache_length -=3D = delta;</div><div> memmove(cache, &cache[delta], = sizeof(glyph_cache_t) * cache_length);</div><div> = cache_base =3D glyph;</div><div> = return;</div><div> }</div><div><br></div><div> /* = Case 2: Complete reset - new location in text stream */</div><div> = cache_base =3D glyph;</div><div> cache_length =3D = 0;</div><div><br></div><div> [curLayoutManager glyphAtIndex: = glyph</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> isValidIndex: = &valid];</div><div><br></div><div> if (valid)</div><div> = {</div><div> unsigned int = i;</div><div><br></div><div> at_end =3D = NO;</div><div> i =3D [curLayoutManager = characterIndexForGlyphAtIndex: glyph];</div><div> = [self _cacheAttributes: i];</div><div><br></div><div> = /* Fetch paragraph style and its valid range */</div><div> = paragraphRange =3D NSMakeRange(i, [curTextStorage length] = - i);</div><div> curParagraphStyle =3D = [curTextStorage attribute: NSParagraphStyleAttributeName</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>atIndex: i</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> = </span>longestEffectiveRange: &paragraphRange</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>inRange: paragraphRange];</div><div> = if (curParagraphStyle =3D=3D nil)</div><div> = {</div><div> curParagraphStyle =3D = [NSParagraphStyle defaultParagraphStyle];</div><div> = }</div><div><br></div><div> /* Fetch initial = font and its valid range */</div><div> curFont =3D = [curLayoutManager effectiveFontForGlyphAtIndex: glyph</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>range: &fontRange];</div><div> = }</div><div> else</div><div> {</div><div> = at_end =3D YES; // No valid glyph at this index - = we're at end of text</div><div> = }</div><div>}</div><div><br></div><div>/*</div><div> * Fills the = glyph cache up to new_length entries.</div><div> * Grows the cache = buffer if necessary using realloc.</div><div> * For each new glyph, = fetches:</div><div> * - Glyph index and character index from = layout manager</div><div> * - Attributes (if character index = moved past attributeRange)</div><div> * - Font (if glyph = moved past fontRange)</div><div> * - Advancement size from = layout manager</div><div> *</div><div> * Stops early if we hit = invalid glyphs or paragraph = boundaries.</div><div> */</div><div>-(void) _cacheGlyphs: (unsigned = int)new_length</div><div>{</div><div> glyph_cache_t = *g;</div><div> BOOL valid;</div><div><br></div><div> /* Grow = buffer if needed */</div><div> if (cache_size < = new_length)</div><div> {</div><div> = cache_size =3D new_length;</div><div> cache =3D = realloc(cache, sizeof(glyph_cache_t) * cache_size);</div><div> = }</div><div><br></div><div> /* Fill cache entries from = current length up to new_length */</div><div> for (g =3D = &cache[cache_length]; cache_length < new_length; cache_length++, = g++)</div><div> {</div><div> g->g =3D = [curLayoutManager glyphAtIndex: cache_base + = cache_length</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = isValidIndex: &valid];</div><div> if = (!valid)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>{</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = at_end =3D YES;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> break;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>}</div><div> g->char_index =3D = [curLayoutManager characterIndexForGlyphAtIndex: cache_base + = cache_length];</div><div> </div><div> = /* Stop if we crossed paragraph boundary = */</div><div> if (g->char_index >=3D = paragraphRange.location + paragraphRange.length)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>{</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> at_end =3D = YES;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> break;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>}</div><div><br></div><div> = /* Update attribute cache if needed */</div><div> = if (g->char_index >=3D attributeRange.location + = attributeRange.length)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>{</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = [self _cacheAttributes: g->char_index];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>}</div><div><br></div><div> /* Copy = decomposed attributes into cache entry */</div><div> = g->attributes.explicit_kern =3D = attributes.explicit_kern;</div><div> = g->attributes.kern =3D attributes.kern;</div><div> = g->attributes.baseline_offset =3D = attributes.baseline_offset;</div><div> = g->attributes.superscript =3D = attributes.superscript;</div><div><br></div><div> /* = Update font cache if needed */</div><div> if = (cache_base + cache_length >=3D fontRange.location + = fontRange.length)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>{</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = curFont =3D [curLayoutManager effectiveFontForGlyphAtIndex: = cache_base + cache_length</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = range: &fontRange];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>}</div><div> = g->font =3D curFont;</div><div><br></div><div> /* = Initialize layout fields */</div><div> = g->dont_show =3D NO;</div><div> = g->outside_line_frag =3D NO;</div><div> = g->nominal =3D YES;</div><div><br></div><div> /* = Get glyph advancement from layout manager */</div><div> = // FIXME: This assumes the layout manager implements this GNUstep = extension</div><div> g->size =3D = [curLayoutManager advancementForGlyphAtIndex: cache_base + = cache_length];</div><div> = }</div><div>}</div><div><br></div><div><br></div><div>/*</div><div> *= WORD WRAPPING SUPPORT</div><div> * Searches backward from glyph gi = to find a suitable word break point.</div><div> * Returns the glyph = index of the first glyph on the next = line.</div><div> *</div><div> * Breaking = rules:</div><div> * - Control glyphs (newlines, tabs) are = always break points</div><div> * - Whitespace characters = (space, newline, CR, tab) mark breaks and are hidden</div><div> * = - CJK characters (0x2FF0-0x9FFF) can break before (each CJK char = is its own word)</div><div> *</div><div> * The returned index = is always >=3D cache_base and <=3D = gi.</div><div> */</div><div>-(unsigned int) = breakLineByWordWrappingBefore: (unsigned = int)gi</div><div>{</div><div> glyph_cache_t *g;</div><div> = unichar ch;</div><div> NSString *str =3D [curTextStorage = string];</div><div><br></div><div> gi -=3D cache_base; // = Convert to cache-relative index</div><div> g =3D cache + = gi;</div><div><br></div><div> while (gi > 0)</div><div> = {</div><div> if (g->g =3D=3D = NSControlGlyph)</div><div> return gi + = cache_base; // Always break at control glyphs</div><div> = </div><div> ch =3D [str = characterAtIndex: g->char_index];</div><div> = </div><div> /* Check for whitespace = characters that allow breaking */</div><div> if (ch = =3D=3D 0x20 || // space</div><div> ch = =3D=3D 0x0a || // new line</div><div> = ch =3D=3D 0x0d || // carriage return</div><div> = ch =3D=3D 0x09) // horiz. tab</div><div> = {</div><div> = g->dont_show =3D YES; // Hide the whitespace character = itself</div><div> if (gi > = 0)</div><div> = {</div><div> g->pos =3D= g[-1].pos;</div><div> = g->pos.x +=3D g[-1].size.width;</div><div> = }</div><div> = else</div><div> g->pos =3D = NSMakePoint(0, 0);</div><div> = g->size.width =3D 0;</div><div> = return gi + 1 + cache_base; // Break after the = whitespace</div><div> }</div><div> = </div><div> /* CJK characters: = treat each as a word boundary */</div><div> else if = ((ch > 0x2ff0) && (ch < 0x9fff))</div><div> = {</div><div> = g->dont_show =3D NO;</div><div> = if (gi > 0)</div><div> = {</div><div> = g->pos =3D g[-1].pos;</div><div> = g->pos.x +=3D = g[-1].size.width;</div><div> = }</div><div> = else</div><div> = g->pos =3D NSMakePoint(0,0);</div><div> = return gi + cache_base; // Break before this = CJK character</div><div> } = </div><div> </div><div> = gi--;</div><div> g--;</div><div> = }</div><div> return gi + cache_base; // Reached start = of cache - break = here</div><div>}</div><div><br></div><div><br></div><div>/*</div><div>&nbs= p;* LINE FRAGMENT STRUCTURE</div><div> * Tracks the geometry and = content of a single line fragment (a rectangular</div><div> * = region within a line where glyphs are = placed).</div><div> */</div><div>struct = GSHorizontalTypesetter_line_frag_s</div><div>{</div><div> NSRect = rect; // The fragment = rectangle in container coordinates</div><div> CGFloat last_used; = // X coordinate where glyph content = ends</div><div> unsigned int lastGlyphIndex; // Index (relative to = cache_base) of last glyph + 1</div><div>};</div><div>typedef struct = GSHorizontalTypesetter_line_frag_s = line_frag_t;</div><div><br></div><div>/*</div><div> * Apple's = maximum meaningful width for text containers.</div><div> * Widths = beyond this are treated as infinite and ignored for layout = purposes.</div><div> */</div><div>#define LARGE_SIZE = 1e7</div><div><br></div><div>/*</div><div> * FULL = JUSTIFICATION</div><div> * Distributes extra space evenly across = space characters in the line.</div><div> * Only operates if the = line width is reasonable (not = LARGE_SIZE).</div><div> *</div><div> * = Algorithm:</div><div> * 1. Count space characters in the = line</div><div> * 2. Calculate extra space per space: = (rect.width - last_used) / num_spaces</div><div> * 3. Shift = all glyphs after each space by accumulating delta</div><div> * = 4. Mark glyphs after spaces as non-nominal (they have adjusted = positions)</div><div> */</div><div>-(void) fullJustifyLine: = (line_frag_t *)lf : (int)num_line_frags</div><div>{</div><div> = unsigned int i, start;</div><div> CGFloat extra_space, = delta;</div><div> unsigned int num_spaces;</div><div> = NSString *str =3D [curTextStorage string];</div><div> = glyph_cache_t *g;</div><div> unichar = ch;</div><div><br></div><div> if (lf->rect.size.width >=3D = LARGE_SIZE)</div><div> {</div><div> = return;</div><div> }</div><div><br></div><div> for = (start =3D 0; num_line_frags; num_line_frags--, lf++)</div><div> = {</div><div> num_spaces =3D = 0;</div><div> for (i =3D start, g =3D cache + i; i = < lf->lastGlyphIndex; i++, g++)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>{</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if = (g->dont_show)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = continue;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> ch =3D [str = characterAtIndex: g->char_index];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> if = (ch =3D=3D 0x20)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = num_spaces++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>}</div><div> = if (!num_spaces)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> = </span>continue;</div><div><br></div><div> = extra_space =3D lf->rect.size.width - = lf->last_used;</div><div> extra_space /=3D = num_spaces;</div><div> delta =3D 0;</div><div> = for (i =3D start, g =3D cache + i; i < = lf->lastGlyphIndex; i++, g++)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>{</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = g->pos.x +=3D delta;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if (!g->dont_show = && [str characterAtIndex: g->char_index] =3D=3D = 0x20)</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if (i < = lf->lastGlyphIndex)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>g[1].nominal =3D NO; = // Next glyph has non-standard position</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = delta +=3D extra_space;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>}</div><div> = start =3D lf->lastGlyphIndex;</div><div> = lf->last_used =3D lf->rect.size.width;</div><div> = }</div><div>}</div><div><br></div><div>/*</div><div> * RIGHT = ALIGNMENT</div><div> * Shifts all glyphs right by the difference = between fragment width and used = width.</div><div> */</div><div>-(void) rightAlignLine: (line_frag_t = *)lf : (int)num_line_frags</div><div>{</div><div> unsigned int = i;</div><div> CGFloat delta;</div><div> glyph_cache_t = *g;</div><div><br></div><div> if (lf->rect.size.width >=3D = LARGE_SIZE)</div><div> {</div><div> = return;</div><div> }</div><div><br></div><div> for (i = =3D 0, g =3D cache; num_line_frags; num_line_frags--, = lf++)</div><div> {</div><div> delta =3D = lf->rect.size.width - lf->last_used;</div><div> = for (; i < lf->lastGlyphIndex; i++, g++)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>g->pos.x +=3D delta;</div><div> = lf->last_used +=3D delta;</div><div> = }</div><div>}</div><div><br></div><div>/*</div><div> * CENTER = ALIGNMENT</div><div> * Shifts all glyphs right by half the = remaining space.</div><div> */</div><div>-(void) centerAlignLine: = (line_frag_t *)lf : (int)num_line_frags</div><div>{</div><div> = unsigned int i;</div><div> CGFloat delta;</div><div> = glyph_cache_t *g;</div><div><br></div><div> if = (lf->rect.size.width >=3D LARGE_SIZE)</div><div> = {</div><div> return;</div><div> = }</div><div><br></div><div> for (i =3D 0, g =3D cache; = num_line_frags; num_line_frags--, lf++)</div><div> = {</div><div> delta =3D (lf->rect.size.width - = lf->last_used) / 2.0;</div><div> for (; i < = lf->lastGlyphIndex; i++, g++)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>g->pos.x +=3D = delta;</div><div> lf->last_used +=3D = delta;</div><div> = }</div><div>}</div><div><br></div><div><br></div><div>/*</div><div> *= SOFT INVALIDATION OPTIMIZATION</div><div> * Attempts to reuse = layout information from previous layout passes that</div><div> * = were "soft invalidated" (marked as potentially changed but not = definitely wrong).</div><div> *</div><div> * This handles the = common case of simple text edits where line fragments</div><div> * = just need to be shifted vertically without changing their horizontal = layout.</div><div> *</div><div> * Returns YES if = soft-invalidated layout was successfully = reused.</div><div> */</div><div>-(BOOL) = _reuseSoftInvalidatedLayout</div><div>{</div><div> NSRect r0, = r;</div><div> NSSize shift;</div><div> int = i;</div><div> unsigned int g, g2, first;</div><div> CGFloat = container_height;</div><div> </div><div> /* Get first = soft-invalidated rect starting at current glyph */</div><div> r0 =3D= [curLayoutManager _softInvalidateLineFragRect: 0</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> firstGlyph: &first</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> nextGlyph: &g</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> inTextContainer: = curTextContainer];</div><div><br></div><div> container_height =3D = [curTextContainer containerSize].height;</div><div> if = (!(curPoint.y + r0.size.height <=3D = container_height))</div><div> return NO; // Won't fit = at current Y position</div><div><br></div><div> = /*</div><div> * We can shift the rects vertically to fit. = Collect all consecutive</div><div> * soft-invalidated line = fragments and apply the same shift.</div><div> = */</div><div> shift.width =3D 0;</div><div> = shift.height =3D curPoint.y - r0.origin.y;</div><div> i =3D = 1;</div><div> curPoint.y =3D NSMaxY(r0) + = shift.height;</div><div> </div><div> for (; 1; = i++)</div><div> {</div><div> r =3D = [curLayoutManager _softInvalidateLineFragRect: i</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> firstGlyph: &first</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> nextGlyph: &g2</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>inTextContainer: = curTextContainer];</div><div><br></div><div> /* Gap = in soft-invalidated info - must fill in before continuing = */</div><div> if (first !=3D g)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>{</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> break;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>}</div><div><br></div><div> if = (NSIsEmptyRect(r) || NSMaxY(r) + shift.height > = container_height)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> = </span>break;</div><div><br></div><div> g =3D = g2;</div><div> curPoint.y =3D NSMaxY(r) + = shift.height;</div><div> }</div><div><br></div><div> = /* Commit the reused layout to the layout manager */</div><div> = [curLayoutManager _softInvalidateUseLineFrags: i</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> withShift: shift</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>inTextContainer: = curTextContainer];</div><div><br></div><div> curGlyphIndex =3D = g;</div><div> return = YES;</div><div>}</div><div><br></div><div><br></div><div>/*</div><div>&nbs= p;* Calculates the proposed rectangle for a new line = fragment.</div><div> * </div><div> * newParagraph: YES = for first line of paragraph (uses = firstLineHeadIndent),</div><div> * = NO for subsequent lines (uses = headIndent)</div><div> * line_height: The height to request = from the text container</div><div> *</div><div> * Returns: = Proposed rectangle in container = coordinates</div><div> */</div><div>- (NSRect)_getProposedRectFor: = (BOOL)newParagraph</div><div> = withLineHeight: (CGFloat) = line_height </div><div>{</div><div> CGFloat = hindent;</div><div> CGFloat tindent =3D [curParagraphStyle = tailIndent];</div><div><br></div><div> if = (newParagraph)</div><div> hindent =3D [curParagraphStyle = firstLineHeadIndent];</div><div> else</div><div> = hindent =3D [curParagraphStyle = headIndent];</div><div><br></div><div> /* Negative tail indent is = treated as inset from right edge */</div><div> if (tindent <=3D = 0.0)</div><div> { </div><div> = NSSize size;</div><div><br></div><div> size =3D = [curTextContainer containerSize];</div><div> tindent = =3D size.width + tindent;</div><div> = }</div><div><br></div><div> return = NSMakeRect(hindent,</div><div> = curPoint.y,</div><div> = tindent - = hindent,</div><div> = line_height + [curParagraphStyle = lineSpacing]);</div><div>}</div><div><br></div><div>/*</div><div> * = Creates the "extra line fragment" used when text ends with a = newline.</div><div> * This provides a place for the insertion point = (caret) after the last newline.</div><div> * The fragment has full = line height but minimal width (1 unit).</div><div> */</div><div>- = (void) _addExtraLineFragment</div><div>{</div><div> NSRect r, r2, = remain;</div><div> CGFloat = line_height;</div><div><br></div><div> /*</div><div> * = We need the attributes from the last character to match the = style.</div><div> * _cacheMoveTo: ensures curParagraphStyle = and curFont are set.</div><div> */</div><div> if = (curGlyphIndex)</div><div> {</div><div> = [self _cacheMoveTo: curGlyphIndex - 1];</div><div> = }</div><div> else</div><div> {</div><div> = /* No glyphs yet - use typing attributes (default style = for new text) */</div><div> NSDictionary = *typingAttributes =3D [curLayoutManager = typingAttributes];</div><div> curParagraphStyle =3D = [typingAttributes</div><div> = objectForKey: = NSParagraphStyleAttributeName];</div><div> if = (curParagraphStyle =3D=3D nil)</div><div> = {</div><div> curParagraphStyle =3D = [NSParagraphStyle defaultParagraphStyle];</div><div> = }</div><div> curFont =3D [typingAttributes = objectForKey: NSFontAttributeName];</div><div> = }</div><div><br></div><div> /* Determine line height from font or = use default */</div><div> if (curFont)</div><div> = {</div><div> line_height =3D [curFont = defaultLineHeightForFont];</div><div> }</div><div> = else</div><div> {</div><div> = line_height =3D 15.0;</div><div> = }</div><div><br></div><div> r =3D [self _getProposedRectFor: = YES</div><div> = withLineHeight: line_height];</div><div> r =3D = [curTextContainer lineFragmentRectForProposedRect: r</div><div> = = = sweepDirection: NSLineSweepRight</div><div> = = movementDirection: = NSLineMovesDown</div><div> = = remainingRect: = &remain];</div><div> </div><div> if = (!NSIsEmptyRect(r))</div><div> {</div><div> = r2 =3D r;</div><div> r2.size.width =3D 1; = // Minimal width for caret positioning</div><div> = [curLayoutManager setExtraLineFragmentRect: r</div><div> = = usedRect: = r2</div><div> = = textContainer: curTextContainer];</div><div> = }</div><div>}</div><div><br></div><div>/*</div><div> * Helper for = line height calculations.</div><div> * Updates *lineHeight to = newHeight if newHeight is larger (and within max).</div><div> * = Returns YES if the line height was = updated.</div><div> */</div><div>static inline BOOL = wantNewLineHeight(CGFloat h, CGFloat *lineHeight, CGFloat = maxLineHeight)</div><div>{</div><div> CGFloat newHeight =3D = h;</div><div><br></div><div> if (maxLineHeight > 0 && = newHeight > maxLineHeight)</div><div> {</div><div> = newHeight =3D maxLineHeight;</div><div> = }</div><div><br></div><div> if (newHeight > = *lineHeight)</div><div> {</div><div> = *lineHeight =3D newHeight;</div><div> return = YES;</div><div> }</div><div> return = NO;</div><div>}</div><div><br></div><div>/*</div><div> * CORE = LAYOUT METHOD</div><div> * Lays out a single line of text, handling = all complexity of glyph positioning,</div><div> * line breaking, = attachments, and alignment.</div><div> *</div><div> * = newParagraph: YES if this is the first line of a = paragraph</div><div> *</div><div> * Return = values:</div><div> * 0 - Line completed normally, next glyph = continues this paragraph</div><div> * 1 - No room in text = container (line fragments exhausted)</div><div> * 2 - All = glyphs laid out (end of text)</div><div> * 3 - Line ended = with newline, next glyph starts new paragraph</div><div> * 4 = - Ambiguous state (must test before next call - from soft = invalidation)</div><div> */</div><div>-(int) = layoutLineNewParagraph: (BOOL)newParagraph</div><div>{</div><div> = NSRect rect;</div><div><br></div><div> /* LINE METRICS VARIABLES = */</div><div> CGFloat line_height; // Current line = height (ascender + descender)</div><div> CGFloat max_line_height; = // Maximum allowed (from paragraph style, 0 =3D = unlimited)</div><div> CGFloat baseline; = // Distance from top of line to baseline</div><div> CGFloat = ascender; // Space needed above baseline (max = of all glyphs)</div><div> CGFloat descender; = // Space needed below baseline (max of all = glyphs)</div><div><br></div><div> /*</div><div> * SOFT = INVALIDATION CHECK</div><div> * Try to reuse previous layout = if available and appropriate.</div><div> */</div><div> = if ([curTextContainer isSimpleRectangularTextContainer] = &&</div><div> [curLayoutManager = _softInvalidateFirstGlyphInTextContainer: curTextContainer] =3D=3D = curGlyphIndex)</div><div> {</div><div> = if ([self _reuseSoftInvalidatedLayout])</div><div> = return 4;</div><div> = }</div><div><br></div><div> /* Initialize cache at current glyph = position */</div><div> [self _cacheMoveTo: = curGlyphIndex];</div><div> if (!cache_length)</div><div> = [self _cacheGlyphs: CACHE_INITIAL];</div><div> if = (!cache_length && at_end)</div><div> = {</div><div> /* No more glyphs to lay out = */</div><div> if (newParagraph)</div><div> = {</div><div> = [self _addExtraLineFragment]; // Text ended with = newline</div><div> }</div><div> = return 2;</div><div> = }</div><div><br></div><div> /* INITIALIZE LINE METRICS from first = glyph's font */</div><div> {</div><div> CGFloat min =3D= [curParagraphStyle minimumLineHeight];</div><div> = max_line_height =3D [curParagraphStyle = maximumLineHeight];</div><div><br></div><div> /* Sanity: = max must be >=3D min if both are specified */</div><div> = if (max_line_height > 0 && max_line_height < = min)</div><div> max_line_height =3D = min;</div><div><br></div><div> line_height =3D = [cache->font defaultLineHeightForFont];</div><div> = ascender =3D [cache->font ascender];</div><div> = descender =3D -[cache->font = descender];</div><div><br></div><div> if (line_height < = min)</div><div> line_height =3D = min;</div><div><br></div><div> if (max_line_height > 0 = && line_height > max_line_height)</div><div> = line_height =3D max_line_height;</div><div> = }</div><div><br></div><div> /*</div><div> * LINE = FRAGMENT ACQUISITION</div><div> * Get rectangles from text = container until we have room for at least</div><div> * one = glyph. If line height increases due to large glyphs, we = restart</div><div> * this process since the rectangles might = change.</div><div> */</div><div>restart: = ;</div><div><br></div><div> do</div><div> = {</div><div> NSRect = remain;</div><div><br></div><div> remain =3D [self = _getProposedRectFor: newParagraph</div><div> = = withLineHeight: line_height];</div><div><br></div><div> = /*</div><div> * Build list of line = fragment rects for this line.</div><div> * A = line may have multiple fragments when flowing around = shapes.</div><div> * TODO: This builds all = rects in advance which might be inefficient</div><div> = * for containers with many exclusions (e.g., narrow = columns).</div><div> */</div><div> = line_frags_num =3D 0;</div><div> rect = =3D [curTextContainer lineFragmentRectForProposedRect: = remain</div><div> = = sweepDirection: = NSLineSweepRight</div><div> = = movementDirection: = NSLineMovesDown</div><div> = = = remainingRect: &remain];</div><div> while = (!NSIsEmptyRect(rect))</div><div> = {</div><div> = line_frags_num++;</div><div> if = (line_frags_num > line_frags_size)</div><div> = {</div><div> = line_frags_size +=3D 2;</div><div> = line_frags =3D realloc(line_frags, = sizeof(line_frag_t) * line_frags_size);</div><div> = }</div><div> = line_frags[line_frags_num - 1].rect =3D = rect;</div><div><br></div><div> rect =3D= [curTextContainer lineFragmentRectForProposedRect: = remain</div><div> = = sweepDirection: = NSLineSweepRight</div><div> = = = movementDirection: NSLineDoesntMove</div><div> = = = remainingRect: &remain];</div><div> = }</div><div> if (line_frags_num = =3D=3D 0)</div><div> {</div><div> = /* No fragments available - container might = be too small */</div><div> if = (curPoint.y =3D=3D 0.0 &&</div><div> = line_height > [curTextContainer = containerSize].height &&</div><div> = [curTextContainer containerSize].height > = 0.0)</div><div> = {</div><div> /* = Emergency: shrink line height to fit at least one line = */</div><div> = line_height =3D [curTextContainer = containerSize].height;</div><div> = max_line_height =3D line_height;</div><div> = continue;</div><div> = }</div><div> = return 1; // No room in container</div><div> = }</div><div> }</div><div> while = (line_frags_num =3D=3D 0);</div><div><br></div><div> = /*</div><div> * MAIN GLYPH LAYOUT LOOP</div><div> = * Positions each glyph in the line fragments, = handling:</div><div> * - Font changes and metric = updates</div><div> * - Kerning and baseline = adjustments</div><div> * - Superscript/subscript = positioning</div><div> * - Tab stops</div><div> = * - Text attachments (images, etc.)</div><div> = * - Line breaking when fragments fill up</div><div> = */</div><div> {</div><div> unsigned int i =3D = 0;</div><div> glyph_cache_t = *g;</div><div><br></div><div> NSPoint p; = // Current glyph position (relative to = line fragment)</div><div> </div><div> = NSFont *f =3D cache->font;</div><div><br></div><div> = CGFloat f_ascender =3D [f ascender];</div><div> CGFloat = f_descender =3D -[f descender];</div><div><br></div><div> = NSGlyph last_glyph =3D NSNullGlyph; // For kerning = calculations</div><div> NSPoint = last_p;</div><div><br></div><div> unsigned int = firstGlyphIndex; // First glyph in current line = fragment</div><div> line_frag_t *lf =3D line_frags; = // Current line fragment</div><div> int lfi =3D = 0; = // Line fragment index</div><div><br></div><div> = BOOL prev_had_non_nominal_width; // Track if previous glyph had = custom spacing</div><div><br></div><div><br></div><div> = last_p =3D p =3D NSMakePoint(0, 0);</div><div><br></div><div> = g =3D cache;</div><div> firstGlyphIndex =3D = 0;</div><div> prev_had_non_nominal_width =3D = NO;</div><div><br></div><div> while (1)</div><div> = {</div><div> BOOL = doesGlyphFitInLine =3D YES;</div><div><br></div><div> = /* Ensure we have cached glyphs to process = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>if (i >=3D cache_length)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if = (at_end)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>newParagraph =3D = NO;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>break; // End of text</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> [self _cacheGlyphs: = cache_length + CACHE_STEP];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if (i >=3D = cache_length)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>newParagraph =3D = NO;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>break; // No more glyphs available</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> g =3D cache + = i;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> }</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>/*</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> * FONT = CHANGE HANDLING</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> * Update ascender/descender = tracking when font changes.</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> * Reset last_glyph to disable = kerning across font boundaries.</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span>if = (g->font !=3D f)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = f =3D g->font;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> f_ascender =3D [f = ascender];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> f_descender =3D -[f = descender];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> last_glyph =3D = NSNullGlyph;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>/* Apply explicit kerning if = specified in attributes */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>g->nominal =3D = !prev_had_non_nominal_width;</div><div><br></div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span>if = (g->attributes.explicit_kern &&</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = g->attributes.kern !=3D 0)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> p.x +=3D = g->attributes.kern;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> g->nominal =3D = NO;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> }</div><div><br></div><div> /* = Check if glyph fits in current line fragment width */</div><div> = doesGlyphFitInLine =3D !((i > firstGlyphIndex) = && (p.x + g->size.width > = lf->rect.size.width));</div><div> = </div><div> if = (doesGlyphFitInLine)</div><div> = {</div><div> /* Calculate = vertical position with baseline adjustments */</div><div> = CGFloat y =3D = 0;</div><div><br></div><div> /* = Apply superscript offset (negative =3D up in flipped coords) = */</div><div> if = (g->attributes.superscript)</div><div> = {</div><div> = y -=3D g->attributes.superscript * [f = xHeight];</div><div> = }</div><div> /* Apply explicit = baseline offset */</div><div> = if (g->attributes.baseline_offset)</div><div> = {</div><div> = y +=3D = g->attributes.baseline_offset;</div><div> = }</div><div><br></div><div> = if (y !=3D p.y)</div><div> = {</div><div> = p.y =3D y;</div><div> = g->nominal =3D NO; // = Non-standard vertical position</div><div> = }</div><div> = </div><div> /* = Update line metrics based on this glyph */</div><div> = if (f_ascender > = ascender)</div><div> = ascender =3D f_ascender;</div><div> = if (f_descender > descender)</div><div> = descender =3D = f_descender;</div><div><br></div><div> = /* Adjust for superscript/subscript height requirements = */</div><div> if (y < 0 = && f_ascender - y > ascender)</div><div> = ascender =3D f_ascender - = y;</div><div> if (y > 0 = && f_descender + y > descender)</div><div> = descender =3D f_descender + = y;</div><div><br></div><div> /* = If metrics changed, check if we need to restart with new line height = */</div><div> if = (wantNewLineHeight(ascender + descender, &line_height, = max_line_height))</div><div> = goto restart;</div><div> = }</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>/* CONTROL GLYPH HANDLING = (newlines, tabs, etc.) */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>if (g->g =3D=3D = NSControlGlyph)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = unichar ch =3D [[curTextStorage string] characterAtIndex: = g->char_index];</div><div><br></div><div><span class=3D"Apple-tab-span"= style=3D"white-space:pre"> </span> g->pos =3D = p;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> g->size.width =3D 0;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = g->dont_show =3D YES; // Control glyphs don't = render</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre">= </span> g->nominal =3D = !prev_had_non_nominal_width;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = i++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = g++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> last_glyph =3D = NSNullGlyph;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = prev_had_non_nominal_width =3D NO;</div><div><br></div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = /* NEWLINE: End this line, start new paragraph */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = if (ch =3D=3D 0xa) // new line</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>newParagraph =3D = YES;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>break;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> /* TAB: Advance to = next tab stop */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if (ch =3D=3D 0x9) = // horiz. tab</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>NSArray *tabs =3D = [curParagraphStyle tabStops];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>NSTextTab *tab =3D = nil;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>CGFloat defaultInterval =3D [curParagraphStyle = defaultTabInterval];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>if (defaultInterval =3D=3D = 0.0)</div><div> = {</div><div> = defaultInterval =3D 100.0; // Reasonable = default</div><div> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>unsigned = tabIndex;</div><div> = unsigned tabCount =3D [tabs count];</div><div> = </div><div> = /* Find next tab stop after = current position */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>for (tabIndex =3D 0; = tabIndex < tabCount; tabIndex++)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> tab =3D = [tabs objectAtIndex: tabIndex];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if ([tab = location] > p.x + lf->rect.origin.x)</div><div> = = {</div><div> = break;</div><div> = }</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>if (tabIndex =3D=3D = tabCount)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = /* Past last explicit tab stop - use default interval = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> p.x =3D (floor(p.x / defaultInterval) + 1.0) * = defaultInterval;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> }</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>else</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = p.x =3D [tab location] - = lf->rect.origin.x;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> }</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>prev_had_non_nominal_width =3D YES;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>continue;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><br></div><div> = /* Unknown control character - log and ignore */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = NSDebugLLog(@"GSHorizontalTypesetter",</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = @"ignoring unknown control character %04x\n", = ch);</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> continue;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>/* TEXT ATTACHMENT HANDLING = (images, files, etc.) */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>if (g->g =3D=3D = GSAttachmentGlyph)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = NSTextAttachment *attach;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = NSTextAttachmentCell *cell;</div><div><span class=3D"Apple-tab-span"= style=3D"white-space:pre"> </span> NSRect = r;</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> attach =3D = [curTextStorage attribute: NSAttachmentAttributeName</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = atIndex: g->char_index</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = effectiveRange: NULL];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = cell =3D (NSTextAttachmentCell*)[attach = attachmentCell];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if = (!cell)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div> = /* No cell for attachment - treat as zero-width invisible glyph = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>g->pos =3D p;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>g->size =3D = NSMakeSize(0, 0);</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>g->dont_show =3D = YES;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>g->nominal =3D YES;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>i++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>g++;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>last_glyph =3D NSNullGlyph;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>continue;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><br></div><div> = /* Calculate baseline position for attachment alignment = */</div><div> baseline =3D = line_height - descender;</div><div><br></div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = /* Ask attachment cell for its desired frame */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = r =3D [cell cellFrameForTextContainer: = curTextContainer</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = proposedLineFragment: lf->rect</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = glyphPosition: NSMakePoint(p.x,</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> lf->rect.size.height - = baseline)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> characterIndex: = g->char_index];</div><div><br></div><div> = /* Check if attachment fits in current fragment = */</div><div> = doesGlyphFitInLine =3D !((i > firstGlyphIndex) && (p.x + = NSMaxX(r) > lf->rect.size.width));</div><div> = if (doesGlyphFitInLine)</div><div> = {</div><div> = /* Update line metrics for attachment = size */</div><div> = if (-NSMinY(r) > descender)</div><div> = descender =3D = -NSMinY(r);</div><div> = if (NSMaxY(r) > ascender)</div><div> = ascender =3D = NSMaxY(r);</div><div><br></div><div> = /* Check if attachment forces line height increase = */</div><div> if = (wantNewLineHeight(ascender + descender, &line_height, = max_line_height))</div><div> = goto restart;</div><div> = }</div><div><br></div><div> = /* Position attachment (note: r is upside-down = relative to our coords) */</div><div> = g->size =3D r.size;</div><div> = g->pos.x =3D p.x + r.origin.x;</div><div> = g->pos.y =3D p.y - = r.origin.y;</div><div><br></div><div> = p.x =3D g->pos.x + g->size.width;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = g->nominal =3D NO; // Attachments always have custom = positioning</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> }</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>else</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div> = /* STANDARD GLYPH: Just use cached = advancement */</div><div> /* = TODO: Kerning is commented out as a bottleneck - needs optimization = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> last_p =3D g->pos =3D p;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = p.x +=3D g->size.width;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>/* LINE BREAKING: Glyph didn't = fit in current fragment */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>if = (!doesGlyphFitInLine)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = switch ([curParagraphStyle lineBreakMode])</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = default:</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> case = NSLineBreakByCharWrapping:</div><div> = /* Break immediately before current glyph = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>lf->lastGlyphIndex =3D i;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>break;</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> case = NSLineBreakByWordWrapping:</div><div> = /* Search backward for word boundary = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>lf->lastGlyphIndex =3D [self breakLineByWordWrappingBefore: = cache_base + i] - cache_base;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>if (lf->lastGlyphIndex = <=3D firstGlyphIndex)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div> = // No = word boundary found - fall back to character wrapping</div><div> = = lf->lastGlyphIndex =3D i;</div><div> = }</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>break;</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> case = NSLineBreakByTruncatingHead:</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> case = NSLineBreakByTruncatingMiddle:</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> case = NSLineBreakByTruncatingTail:</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> case = NSLineBreakByClipping:</div><div> = /* CLIPPING/TRUNCATING: Hide overflowing glyphs = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>g->outside_line_frag =3D YES;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>while (1)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = i++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = g++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if (i >=3D= cache_length)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>if = (at_end)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = newParagraph =3D NO;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = i--;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = break;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>[self = _cacheGlyphs: cache_length + CACHE_STEP];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>if (i >=3D cache_length)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = newParagraph =3D NO;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = i--;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = break;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>g =3D cache + = i;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = g->dont_show =3D YES;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> g->pos =3D= p;</div><div> = /* Stop at paragraph break */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = if (g->g =3D=3D NSControlGlyph</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>&& [[curTextStorage string]</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> characterAtIndex: g->char_index] =3D=3D = 0xa)</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> break;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>lf->lastGlyphIndex =3D = i + 1;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre">= </span>break;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><br></div><div> = /*</div><div> * = SAFETY: Ensure at least one glyph per fragment.</div><div> = * Prevents infinite loops when = container is narrower than a single glyph.</div><div> = */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = if (lf->lastGlyphIndex <=3D firstGlyphIndex)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = lf->lastGlyphIndex =3D i + = 1;</div><div><br></div><div> /* = Reset position for next line fragment */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = last_p =3D p =3D NSMakePoint(0, 0);</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = i =3D lf->lastGlyphIndex;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = g =3D cache + i;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> lf->last_used =3D = g[-1].pos.x + g[-1].size.width;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> last_glyph =3D = NSNullGlyph;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = prev_had_non_nominal_width =3D NO;</div><div><br></div><div> = /* Move to next line fragment or end = line if exhausted */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = lf++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = lfi++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if (lfi =3D=3D = line_frags_num)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>newParagraph =3D = NO;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>break;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> firstGlyphIndex =3D = i;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>else</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div> /* Glyph fit = - advance to next */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> last_glyph =3D = g->g;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if (last_glyph =3D=3D= GSAttachmentGlyph)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>last_glyph =3D = NSNullGlyph;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> = </span>prev_had_non_nominal_width =3D YES;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = else</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> = </span>prev_had_non_nominal_width =3D NO;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = i++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = g++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> }</div><div> = }</div><div> /* END MAIN LAYOUT LOOP = */</div><div><br></div><div> /* ALIGNMENT PASS: Apply = paragraph alignment to positioned glyphs */</div><div> if = (lfi !=3D line_frags_num)</div><div> = {</div><div> /* Line filled exactly - apply = alignment */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>lf->lastGlyphIndex =3D = i;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>lf->last_used =3D p.x;</div><div><br></div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span>if = ([curParagraphStyle alignment] =3D=3D = NSRightTextAlignment)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> [self rightAlignLine: = line_frags : line_frags_num];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>else if ([curParagraphStyle = alignment] =3D=3D NSCenterTextAlignment)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = [self centerAlignLine: line_frags : = line_frags_num];</div><div> }</div><div> = else</div><div> {</div><div> = /* Line broke early - check for justification or alignment = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>if ([curParagraphStyle lineBreakMode] =3D=3D = NSLineBreakByWordWrapping &&</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = [curParagraphStyle alignment] =3D=3D = NSJustifiedTextAlignment)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> [self fullJustifyLine: = line_frags : line_frags_num];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>else if ([curParagraphStyle = alignment] =3D=3D NSRightTextAlignment)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = [self rightAlignLine: line_frags : = line_frags_num];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>else if ([curParagraphStyle = alignment] =3D=3D NSCenterTextAlignment)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = [self centerAlignLine: line_frags : = line_frags_num];</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>lfi--;</div><div> = }</div><div><br></div><div> /* COMMIT LAYOUT TO = LAYOUT MANAGER */</div><div> [curLayoutManager = setTextContainer: curTextContainer</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = forGlyphRange: NSMakeRange(cache_base, = i)];</div><div> curGlyphIndex =3D i + = cache_base;</div><div> </div><div> = {</div><div> line_frag_t *lf;</div><div> = NSPoint p;</div><div> unsigned int = lineFragCounter, lineFragCounter2;</div><div> = glyph_cache_t *g;</div><div> NSRect = used_rect;</div><div><br></div><div> /* Final = baseline calculation */</div><div> baseline =3D = line_height - descender;</div><div><br></div><div> = /* Iterate through line fragments and register each with layout manager = */</div><div> for (lf =3D line_frags, = lineFragCounter =3D 0, g =3D cache; lfi >=3D 0; lfi--, = lf++)</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>{</div><div> /* Calculate used = rect (actual ink bounds) vs fragment rect (available space) = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> used_rect.origin.x =3D g->pos.x + = lf->rect.origin.x;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> used_rect.size.width =3D = lf->last_used - g->pos.x;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> used_rect.origin.y =3D = lf->rect.origin.y;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> used_rect.size.height =3D = lf->rect.size.height;</div><div><br></div><div> = /* Register the line fragment */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = [curLayoutManager setLineFragmentRect: lf->rect</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> forGlyphRange: NSMakeRange(cache_base + = lineFragCounter, lf->lastGlyphIndex - = lineFragCounter)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = usedRect: used_rect];</div><div> = </div><div> /* Register = individual glyph positions */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> p =3D = g->pos;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> p.y +=3D baseline; = // Convert from relative to absolute position</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = lineFragCounter2 =3D lineFragCounter;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = while (lineFragCounter < lf->lastGlyphIndex)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div> /* = Set flags for special glyph handling */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = if (g->outside_line_frag)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>{</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> [curLayoutManager = setDrawsOutsideLineFragment: YES</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = forGlyphAtIndex: cache_base + lineFragCounter];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>}</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if = (g->dont_show)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>{</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = [curLayoutManager setNotShownAttribute: YES</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> forGlyphAtIndex: cache_base + = lineFragCounter];</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>}</div><div> = </div><div> = /* Register glyph runs with non-nominal = positioning */</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if = (!g->nominal && lineFragCounter !=3D = lineFragCounter2)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>{</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = [curLayoutManager setLocation: p</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> forStartOfGlyphRange: NSMakeRange(cache_base + = lineFragCounter2, lineFragCounter - lineFragCounter2)];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = if (g[-1].g =3D=3D GSAttachmentGlyph)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = [curLayoutManager setAttachmentSize: g[-1].size</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>forGlyphRange: NSMakeRange(cache_base + lineFragCounter2, = lineFragCounter - lineFragCounter2)];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> p =3D = g->pos;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> p.y +=3D = baseline;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> lineFragCounter2 =3D= lineFragCounter;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>}</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = lineFragCounter++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = g++;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> }</div><div> = /* Register final run in fragment = */</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> if (lineFragCounter !=3D lineFragCounter2)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = [curLayoutManager setLocation: p</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>forStartOfGlyphRange: NSMakeRange(cache_base + lineFragCounter2, = lineFragCounter - lineFragCounter2)];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = if (g[-1].g =3D=3D GSAttachmentGlyph)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>{</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> [curLayoutManager = setAttachmentSize: g[-1].size</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = forGlyphRange: NSMakeRange(cache_base + lineFragCounter2, = lineFragCounter - lineFragCounter2)];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>}</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> }</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>}</div><div> }</div><div> = }</div><div><br></div><div> /* Advance current point to next line = */</div><div> curPoint =3D NSMakePoint(0, = NSMaxY(line_frags->rect));</div><div><br></div><div> if = (newParagraph)</div><div> return 3;</div><div> = else</div><div> return = 0;</div><div>}</div><div><br></div><div><br></div><div>/*</div><div> = * MAIN ENTRY POINT</div><div> * Lays out multiple lines of glyphs = into the text container.</div><div> *</div><div> * = Parameters:</div><div> * layoutManager: = The GSLayoutManager managing this text</div><div> * = textContainer: The container defining layout = geometry</div><div> * glyphIndex: = Starting glyph index for this layout = operation</div><div> * previousLineFragRect: Rectangle = of the previous line (for positioning)</div><div> * = nextGlyphIndex: OUTPUT - index of first = unlaid glyph</div><div> * howMany: = Maximum number of lines to layout (0 =3D = unlimited)</div><div> *</div><div> * Return = values:</div><div> * 0 - Layout completed successfully (more = glyphs may remain)</div><div> * 1 - Text container is = full</div><div> * 2 - All glyphs have been laid = out</div><div> */</div><div>-(int) layoutGlyphsInLayoutManager: = (GSLayoutManager *)layoutManager</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> inTextContainer: = (NSTextContainer *)textContainer</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = startingAtGlyphIndex: (unsigned int)glyphIndex</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = previousLineFragmentRect: = (NSRect)previousLineFragRect</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = nextGlyphIndex: (unsigned int *)nextGlyphIndex</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = numberOfLineFragments: (unsigned = int)howMany</div><div>{</div><div> int ret, = real_ret;</div><div> BOOL = newParagraph;</div><div><br></div><div> /* REENTRANCY HANDLING = */</div><div> if (![lock tryLock])</div><div> = {</div><div> /* Already in use - create temporary = instance to handle nested call */</div><div> = GSHorizontalTypesetter *temp;</div><div><br></div><div> = temp =3D [[object_getClass(self) alloc] init];</div><div> = ret =3D [temp layoutGlyphsInLayoutManager: = layoutManager</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = inTextContainer: textContainer</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> startingAtGlyphIndex: glyphIndex</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = previousLineFragmentRect: = previousLineFragRect</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> = nextGlyphIndex: nextGlyphIndex</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>numberOfLineFragments: howMany];</div><div> = DESTROY(temp);</div><div> return = ret;</div><div> = }</div><div><br></div><div>NS_DURING</div><div> /* Initialize = layout context */</div><div> curLayoutManager =3D = layoutManager;</div><div> curTextContainer =3D = textContainer;</div><div> curTextStorage =3D [layoutManager = textStorage];</div><div> curGlyphIndex =3D = glyphIndex;</div><div><br></div><div> [self = _cacheClear];</div><div><br></div><div> real_ret =3D 4; // = Initial state forces paragraph check</div><div> curPoint =3D = NSMakePoint(0, = NSMaxY(previousLineFragRect));</div><div> </div><div> = /* Main layout loop - process lines until limit or completion = */</div><div> while (1)</div><div> {</div><div> = /* Determine if we're starting a new paragraph = */</div><div> if (real_ret =3D=3D 4)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>{</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if = (!curGlyphIndex)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> {</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = newParagraph =3D YES; // Very first glyph in = text</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> else</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = {</div><div> /* = Check if previous character was a newline */</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = unsigned int chi;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> unichar = ch;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> chi =3D [curLayoutManager = characterRangeForGlyphRange: NSMakeRange(curGlyphIndex - 1, = 1)</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> actualGlyphRange: = NULL].location;</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> ch =3D = [[curTextStorage string] characterAtIndex: chi];</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> if (ch =3D=3D = '\n')</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>newParagraph =3D YES;</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = else</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>newParagraph =3D = NO;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span> }</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>}</div><div> = else if (real_ret =3D=3D 3)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>{</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = newParagraph =3D YES; // Previous line ended with = newline</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>}</div><div> = else</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>{</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span> newParagraph =3D = NO;</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>}</div><div><br></div><div> /* Layout one = line */</div><div> ret =3D [self = layoutLineNewParagraph: newParagraph];</div><div><br></div><div> = /* Normalize return codes 3 and 4 to 0 for loop control = */</div><div> real_ret =3D ret;</div><div> = if (ret =3D=3D 3 || ret =3D=3D 4)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span>ret =3D = 0;</div><div><br></div><div> if = (ret)</div><div><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>break; // Error or completion (1 or = 2)</div><div><br></div><div> if = (howMany)</div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>if (!--howMany)</div><div><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span> = break; // Reached line limit</div><div> = }</div><div><br></div><div> *nextGlyphIndex =3D = curGlyphIndex;</div><div>NS_HANDLER</div><div> /* Exception = handling: log, unlock, and re-raise */</div><div> = NSLog(@"GSHorizontalTypesetter - %@", [localException = reason]);</div><div> [lock unlock];</div><div> = [localException raise];</div><div> ret=3D0; /* Unreachable, but = silences compiler warnings = */</div><div>NS_ENDHANDLER</div><div> </div><div> [lock = unlock];</div><div> return = ret;</div><div>}</div><div><br></div><div>@end</div><div>```</div><div><br= ></div><div>## Summary</div><div><br></div><div>This is a sophisticated = horizontal typesetter for GNUstep implementing the full Cocoa text = layout architecture. Key features = include:</div><div><br></div><div>**Architecture:**</div><div>- = **Thread-local singleton** pattern for performance without locking = overhead</div><div>- **Reentrancy support** via temporary instance = creation when nested calls occur</div><div>- **Two-phase caching**: = First cache glyph attributes, then compute = positions</div><div><br></div><div>**Layout Algorithm:**</div><div>1. = **Soft invalidation** check for incremental layout reuse</div><div>2. = **Line fragment acquisition** from text container (handles complex = shapes)</div><div>3. **Glyph-by-glyph positioning** with font changes, = attachments, tabs</div><div>4. **Line breaking** by character or word = (with CJK support)</div><div>5. **Alignment/justification** pass to = distribute space</div><div>6. **Commit** to layout manager with precise = glyph ranges and positions</div><div><br></div><div>**Performance = Optimizations:**</div><div>- Aggressive caching of glyph metrics and = attributes</div><div>- Batch glyph processing with expandable = buffers</div><div>- Inline helper functions for hot paths</div><div>- = Memmove for cache window sliding rather than = refetching</div><div><br></div><div>The code handles complex typography = including bidirectional text support preparation, text attachments, = multiple line fragments per line, and all standard paragraph = alignments.</div><div><br></div><div><br></div><div>=E2=80=94=E2=80=94 = =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94= =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 = =E2=80=94=E2=80=94 </div><div><br></div><div><br></div><div>Then I = asked:</div><div><br></div><div><br></div><div>=E2=80=9EWhat = refactorings do you propose to make that above code you commented better = readable and simpler to comprehend without making it = worse?=E2=80=9C</div><div><br></div><div><br></div><div>=E2=80=94=E2=80=94= =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94= =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 = =E2=80=94=E2=80=94 </div><div><br></div><div><br></div><div><div>I'll= analyze this code and propose concrete refactorings to improve = readability and maintainability. Let me work through this = systematically.</div><div><br></div><div>## Proposed Refactorings for = GSHorizontalTypesetter</div><div><br></div><div>### 1. **Extract Magic = Numbers and Character Constants**</div><div><br></div><div>Replace = hardcoded hex values with named = constants:</div><div><br></div><div>```objc</div><div>// Add to top of = file or private header</div><div>static const unichar kSpaceCharacter =3D = 0x20;</div><div>static const unichar kNewlineCharacter =3D = 0x0a;</div><div>static const unichar kCarriageReturnCharacter =3D = 0x0d;</div><div>static const unichar kTabCharacter =3D = 0x09;</div><div>static const unichar kCJKRangeStart =3D = 0x2ff0;</div><div>static const unichar kCJKRangeEnd =3D = 0x9fff;</div><div>static const CGFloat kDefaultTabInterval =3D = 100.0;</div><div>static const CGFloat kFallbackLineHeight =3D = 15.0;</div><div>static const CGFloat kExtraLineFragmentWidth =3D = 1.0;</div><div>```</div><div><br></div><div>### 2. **Extract Complex = Conditionals into Meaningful Boolean = Methods**</div><div><br></div><div>The glyph fitting check appears = multiple times and is hard to = read:</div><div><br></div><div>```objc</div><div>// BEFORE: scattered = throughout code</div><div>doesGlyphFitInLine =3D !((i > = firstGlyphIndex) && (p.x + g->size.width > = lf->rect.size.width));</div><div><br></div><div>// AFTER: extracted = method</div><div>- (BOOL)doesGlyphAtIndex:(unsigned = int)glyphIndex </div><div> = withWidth:(CGFloat)width </div><div> = fitInLineFragment:(line_frag_t = *)fragment</div><div> = afterFirstGlyph:(unsigned = int)firstIndex</div><div>{</div><div> if (glyphIndex <=3D = firstIndex) return YES; // First glyph always = fits</div><div> return (p.x + width <=3D = fragment->rect.size.width);</div><div>}</div><div>```</div><div><br></d= iv><div>### 3. **Replace `goto restart` with Structured = Loop**</div><div><br></div><div>The `restart` label for line height = recalculation is a classic goto = pattern:</div><div><br></div><div>```objc</div><div>// BEFORE: = goto-based restart</div><div>restart: ;</div><div>do {</div><div> = // ... calculate fragments ...</div><div> if = (line_frags_num =3D=3D 0) {</div><div> if (/* = emergency shrink */) {</div><div> = line_height =3D ...;</div><div> = continue; // or goto restart</div><div> = }</div><div> return = 1;</div><div> }</div><div>} while (line_frags_num =3D=3D = 0);</div><div><br></div><div>// ... later ...</div><div>if = (wantNewLineHeight(...)) goto restart;</div><div><br></div><div>// = AFTER: extracted method with explicit loop</div><div>- = (BOOL)_acquireLineFragmentsWithHeight:(CGFloat)lineHeight</div><div> = = maxLineHeight:(CGFloat)maxLineHeight</div><div> = = newParagraph:(BOOL)newParagraph</div><div> = = resultingHeight:(CGFloat = *)outHeight</div><div>{</div><div> CGFloat currentHeight =3D = lineHeight;</div><div> CGFloat maxHeight =3D = maxLineHeight;</div><div> </div><div> = while (YES) {</div><div> [self = _buildLineFragmentsForHeight:currentHeight = newParagraph:newParagraph];</div><div> = </div><div> if (line_frags_num = > 0) {</div><div> *outHeight = =3D currentHeight;</div><div> = return YES; // Success</div><div> = }</div><div> </div><div> = // Emergency: container too small for even one = line</div><div> if (curPoint.y =3D=3D 0.0 = && currentHeight > containerHeight && containerHeight = > 0.0) {</div><div> = currentHeight =3D containerHeight;</div><div> = maxHeight =3D currentHeight;</div><div> = continue; // Retry with shrunk = height</div><div> }</div><div> = </div><div> return NO; = // No room in container</div><div> = }</div><div>}</div><div><br></div><div>// Then in main layout, replace = goto with:</div><div>while (![self _updateLineHeightIfNeeded:ascender = descender:descender </div><div> = = currentHeight:&line_height </div><div> = = maxHeight:max_line_height]) {</div><div> // = Restart layout with new height</div><div> if (![self = _acquireLineFragmentsWithHeight:line_height ...]) {</div><div> = return 1;</div><div> = }</div><div> // Re-initialize cache if = needed</div><div>}</div><div>```</div><div><br></div><div>### 4. = **Extract State Machine Transitions**</div><div><br></div><div>The = `real_ret` state machine in `layoutGlyphsInLayoutManager:` is = cryptic:</div><div><br></div><div>```objc</div><div>// BEFORE: confusing = state tracking</div><div>real_ret =3D 4;</div><div>while (1) = {</div><div> if (real_ret =3D=3D 4) {</div><div> = // determine newParagraph</div><div> } = else if (real_ret =3D=3D 3) {</div><div> = newParagraph =3D YES;</div><div> } else {</div><div> = newParagraph =3D NO;</div><div> = }</div><div> ret =3D [self = layoutLineNewParagraph:newParagraph];</div><div> real_ret =3D= ret;</div><div> if (ret =3D=3D 3 || ret =3D=3D 4) ret =3D = 0;</div><div>}</div><div><br></div><div>// AFTER: explicit state enum = and transition method</div><div>typedef NS_ENUM(NSInteger, LayoutState) = {</div><div> LayoutStateInitial =3D 0, = // Need to check if new paragraph</div><div> = LayoutStateNewParagraph, // Previous line ended with = newline</div><div> LayoutStateContinuing, = // In middle of paragraph</div><div> = LayoutStateComplete, // All glyphs laid = out</div><div> LayoutStateNoRoom = // Container = full</div><div>};</div><div><br></div><div>- = (LayoutState)determineNextStateFrom:(LayoutState)previousState </div>= <div> = = lineResult:(int)lineResult</div><div>{</div><div> switch = (lineResult) {</div><div> case 2: return = LayoutStateComplete; // At end</div><div> = case 1: return LayoutStateNoRoom; // No = room</div><div> case 3: return = LayoutStateNewParagraph; // Ended with newline</div><div> = case 0: return LayoutStateContinuing; // Normal line = break</div><div> case 4: return = LayoutStateInitial; // Ambiguous, need check</div><div> = default: return = LayoutStateComplete;</div><div> = }</div><div>}</div><div><br></div><div>- = (BOOL)isNewParagraphForState:(LayoutState)state </div><div> = = atGlyphIndex:(unsigned int)glyphIndex</div><div>{</div><div> = switch (state) {</div><div> case = LayoutStateInitial:</div><div> = return [self _glyphStartsNewParagraph:glyphIndex];</div><div> = case LayoutStateNewParagraph:</div><div> = return YES;</div><div> = case LayoutStateContinuing:</div><div> = return NO;</div><div> = default:</div><div> return = NO;</div><div> = }</div><div>}</div><div>```</div><div><br></div><div>### 5. **Break Up = `layoutLineNewParagraph:` into Phased = Methods**</div><div><br></div><div>This 400+ line method does too much. = Extract phases:</div><div><br></div><div>```objc</div><div>- = (int)layoutLineNewParagraph:(BOOL)newParagraph</div><div>{</div><div> = ; // Phase 1: Setup and cache initialization</div><div> = if (![self _prepareLayoutForNewParagraph:newParagraph]) = {</div><div> return [self = _handleEndOfTextOrContainer];</div><div> }</div><div> = </div><div> // Phase 2: Acquire line fragments = (may restart if metrics change)</div><div> CGFloat = finalLineHeight;</div><div> if (![self = _acquireLineFragmentsWithHeight:initialLineHeight </div><div> = = = maxLineHeight:maxLineHeight</div><div> = = newParagraph:newParagraph</div><div> = = = resultingHeight:&finalLineHeight]) {</div><div> = return 1; // No room</div><div> }</div><div> = </div><div> // Phase 3: Position glyphs in = fragments</div><div> LayoutLineResult *result =3D [self = _positionGlyphsInFragments];</div><div> if = (result.needsRestart) {</div><div> // = Recursively retry with new height (limited depth to prevent infinite = loop)</div><div> return [self = layoutLineNewParagraph:newParagraph];</div><div> = }</div><div> </div><div> // Phase 4: = Apply alignment</div><div> [self = _applyAlignmentToLine:result];</div><div> = </div><div> // Phase 5: Commit to layout = manager</div><div> [self _commitLayoutResult:result = lineHeight:finalLineHeight];</div><div> = </div><div> return result.endedWithNewline ? 3 = : 0;</div><div>}</div><div>```</div><div><br></div><div>### 6. **Replace = Anonymous Structs with Named = Types**</div><div><br></div><div>```objc</div><div>// BEFORE: anonymous = struct in ivars</div><div>struct {</div><div> BOOL = explicit_kern;</div><div> float kern;</div><div> = float baseline_offset;</div><div> int = superscript;</div><div>} attributes;</div><div><br></div><div>// AFTER: = named type with documentation</div><div>/**</div><div> * = GSScriptAttributes collects the typographic attributes that = affect</div><div> * glyph positioning relative to the = baseline.</div><div> */</div><div>typedef struct {</div><div> = BOOL hasExplicitKern; // YES if = NSKernAttributeName was specified</div><div> CGFloat = kernAmount; // Additional spacing between = glyphs</div><div> CGFloat baselineOffset; // = Vertical shift from normal baseline (positive =3D up)</div><div> = NSInteger superscriptLevel; // 0 =3D normal, +1 =3D superscript, = -1 =3D subscript</div><div>} = GSScriptAttributes;</div><div><br></div><div>// Usage in class = becomes:</div><div>GSScriptAttributes = _currentScriptAttributes;</div><div>```</div><div><br></div><div>### 7. = **Extract Cache Management to Dedicated = Class**</div><div><br></div><div>The cache manipulation is complex and = scattered:</div><div><br></div><div>```objc</div><div>// New class: = GSGlyphCache</div><div>@interface GSGlyphCache : = NSObject</div><div>@property (readonly) NSUInteger = baseIndex;</div><div>@property (readonly) NSUInteger = count;</div><div>@property (readonly) BOOL = atEnd;</div><div><br></div><div>- = (void)repositionToGlyphIndex:(NSUInteger)index </div><div> = = layoutManager:(GSLayoutManager *)lm;</div><div>- = (void)ensureCount:(NSUInteger)count </div><div> = layoutManager:(GSLayoutManager *)lm;</div><div>- (glyph_cache_t = *)glyphAtIndex:(NSUInteger)index; // Relative to base</div><div>- = (void)slideWindowToIndex:(NSUInteger)index;</div><div>@end</div><div>```</= div><div><br></div><div>### 8. **Replace C Arrays with NSMutableArray or = Smart Pointer**</div><div><br></div><div>The manual `realloc` management = is error-prone:</div><div><br></div><div>```objc</div><div>// BEFORE: = manual C array management</div><div>if (line_frags_num > = line_frags_size) {</div><div> line_frags_size +=3D = 2;</div><div> line_frags =3D realloc(line_frags, = sizeof(line_frag_t) * = line_frags_size);</div><div>}</div><div><br></div><div>// AFTER: use = NSMutableArray with wrapper object, or at least:</div><div>typedef = struct {</div><div> line_frag_t *items;</div><div> = NSUInteger count;</div><div> NSUInteger = capacity;</div><div>} = GSLineFragmentArray;</div><div><br></div><div>static inline void = GSLineFragmentArrayPush(GSLineFragmentArray *array) {</div><div> = if (array->count >=3D array->capacity) = {</div><div> array->capacity =3D = array->capacity ? array->capacity * 2 : 4;</div><div> = array->items =3D realloc(array->items, = sizeof(line_frag_t) * array->capacity);</div><div> = }</div><div> = array->count++;</div><div>}</div><div>```</div><div><br></div><div>### = 9. **Document Return Value Semantics with = Enum**</div><div><br></div><div>```objc</div><div>// BEFORE: magic = integer returns</div><div>-(int) layoutLineNewParagraph: = (BOOL)newParagraph // Returns 0, 1, 2, 3, or = 4</div><div><br></div><div>// AFTER: explicit result = type</div><div>typedef NS_ENUM(NSInteger, GSLayoutLineResult) = {</div><div> GSLayoutLineResultContinuing =3D 0, = // Line complete, more glyphs in paragraph</div><div> = GSLayoutLineResultNoRoom =3D 1, = // Text container exhausted</div><div> = GSLayoutLineResultEndOfText =3D 2, // All glyphs = consumed</div><div> GSLayoutLineResultNewParagraph =3D 3, = // Line ended with newline</div><div> = GSLayoutLineResultNeedParagraphCheck =3D 4 // Ambiguous, caller must = check</div><div>};</div><div><br></div><div>- = (GSLayoutLineResult)layoutLineNewParagraph:(BOOL)newParagraph;</div><div>`= ``</div><div><br></div><div>### 10. **Flatten Nested Conditionals in = Main Loop**</div><div><br></div><div>The main glyph loop has deeply = nested conditions. Extract = handlers:</div><div><br></div><div>```objc</div><div>- = (BOOL)_handleControlGlyph:(glyph_cache_t *)glyph </div><div> = = atPosition:(NSPoint *)position</div><div> = lineFragment:(line_frag_t = *)fragment</div><div> = newParagraph:(BOOL *)outNewParagraph</div><div>{</div><div> = unichar ch =3D [[curTextStorage string] = characterAtIndex:glyph->char_index];</div><div> = </div><div> glyph->pos =3D = *position;</div><div> glyph->size.width =3D = 0;</div><div> glyph->dont_show =3D YES;</div><div> = </div><div> switch (ch) {</div><div> = case kNewlineCharacter:</div><div> = *outNewParagraph =3D YES;</div><div> = return NO; // Stop processing = this line</div><div> = </div><div> case = kTabCharacter:</div><div> = *position =3D [self _positionAfterTabFrom:*position = inFragment:fragment];</div><div> = return YES; // Continue to next glyph</div><div> = </div><div> = default:</div><div> = NSDebugLLog(@"GSHorizontalTypesetter", @"Unknown control %04x", = ch);</div><div> return = YES;</div><div> }</div><div>}</div><div><br></div><div>- = (BOOL)_handleAttachmentGlyph:(glyph_cache_t = *)glyph </div><div> = atPosition:(NSPoint *)position</div><div> = = lineFragment:(line_frag_t *)fragment</div><div> = = lineHeight:(CGFloat *)lineHeight</div><div>{</div><div> = // Extract attachment logic...</div><div> // Return = YES if fits and metrics updated, NO if needs line = break</div><div>}</div><div>```</div><div><br></div><div>### 11. = **Extract Metric Calculation to Dedicated = Method**</div><div><br></div><div>```objc</div><div>- = (void)_updateLineMetricsForGlyph:(glyph_cache_t *)glyph</div><div> = = currentAscender:(CGFloat *)ascender</div><div> = = currentDescender:(CGFloat *)descender</div><div> = = currentHeight:(CGFloat *)lineHeight</div><div> = = maxLineHeight:(CGFloat)maxHeight</div><div>{</div><div> = NSFont *font =3D glyph->font;</div><div> CGFloat = glyphAscender =3D [font ascender];</div><div> CGFloat = glyphDescender =3D -[font descender];</div><div> = </div><div> // Apply superscript = adjustments</div><div> CGFloat yOffset =3D = 0;</div><div> if (glyph->attributes.superscript) = {</div><div> yOffset -=3D = glyph->attributes.superscript * [font xHeight];</div><div> = }</div><div> if = (glyph->attributes.baseline_offset) {</div><div> = yOffset +=3D = glyph->attributes.baseline_offset;</div><div> = }</div><div> </div><div> // Update = metrics</div><div> *ascender =3D MAX(*ascender, = glyphAscender - MIN(yOffset, 0));</div><div> *descender =3D = MAX(*descender, glyphDescender + MAX(yOffset, 0));</div><div> = </div><div> CGFloat newHeight =3D *ascender + = *descender;</div><div> if (maxHeight > 0) = {</div><div> newHeight =3D MIN(newHeight, = maxHeight);</div><div> }</div><div> = *lineHeight =3D MAX(*lineHeight, = newHeight);</div><div>}</div><div>```</div><div><br></div><div>### 12. = **Simplify Soft Invalidation Logic**</div><div><br></div><div>The soft = invalidation method is hard to follow. Restructure with early returns = and clear phases:</div><div><br></div><div>```objc</div><div>- = (BOOL)_reuseSoftInvalidatedLayout</div><div>{</div><div> // = Quick reject: not at soft invalidation point</div><div> if = (![self _isAtSoftInvalidationPoint]) return NO;</div><div> = </div><div> // Quick reject: won't fit = vertically</div><div> NSRect firstRect =3D [self = _firstSoftInvalidatedRect];</div><div> if (![self = _canFitVertically:firstRect]) return NO;</div><div> = </div><div> // Collect consecutive valid = fragments</div><div> GSSoftInvalidationBatch *batch =3D = [self _collectSoftInvalidatedFragments];</div><div> if = (batch.isEmpty) return NO;</div><div> = </div><div> // Apply shift and = commit</div><div> [self = _applySoftInvalidationBatch:batch];</div><div> = curGlyphIndex =3D batch.endGlyphIndex;</div><div> return = YES;</div><div>}</div><div>```</div><div><br></div><div>---</div><div><br>= </div><div>## Summary Table</div><div><br></div><div>| Refactoring | = Benefit | Effort = |</div><div>|-------------|---------|--------|</div><div>| Named = constants | Eliminates magic numbers, self-documenting | Low = |</div><div>| Boolean methods | Clarifies intent of complex conditions | = Low |</div><div>| Replace goto | Structured flow, easier debugging | = Medium |</div><div>| State machine enum | Explicit transitions, type = safety | Medium |</div><div>| Method extraction | Single responsibility, = testable units | Medium |</div><div>| Named structs | Self-documenting, = IDE completion | Low |</div><div>| Cache class | Encapsulation, easier = optimization | High |</div><div>| Array abstraction | Memory safety, = bounds checking | Medium |</div><div>| Return value enum | Compiler = checking, clear semantics | Low |</div><div>| Glyph handlers | Flatten = nesting, isolate concerns | Medium |</div><div>| Metric extraction | = Reusable, testable calculations | Low |</div><div>| Soft invalidation = phases | Linear flow, early exits | Medium = |</div><div><br></div><div>These changes maintain performance (critical = for text layout) while significantly improving maintainability. The most = impactful would be **#5 (method extraction)** and **#3 (goto = elimination)**, which together would reduce the cognitive load of = understanding the layout = algorithm.</div></div><div><br></div><div><br></div><div>=E2=80=94=E2=80=94= =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 =E2=80=94=E2=80=94= =E2=80=94=E2=80=94 =E2=80=94=E2=80=94 = =E2=80=94=E2=80=94 </div><div><br></div><div><br></div><div>I see = you read until here. I know, that was a lot to digest. Now I am asking: = what do you think about this? Does it make sense? Should we do those = refactorings?</div><div><br></div><div><br></div><div>Kind = regards,</div><div><br></div><div><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> = </span>Lars</div></div></div></blockquote></div><br></div></body></html>= --Apple-Mail=_E574689C-527D-4D09-9378-02F1A9847500--