Analysis of GSHorizontalTypesetter
"[email protected]" via "Developers list for GNUstep, the GNU groupware evironment" <[email protected]> Sun, 15 Mar 2026 03:23:02 +0100
| Newsgroups | gmane.comp.lib.gnustep.devel |
|---|---|
| Message-ID | <[email protected]> |
--Apple-Mail=_129DB140-0349-41BF-87CA-B70DD12BB7CE Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Hi GNUstep-Devs, especially Riccardo, Fred and Greg since we were talking about this in = the call today. 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. So I asked it the following question: =E2=80=9EHello KIMI, please analyze the following code and explain it in = comments you add or correct:=E2=80=9C and then gave it both the header and the implementation. 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): =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 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. ```objc /* GSHorizontalTypesetter.h Copyright (C) 2002 Free Software Foundation, Inc. Author: Alexander Malmberg <[email protected]> Date: 2002 This file is part of the GNUstep GUI Library. 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. 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. 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. */ #ifndef _GNUstep_H_GSHorizontalTypesetter #define _GNUstep_H_GSHorizontalTypesetter #import <GNUstepGUI/GSTypesetter.h> @class NSLock; @class GSLayoutManager, NSTextContainer, NSTextStorage; @class NSDictionary; @class NSParagraphStyle, NSFont; /* * 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 /* * 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; /* * 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 unsigned int curGlyphIndex; // Current position in the glyph = stream NSPoint curPoint; // Current layout position (y = advances per line) /* * 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 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; NSFont *curFont; // Current font for glyph = metrics NSRange fontRange; // Glyph range where curFont is = valid /* * 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; /* * 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; } /* * 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; @end #endif ``` Now for the implementation file with detailed comments: ```objc /* GSHorizontalTypesetter.m Copyright (C) 2002, 2003 Free Software Foundation, Inc. Author: Alexander Malmberg <[email protected]> Date: November 2002 - February 2003 This file is part of the GNUstep GUI Library. 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. 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. 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. */ #include <math.h> #import <Foundation/NSDebug.h> #import <Foundation/NSException.h> #import <Foundation/NSGeometry.h> #import <Foundation/NSLock.h> #import <Foundation/NSThread.h> #import <Foundation/NSValue.h> #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" /* * 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. */ @implementation GSHorizontalTypesetter /* * 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; } /* * 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]; } /* * 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"]; if (!shared) { shared =3D [[self alloc] init]; [threadDict setObject: shared forKey: @"sharedHorizontalTypesetter"]; RELEASE(shared); } return shared; } /* * 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 /* * 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 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; /* 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; /* * 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; curParagraphStyle =3D nil; paragraphRange =3D NSMakeRange(0, 0); curAttributes =3D nil; attributeRange =3D NSMakeRange(0, 0); curFont =3D nil; fontRange =3D NSMakeRange(0, 0); } /* * 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; if (NSLocationInRange(char_index, attributeRange)) { return; } =20 curAttributes =3D [curTextStorage attributesAtIndex: char_index effectiveRange: &attributeRange]; /* 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]; } /* 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; /* Extract superscript level */ n =3D [curAttributes objectForKey: NSSuperscriptAttributeName]; if (n) attributes.superscript =3D [n intValue]; else attributes.superscript =3D 0; } /* * 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; /* 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; } /* Case 2: Complete reset - new location in text stream */ cache_base =3D glyph; cache_length =3D 0; [curLayoutManager glyphAtIndex: glyph isValidIndex: &valid]; if (valid) { unsigned int i; at_end =3D NO; i =3D [curLayoutManager characterIndexForGlyphAtIndex: glyph]; [self _cacheAttributes: i]; /* 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]; } /* 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 } } /* * 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; /* Grow buffer if needed */ if (cache_size < new_length) { cache_size =3D new_length; cache =3D realloc(cache, sizeof(glyph_cache_t) * cache_size); } /* 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; } /* Update attribute cache if needed */ if (g->char_index >=3D attributeRange.location + = attributeRange.length) { [self _cacheAttributes: g->char_index]; } /* 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; /* 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; /* Initialize layout fields */ g->dont_show =3D NO; g->outside_line_frag =3D NO; g->nominal =3D YES; /* 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]; } } /* * 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]; gi -=3D cache_base; // Convert to cache-relative index g =3D cache + gi; 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 } /* * 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; /* * Apple's maximum meaningful width for text containers. * Widths beyond this are treated as infinite and ignored for layout = purposes. */ #define LARGE_SIZE 1e7 /* * 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; if (lf->rect.size.width >=3D LARGE_SIZE) { return; } 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; 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; } } /* * 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; if (lf->rect.size.width >=3D LARGE_SIZE) { return; } 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; } } /* * 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; if (lf->rect.size.width >=3D LARGE_SIZE) { return; } 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; } } /* * 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]; container_height =3D [curTextContainer containerSize].height; if (!(curPoint.y + r0.size.height <=3D container_height)) return NO; // Won't fit at current Y position /* * 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]; /* Gap in soft-invalidated info - must fill in before continuing = */ if (first !=3D g) { break; } if (NSIsEmptyRect(r) || NSMaxY(r) + shift.height > = container_height) break; g =3D g2; curPoint.y =3D NSMaxY(r) + shift.height; } /* Commit the reused layout to the layout manager */ [curLayoutManager _softInvalidateUseLineFrags: i withShift: shift inTextContainer: curTextContainer]; curGlyphIndex =3D g; return YES; } /* * 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]; if (newParagraph) hindent =3D [curParagraphStyle firstLineHeadIndent]; else hindent =3D [curParagraphStyle headIndent]; /* Negative tail indent is treated as inset from right edge */ if (tindent <=3D 0.0) {=20 NSSize size; size =3D [curTextContainer containerSize]; tindent =3D size.width + tindent; } return NSMakeRect(hindent, curPoint.y, tindent - hindent, line_height + [curParagraphStyle lineSpacing]); } /* * 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; /* * 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]; } /* Determine line height from font or use default */ if (curFont) { line_height =3D [curFont defaultLineHeightForFont]; } else { line_height =3D 15.0; } 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]; } } /* * 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; if (maxLineHeight > 0 && newHeight > maxLineHeight) { newHeight =3D maxLineHeight; } if (newHeight > *lineHeight) { *lineHeight =3D newHeight; return YES; } return NO; } /* * 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; /* 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) /* * 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; } /* 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; } /* INITIALIZE LINE METRICS from first glyph's font */ { CGFloat min =3D [curParagraphStyle minimumLineHeight]; max_line_height =3D [curParagraphStyle maximumLineHeight]; /* Sanity: max must be >=3D min if both are specified */ if (max_line_height > 0 && max_line_height < min) max_line_height =3D min; line_height =3D [cache->font defaultLineHeightForFont]; ascender =3D [cache->font ascender]; descender =3D -[cache->font descender]; if (line_height < min) line_height =3D min; if (max_line_height > 0 && line_height > max_line_height) line_height =3D max_line_height; } /* * 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: ; do { NSRect remain; remain =3D [self _getProposedRectFor: newParagraph withLineHeight: line_height]; /* * 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; 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); /* * 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; NSPoint p; // Current glyph position (relative to line = fragment) =20 NSFont *f =3D cache->font; CGFloat f_ascender =3D [f ascender]; CGFloat f_descender =3D -[f descender]; NSGlyph last_glyph =3D NSNullGlyph; // For kerning calculations NSPoint last_p; 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 BOOL prev_had_non_nominal_width; // Track if previous glyph had = custom spacing last_p =3D p =3D NSMakePoint(0, 0); g =3D cache; firstGlyphIndex =3D 0; prev_had_non_nominal_width =3D NO; while (1) { BOOL doesGlyphFitInLine =3D YES; /* 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; } /* * 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; } /* Apply explicit kerning if specified in attributes */ g->nominal =3D !prev_had_non_nominal_width; if (g->attributes.explicit_kern && g->attributes.kern !=3D 0) { p.x +=3D g->attributes.kern; g->nominal =3D NO; } /* 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; /* 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; } 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; /* 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; /* If metrics changed, check if we need to restart with new = line height */ if (wantNewLineHeight(ascender + descender, &line_height, = max_line_height)) goto restart; } /* CONTROL GLYPH HANDLING (newlines, tabs, etc.) */ if (g->g =3D=3D NSControlGlyph) { unichar ch =3D [[curTextStorage string] characterAtIndex: = g->char_index]; 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; /* NEWLINE: End this line, start new paragraph */ if (ch =3D=3D 0xa) // new line { newParagraph =3D YES; break; } /* 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; } /* Unknown control character - log and ignore */ NSDebugLLog(@"GSHorizontalTypesetter", @"ignoring unknown control character %04x\n", ch); continue; } /* TEXT ATTACHMENT HANDLING (images, files, etc.) */ if (g->g =3D=3D GSAttachmentGlyph) { NSTextAttachment *attach; NSTextAttachmentCell *cell; NSRect r; 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; } /* Calculate baseline position for attachment alignment */ baseline =3D line_height - descender; /* 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]; /* 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); /* Check if attachment forces line height increase */ if (wantNewLineHeight(ascender + descender, = &line_height, max_line_height)) goto restart; } /* 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; 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; } /* 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; 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; 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; } /* * 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; /* 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; /* 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 */ /* 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; 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]; lfi--; } /* 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; /* Final baseline calculation */ baseline =3D line_height - descender; /* 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; /* 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)]; } } } } } /* Advance current point to next line */ curPoint =3D NSMakePoint(0, NSMaxY(line_frags->rect)); if (newParagraph) return 3; else return 0; } /* * 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; /* REENTRANCY HANDLING */ if (![lock tryLock]) { /* Already in use - create temporary instance to handle nested = call */ GSHorizontalTypesetter *temp; 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; } NS_DURING /* Initialize layout context */ curLayoutManager =3D layoutManager; curTextContainer =3D textContainer; curTextStorage =3D [layoutManager textStorage]; curGlyphIndex =3D glyphIndex; [self _cacheClear]; 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; } /* Layout one line */ ret =3D [self layoutLineNewParagraph: newParagraph]; /* 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; if (ret) break; // Error or completion (1 or 2) if (howMany) if (!--howMany) break; // Reached line limit } *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; } @end ``` ## Summary This is a sophisticated horizontal typesetter for GNUstep implementing = the full Cocoa text layout architecture. Key features include: **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 **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 **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 The code handles complex typography including bidirectional text support = preparation, text attachments, multiple line fragments per line, and all = standard paragraph alignments. =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 Then I asked: =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 =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 I'll analyze this code and propose concrete refactorings to improve = readability and maintainability. Let me work through this = systematically. ## Proposed Refactorings for GSHorizontalTypesetter ### 1. **Extract Magic Numbers and Character Constants** Replace hardcoded hex values with named constants: ```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; ``` ### 2. **Extract Complex Conditionals into Meaningful Boolean Methods** The glyph fitting check appears multiple times and is hard to read: ```objc // BEFORE: scattered throughout code doesGlyphFitInLine =3D !((i > firstGlyphIndex) && (p.x + g->size.width > = lf->rect.size.width)); // 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); } ``` ### 3. **Replace `goto restart` with Structured Loop** The `restart` label for line height recalculation is a classic goto = pattern: ```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); // ... later ... if (wantNewLineHeight(...)) goto restart; // 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 } } // 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 } ``` ### 4. **Extract State Machine Transitions** The `real_ret` state machine in `layoutGlyphsInLayoutManager:` is = cryptic: ```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; } // 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 }; - (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; } } - (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; } } ``` ### 5. **Break Up `layoutLineNewParagraph:` into Phased Methods** This 400+ line method does too much. Extract phases: ```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; } ``` ### 6. **Replace Anonymous Structs with Named Types** ```objc // BEFORE: anonymous struct in ivars struct { BOOL explicit_kern; float kern; float baseline_offset; int superscript; } attributes; // 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; // Usage in class becomes: GSScriptAttributes _currentScriptAttributes; ``` ### 7. **Extract Cache Management to Dedicated Class** The cache manipulation is complex and scattered: ```objc // New class: GSGlyphCache @interface GSGlyphCache : NSObject @property (readonly) NSUInteger baseIndex; @property (readonly) NSUInteger count; @property (readonly) BOOL atEnd; - (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 ``` ### 8. **Replace C Arrays with NSMutableArray or Smart Pointer** The manual `realloc` management is error-prone: ```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); } // AFTER: use NSMutableArray with wrapper object, or at least: typedef struct { line_frag_t *items; NSUInteger count; NSUInteger capacity; } GSLineFragmentArray; 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++; } ``` ### 9. **Document Return Value Semantics with Enum** ```objc // BEFORE: magic integer returns -(int) layoutLineNewParagraph: (BOOL)newParagraph // Returns 0, 1, 2, = 3, or 4 // 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 }; - (GSLayoutLineResult)layoutLineNewParagraph:(BOOL)newParagraph; ``` ### 10. **Flatten Nested Conditionals in Main Loop** The main glyph loop has deeply nested conditions. Extract handlers: ```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; } } - (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 } ``` ### 11. **Extract Metric Calculation to Dedicated Method** ```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); } ``` ### 12. **Simplify Soft Invalidation Logic** The soft invalidation method is hard to follow. Restructure with early = returns and clear phases: ```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; } ``` --- ## Summary Table | 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 | 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. =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 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? Kind regards, Lars= --Apple-Mail=_129DB140-0349-41BF-87CA-B70DD12BB7CE 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;"><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></body></html>= --Apple-Mail=_129DB140-0349-41BF-87CA-B70DD12BB7CE--