Adding English involved more than inserting a translation. Font conditions, reading width, line limits and downloadable material needed related checks. I reread actual English files and generator code, then separated line planning into a small reusable module.
Separate translation length from layout failure
Moving Korean sentences into English changes word length and available break positions. Keeping the original card height or heading line count can make an ordinary explanation appear incomplete. I check agreement in meaning separately from reading inside the chosen width. Shortening one language indiscriminately should not become the first remedy.
The actual work archive retained a generator and downloadable English PDFs. For this article I rendered two existing files, twelve pages in total, and inspected their100dpi previews. I saw no obvious overlap or page clipping in those previews. That observation is bounded by the inspected files and resolution; it is not verification of every screen or printed copy.
Establish the font and width before counting characters
Ten characters can occupy very different widths depending on font and weight. Narrow and wide Latin letters, Korean and digits make a character-count estimate particularly rough. The original PDF code measured text using its selected font. I retained that principle while separating the new planner from any particular rendering library through an injected measurement function.
A web integration should measure after its intended font is available. A PDF integration should use the same font conditions as the embedded output. The Canvas example here uses the browser’s current system-ui font; its measurements should not be transferred as exact geometry for a different PDF font. Width, line height and measurement units must agree with the receiving layout.
Keep long words and oversized graphemes visible
Breaking ordinary English at spaces preserves recognizable words. A word wider than the available measure still requires a second strategy. The new planner checks that case even when the word follows existing text. It flushes the preceding line and splits the long token at grapheme boundaries, so a long identifier is not silently dropped.
A visible character can contain several code points. Intl.Segmenter provides grapheme boundaries to retain combining characters and emoji sequences together where possible. If one grapheme exceeds the entire width, the planner emits it with an overflow flag. It does not split or shrink it while reporting success. Language-specific punctuation rules and hyphenation remain separate responsibilities.
Choose the next space instead of deleting extra lines
Keeping only a fixed number of lines can produce a tidy rectangle while deleting the explanation’s ending. Reading the original line limit and geometry led me to make the new example return all planned lines and their required height. The receiving layout chooses a taller section, another page or an explicit continuation.
Word mode normalizes repeated whitespace between tokens while retaining explicit paragraph breaks. The planner reports height without shortening the sentence, but it does not paginate a document. Remaining page space, headings and inter-section spacing belong to the actual generator. In a web integration, agreement between this planned line list and CSS’s rendered wrapping also requires its own check.
Explore the implementation
Line planner with injected measurements
export function planMeasuredLines(text,{measure,width,locale='en',mode='words',lineHeight=1}){
if(typeof text!=='string'||text.length>20000||typeof measure!=='function'||!Number.isFinite(width)||width<=0||!Number.isFinite(lineHeight)||lineHeight<=0||!['words','graphemes'].includes(mode))throw new TypeError('Text, finite measures and a supported mode required');
const segmenter=new Intl.Segmenter(locale,{granularity:'grapheme'});
const graphemes=value=>Array.from(segmenter.segment(value),x=>x.segment);
const measured=value=>{const n=measure(value);if(!Number.isFinite(n)||n<0)throw new TypeError('Measurement must be a nonnegative finite number');return n;};
const lines=[];
const push=value=>{const valueWidth=measured(value);lines.push({text:value,width:valueWidth,overflow:valueWidth>width});};
for(const paragraph of text.split(/\r\n|\r|\n/)){
if(!paragraph.trim()){push('');continue;}
const tokens=mode==='words'?paragraph.trim().split(/\s+/u):graphemes(paragraph),separator=mode==='words'?' ':'';let line='';
for(const token of tokens){
const candidate=line?line+separator+token:token;
if(measured(candidate)<=width){line=candidate;continue;}
if(line){push(line);line='';}
if(measured(token)<=width){line=token;continue;}
// Oversized words are split even when they follow another word.
for(const unit of graphemes(token)){
if(line&&measured(line+unit)>width){push(line);line='';}
line+=unit;
if(measured(line)>width){push(line);line='';}
}
}
if(line)push(line);
}
return {lines,height:lines.length*lineHeight,overflowLines:lines.filter(x=>x.overflow).length,scope:'Whitespace is normalized in word mode; explicit paragraphs and grapheme clusters retained. No language-specific hyphenation, punctuation rules or pagination.'};
}
Check the page and its downloadable file together
A language switch can appear correct while its download still supplies the previous language. Compare the heading, explanation, filename and actual document language as one flow. I have not transferred original PDFs containing customer identity into this article. Instead, the inspected wrapping approach has become a new anonymous example.
Eight local checks covered Unicode clusters, long tokens, whitespace and measurement errors in the new planner. Its code and public response are checked, while new rendered browser review remains unperformed as existing Studio work is preserved. An actual integration should continue through its selected font, narrow viewport, longest heading and downloadable document. The example helps expose decisions; it does not certify that entire flow.